using java.lang.invoke.MethodHandle in clojure - clojure

I'm following a tutorial here: https://www.baeldung.com/java-method-handles
In clojure, I've got a simple example:
(import (java.lang.invoke MethodHandles
MethodHandles$Lookup
MethodType
MethodHandle))
(defonce +lookup+ (MethodHandles/lookup))
(def ^MethodHandle concat-handle (.findVirtual +lookup+
String
"concat"
(MethodType/methodType String String)))
(.invokeExact concat-handle (into-array Object ["hello" "there"]))
which gives an error:
Unhandled java.lang.invoke.WrongMethodTypeException
expected (String,String)String but found (Object[])Object
Invokers.java: 476 java.lang.invoke.Invokers/newWrongMethodTypeException
Invokers.java: 485 java.lang.invoke.Invokers/checkExactType
REPL: 26 hara.object.handle/eval17501
REPL: 26 hara.object.handle/eval17501
Compiler.java: 7062 clojure.lang.Compiler/eval
Compiler.java: 7025 clojure.lang.Compiler/eval
core.clj: 3206 clojure.core/eval
core.clj: 3202 clojure.core/eval
main.clj: 243 clojure.main/repl/read-eval-print/f
is there a way to get invoke working?

You can use .invokeWithArguments which will figure out the correct arity from the supplied arguments:
(.invokeWithArguments concat-handle (object-array ["hello" "there"]))
=> "hellothere"
Or you can use .invoke, but you'll need MethodHandle.asSpreader to apply the varargs correctly to String.concat which has fixed arity:
(def ^MethodHandle other-handle
(.asSpreader
concat-handle
(Class/forName "[Ljava.lang.String;") ;; String[].class
2))
(.invoke other-handle (into-array String ["hello" "there"]))
=> "hellothere"
I'm not sure how to make this work with .invokeExact from Clojure, if it's possible.
The symbolic type descriptor at the call site of invokeExact must exactly match this method handle's type. No conversions are allowed on arguments or return values.
This answer has more explanation on restrictions of .invoke and .invokeExact.

Some interesting benchmarks based on #TaylorWood's answer:
(with-out-str
(time (dotimes [i 1000000]
(.concat "hello" "there"))))
=> "\"Elapsed time: 8.542214 msecs\"\n"
(with-out-str
(def concat-fn (fn [a b] (.concat a b)))
(time (dotimes [i 1000000]
(concat-fn "hello" "there"))))
=> "\"Elapsed time: 3600.357352 msecs\"\n"
(with-out-str
(def concat-anno (fn [^String a b] (.concat a b)))
(time (dotimes [i 1000000]
(concat-anno "hello" "there"))))
=> "\"Elapsed time: 16.461237 msecs\"\n"
(with-out-str
(def concat-reflect (.? String "concat" :#))
(time (dotimes [i 1000000]
(concat-reflect "hello" "there"))))
=> "\"Elapsed time: 1804.522226 msecs\"\n"
(with-out-str
(def ^MethodHandle concat-handle
(.findVirtual +lookup+
String
"concat"
(MethodType/methodType String String)))
(time (dotimes [i 1000000]
(.invokeWithArguments concat-handle (into-array Object ["hello" "there"])))))
=> "\"Elapsed time: 1974.824815 msecs\"\n"
(with-out-str
(def ^MethodHandle concat-spread
(.asSpreader concat-handle
(Class/forName "[Ljava.lang.String;") ;; String[].class
2))
(time (dotimes [i 1000000]
(.invoke other-handle (into-array String ["hello" "there"])))))
=> "\"Elapsed time: 399.779913 msecs\"\n"

Related

How to split string in clojure on number and convert it to map

I have a string school_name_1_class_2_city_name_3 want to split it to {school_name: 1, class:2, city_name: 3} in clojure I tried this code which didn't work
(def s "key_name_1_key_name_2")
(->> s
(re-seq #"(\w+)_(\d+)_")
(map (fn [[_ k v]] [(keyword k) (Integer/parseInt v)]))
(into {}))
You are looking for the ungreedy version of regex.
Try using #"(\w+?)_(\d+)_?" instead.
user=> (->> s (re-seq #"(\w+?)_(\d+)_?"))
(["key_name_1_" "key_name" "1"] ["key_name_2" "key_name" "2"])
When faced with a problem, just break it down and solve one small step at a time. Using let-spy-pretty from the Tupelo library allows us to see each step of the transformation:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.string :as str]))
(defn str->keymap
[s]
(let-spy-pretty
[str1 (re-seq #"([a-zA-Z_]+|[0-9]+)" s)
seq1 (mapv first str1)
seq2 (mapv #(str/replace % #"^_+" "") seq1)
seq3 (mapv #(str/replace % #"_+$" "") seq2)
map1 (apply hash-map seq3)
map2 (tupelo.core/map-keys map1 #(keyword %) )
map3 (tupelo.core/map-vals map2 #(Long/parseLong %) )]
map3))
(dotest
(is= (str->keymap "school_name_1_class_2_city_name_3")
{:city_name 3, :class 2, :school_name 1}))
with result
------------------------------------
Clojure 1.10.3 Java 11.0.11
------------------------------------
Testing tst.demo.core
str1 =>
(["school_name_" "school_name_"]
["1" "1"]
["_class_" "_class_"]
["2" "2"]
["_city_name_" "_city_name_"]
["3" "3"])
seq1 =>
["school_name_" "1" "_class_" "2" "_city_name_" "3"]
seq2 =>
["school_name_" "1" "class_" "2" "city_name_" "3"]
seq3 =>
["school_name" "1" "class" "2" "city_name" "3"]
map1 =>
{"city_name" "3", "class" "2", "school_name" "1"}
map2 =>
{:city_name "3", :class "2", :school_name "1"}
map3 =>
{:city_name 3, :class 2, :school_name 1}
Ran 2 tests containing 1 assertions.
0 failures, 0 errors.
Passed all tests
Once you understand the steps and everything is working, just replace let-spy-pretty with let and continue on!
This was build using my favorite template project.
Given
(require '[clojure.string :as str])
(def s "school_name_1_class_2_city_name_3")
Following the accepted answer:
(->> s (re-seq #"(.*?)_(\d+)_?")
(map rest) ;; take only the rest of each element
(map (fn [[k v]] [k (Integer. v)])) ;; transform second as integer
(into {})) ;; make a hash-map out of all this
Or:
(apply hash-map ;; the entire thing as a hash-map
(interleave (str/split s #"_(\d+)(_|$)") ;; capture the names
(map #(Integer. (second %)) ;; convert to int
(re-seq #"(?<=_)(\d+)(?=(_|$))" s)))) ;; capture the integers
or:
(zipmap
(str/split s #"_(\d+)(_|$)") ;; extract names
(->> (re-seq #"_(\d+)(_|$)" s) ;; extract values
(map second) ;; by taking only second matched groups
(map #(Integer. %)))) ;; and converting them to integers
str/split leaves out the matched parts
re-seq returns only the matched parts
(_|$) ensures that the number is followed by a _ or is at an end position
The least verbose (where (_|$) could be replaced by _?:
(->> (re-seq #"(.*?)_(\d+)(_|$)" s) ;; capture key vals
(map (fn [[_ k v]] [k (Integer. v)])) ;; reorder coercing values to int
(into {})) ;; to hash-map

Looking for a way to partition date on a monthly basis in Clojure

Want to split a date range into monthly chunks.
example Input - [10/20/2019 - 12/20/2019]
example output - { [10/20/2019 10/31/2019] [11/01/2019 11/30/2019] [12/012019 12/20/2019] }
Thank you
You can use java-time:
(refer-clojure :exclude [range iterate format max min])
(use 'java-time)
(->> (iterate plus (local-date 2019 10 20) (days 1))
(take-while #(before? % (local-date 2019 12 21)))
(partition-by month)
(map (fn [dates] [(first dates) (last dates)])))
Output:
([#object[java.time.LocalDate 0x26c16faf "2019-10-20"]
#object[java.time.LocalDate 0x4113c834 "2019-10-31"]]
[#object[java.time.LocalDate 0x7d0a5212 "2019-11-01"]
#object[java.time.LocalDate 0x249fe02f "2019-11-30"]]
[#object[java.time.LocalDate 0x7345f070 "2019-12-01"]
#object[java.time.LocalDate 0x26d66577 "2019-12-20"]])
here is a simple draft of what you can do (with java interop, no external libs):
first of all let's make an iteration by month, starting with the specified one:
(defn by-month [[mm yyyy]]
(iterate #(.plusMonths % 1)
(java.time.YearMonth/of yyyy mm)))
user> (take 4 (by-month [10 2019]))
;;=> (#object[java.time.YearMonth 0x62fc8302 "2019-10"]
;; #object[java.time.YearMonth 0x1a7bc211 "2019-11"]
;; #object[java.time.YearMonth 0x6a466e83 "2019-12"]
;; #object[java.time.YearMonth 0x652ac30f "2020-01"])
then get start and end date for each YearMonth:
(defn start-end [^java.time.YearMonth ym]
[(.atDay ym 1) (.atEndOfMonth ym)])
;;=> ([#object[java.time.LocalDate 0xe880abb "2019-10-01"]
;; #object[java.time.LocalDate 0x54aadf50 "2019-10-31"]]
;; [#object[java.time.LocalDate 0x14c1b42d "2019-11-01"]
;; #object[java.time.LocalDate 0x32d0a22c "2019-11-30"]])
now, wrap it up in a function of ranges by your input dates:
(defn day-range [[mm1 dd1 yyyy1] [mm2 dd2 yyyy2]]
(let [start (java.time.LocalDate/of yyyy1 mm1 dd1)
end (java.time.LocalDate/of yyyy2 mm2 dd2)
internal (->> [mm1 yyyy1]
by-month
(mapcat start-end)
(drop 1)
(take-while #(neg? (compare % end))))]
(partition 2 `(~start ~#internal ~end))))
user> (day-range [10 20 2019] [12 20 2019])
;;=> ((#object[java.time.LocalDate 0x6a8f92f2 "2019-10-20"]
;; #object[java.time.LocalDate 0x10135df3 "2019-10-31"])
;; (#object[java.time.LocalDate 0x576bcff7 "2019-11-01"]
;; #object[java.time.LocalDate 0x7b5ed908 "2019-11-30"])
;; (#object[java.time.LocalDate 0x6b2117a9 "2019-12-01"]
;; #object[java.time.LocalDate 0x57bf0864 "2019-12-20"]))
now you can postprocess each start-end pair as you need:
(map (fn [[^java.time.LocalDate start ^java.time.LocalDate end]]
(let [fmt (java.time.format.DateTimeFormatter/ofPattern "MM/dd/yyyy")]
[(.format start fmt) (.format end fmt)]))
(day-range [10 20 2019] [12 20 2019]))
;;=> (["10/20/2019" "10/31/2019"]
;; ["11/01/2019" "11/30/2019"]
;; ["12/01/2019" "12/20/2019"])
another way is to iterate by day, and then partition by [month year], collecting first-last afterwards:
(defn ranges [[mm1 dd1 yyyy1] [mm2 dd2 yyyy2]]
(let [start (java.time.LocalDate/of yyyy1 mm1 dd1)
end (java.time.LocalDate/of yyyy2 mm2 dd2)]
(->> start
(iterate (fn [^java.time.LocalDate curr] (.plusDays curr 1)))
(take-while (fn [^java.time.LocalDate dt] (not (pos? (compare dt end)))))
(partition-by (fn [^java.time.LocalDate dt] [(.getMonthValue dt) (.getYear dt)]))
(map (juxt first last)))))
user> (ranges [10 20 2019] [12 20 2019])
;;=> ([#object[java.time.LocalDate 0x383f6a9e "2019-10-20"]
;; #object[java.time.LocalDate 0x2ca14c39 "2019-10-31"]]
;; [#object[java.time.LocalDate 0x74d1974 "2019-11-01"]
;; #object[java.time.LocalDate 0x5f6c16cc "2019-11-30"]]
;; [#object[java.time.LocalDate 0x74f63a42 "2019-12-01"]
;; #object[java.time.LocalDate 0x4b90c388 "2019-12-20"]])
which produces some unneeded intermediate vals, but also gives you a way to split ranges however you want.
Straight Java interop is the way to go here:
(let [start-ld (LocalDate/parse "2019-10-20")
start-bom (.with start-ld (TemporalAdjusters/firstDayOfMonth))
start-eom (.with start-ld (TemporalAdjusters/lastDayOfMonth))]
start-bom => #object[java.time.LocalDate 0x1a69aaa8 "2019-10-01"]
start-eom => #object[java.time.LocalDate 0x329970b5 "2019-10-31"]
You can increment the month like so:
next-bom (.plusMonths start-bom 1)
to get
next-bom => #object[java.time.LocalDate 0x21ced418 "2019-11-01"]
and then you can write a loop.

How to format string in Clojure by using map-indexed and vector

I am trying to read content from a file which is in the format :
ID|Name|Country-Name|Phone-Number eg:
1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
I am using following code to fetch data from it :
(
map-indexed
#(vector %1 %2)
(
map #(vec(.split #"\|" %1))
(
line-seq (clojure.java.io/reader "test.txt")
)
)
)
with this code I am getting this output:
([0 ["1" "Austin" "Germany" "34-554567"]] [1 ["2" "Mary Jane" "Australia" "45-78647"]] [2 ["3" "King Kong" "New-Zealand" "35-467533"]])
I want the output to be like:
ID:["name" "country-name" "phone-number"]
ID:["name" "country-name" "phone-number"]
eg:
1:["Austin" "Germany" "34-554567"]
2:["Mary Jane" "Australia" "45-78647"]
where ID is to be incremented by 1 (start from 1,2,3 and so on) and each result lists the identity or ID, succeeded by the data united with the ID and it should be sorted by an ID.
What changes do I do to my code to make this happen?
maybe
(into {} (map-indexed
#(vector (inc %1) (rest %2))
(repeat 2 ["1" "Austin" "Germany" "34-554567"])))
It looks like your data already has indexes in it:
(def data
"1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
3|King Kong|New-Zealand|35-467533 ")
(defn fmt [line]
(let [sections (-> line
str/trim
(str/split #"\|")) ]
sections) )
(defn run []
(let [lines (vec (str/split-lines data)) ]
(mapv fmt lines)))
(run)
with result:
sections => ["1" "Austin" "Germany" "34-554567"]
sections => ["2" "Mary Jane" "Australia" "45-78647"]
sections => ["3" "King Kong" "New-Zealand" "35-467533"]
If you wanted to throw away the indexes in the data, you could generate your own like so:
(defn fmt [idx line]
(let [sections (-> line
str/trim
(str/split #"\|"))
sections-keep (rest sections)
result (apply vector idx sections-keep)]
result))
(defn run []
(let [lines (vec (str/split-lines data))]
(mapv fmt (range 1 1e9) lines)))
Update
If you want to use a disk file, do this:
(def data
"1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
3|King Kong|New-Zealand|35-467533 ")
(defn fmt [idx line]
(let [sections (-> line
str/trim
(str/split #"\|"))
sections-keep (rest sections)
result (apply vector idx sections-keep)]
result))
(defn run [filename]
(let [lines (vec (str/split-lines (slurp filename)))]
(mapv fmt (range 1 1e9) lines)))
(let [filename "/tmp/demo.txt"]
(spit filename data)
(run filename))
A guess:
(def data
"1|Austin|Germany|34-554567
2|Mary Jane|Australia|45-78647
3|King Kong|New-Zealand|35-467533")
(->> (for [line (clojure.string/split data #"[ ]*[\r\n]+[ ]*")]
(-> line (clojure.string/split #"\|") rest vec))
(map vector (rest (range))))
; ([1 ["Austin" "Germany" "34-554567"]]
; [2 ["Mary Jane" "Australia" "45-78647"]]
; [3 ["King Kong" "New-Zealand" "35-467533"]])
Although I'm not sure why you want to have explicit auto-generated ID in the result and ignore the serial-number you've got in the original data.
Optionally add (into (sorted-map)) to the end so you get the sequential ids mapped to values, and this retains the id order unlike a hash-map.

Having some trouble getting clojure.spec exercise-fn working

In trying to use spec library, I'm getting errors in attempting to use exercise-fn. I've reduced this to the posted example at the main guide page with no change.
relevant code:
(ns spec1
(:require [clojure.spec.alpha :as s]))
;;this and the fdef are literal copies from the example page
(defn adder [x] #(+ x %))
(s/fdef adder
:args (s/cat :x number?)
:ret (s/fspec :args (s/cat :y number?)
:ret number?)
:fn #(= (-> % :args :x) ((:ret %) 0)))
Now, typing the following
(s/exercise-fn adder)
gives the error:
Exception No :args spec found, can't generate clojure.spec.alpha/exercise-fn (alpha.clj:1833)
Dependencies/versions used, [org.clojure/clojure "1.9.0-beta3"]
[org.clojure/tools.logging "0.4.0"]
[org.clojure/test.check "0.9.0"]
Anyone have any ideas as to why this is breaking? Thanks.
You need to backquote the function name, which will add the namespace prefix:
(s/exercise-fn `adder)
For example, in my test code:
(s/fdef ranged-rand
:args (s/and
(s/cat :start int? :end int?)
#(< (:start %) (:end %) 1e9)) ; need add 1e9 limit to avoid integer overflow
:ret int?
:fn (s/and #(>= (:ret %) (-> % :args :start))
#(< (:ret %) (-> % :args :end))))
(dotest
(when true
(stest/instrument `ranged-rand)
(is (thrown? Exception (ranged-rand 8 5))))
(spyx (s/exercise-fn `ranged-rand)))
Which results in:
(s/exercise-fn (quote tst.tupelo.x.spec/ranged-rand))
=> ([(-2 0) -1] [(-4 1) -1] [(-2 0) -2] [(-1 0) -1] [(-14 6) -4]
[(-36 51) 45] [(-28 -3) -7] [(0 28) 27] [(-228 -53) -130] [(-2 0) -1])
Note that the namespace-qualified function name tst.tupelo.x.spec/ranged-rand is used.

Getting date intervals from vector

I have a day ordered vector in clojure that's something like
(def a [{:day #inst "2017-01-01T21:57:14.873-00:00" :balance 100.00},
{:day #inst "2017-01-05T21:57:14.873-00:00" :balance -50.00},
{:day #inst "2017-01-10T21:57:14.873-00:00" :balance -100.00},
{:day #inst "2017-01-14T21:57:14.873-00:00" :balance 50.00},
{:day #inst "2017-01-17T21:57:14.873-00:00" :balance -200.00}])
I would like to get all date intervals where balance is negative. The
period ends when balance gets positive on next position or when
balance changes its value but keeps negative, like:
[{:start #inst "2017-01-05T21:57:14.873-00:00"
:end #inst "2017-01-09T21:57:14.873-00:00"
:value -50.00},
{:start "2017-01-10T21:57:14.873-00:00"
:end "2017-01-13T21:57:14.873-00:00"
:value -100.00},
{:start "2017-01-17T21:57:14.873-00:00"
:value -200.00}]
I've found this and this but I couldn't adapt to my data. How can I do it?
Cheating a little with the dates by using this not yet implemented function, which is supposed to decrement a date:
(defn dec-date [d] d)
This would be one way to solve the problem:
(->> a
(partition-by #(-> % :balance neg?))
(drop-while #(-> % first :balance pos?))
(mapcat identity)
(map (juxt :day :balance))
(partition 2 1)
(map (fn [[[date-1 val-1] [date-2 val-2]]]
(if (neg? val-1)
{:start date-1
:end (dec-date date-2)
:value val-1}
{:start date-2
:value val-1})))))
;;=> ({:start "05/01", :end "10/01", :value -50.0} {:start "10/01", :end "14/01", :value -100.0} {:start "17/01", :value 50.0})
If dec-date was properly implemented then the first :end would be "09/01" rather than "10/01" and the second :end would be "13/01" rather than "14/01", which would be the correct answer.
Now hopefully an improved answer that will work for more edge cases:
(->> a
(partition-by #(-> % :balance neg?))
(drop-while #(-> % first :balance pos?))
(mapcat identity)
(map (juxt :day :balance))
(partition-all 2 1)
(keep (fn [[[date-1 val-1] [date-2 val-2]]]
(cond
(neg? val-1) (cond-> {:start date-1
:value val-1}
date-2 (assoc :end (dec-date date-2)))
(pos? val-1) nil
:else {:start date-2
:value val-1}))))