Is there a better way of implementing nested loops in clojure?
As a beginner I have written this code of nested loop for comparing difference between dates in days.
Comparing this with nested loops in java using for or while.
(def my-vector [{:course-type "clojure"
:start-date "2021-01-25"
:end-date "2021-02-06"}
{:course-type "r"
:start-date "2021-01-15"
:end-date "2021-02-06"}
{:course-type "python"
:start-date "2020-12-05"
:end-date "2021-01-05"}
{:course-type "java"
:start-date "2020-09-15"
:end-date "2020-10-20"}
])
(defn find-gap-in-course [mycourses]
(println "Finding gap between dates....")
(loop [[course1 & mycourses] mycourses]
(loop [[course2 & mycourses] mycourses]
(when (and
(and (not-empty course1) (not-empty course2))
(> (-> java.time.temporal.ChronoUnit/DAYS
(.between
(LocalDate/parse (course2 :end-date))
(LocalDate/parse (course1 :start-date)))) 30))
(println "Dates evaluated are =" (course2 :end-date) (course1 :start-date))
(println "Gap of > 30 days between dates ="
(-> java.time.temporal.ChronoUnit/DAYS
(.between
(LocalDate/parse (course2 :end-date))
(LocalDate/parse (course1 :start-date)))))
(do true)))
(do false)
(if course1 (recur mycourses))))
(find-gap-in-course my-vector)
Learning to program in Clojure requires that one learn to think a bit differently because the tricks and techniques which people become accustomed to using in imperative programming may not serve as well in Clojure. For example in a nested loop, such as you've shown above, what are you trying to do? You're trying to match all of the elements of mycourses against one another and do some processing. So let's define a function which gives us back all the combinations of elements in a collection 1:
(defn combos[c] ; produce all combinations of elements in a collection
(for [x c y c] (vector x y)))
This is a very simple function which matches all the elements of a collection against one another and returns the accumulated pairings. For example, if you invoke
(combos [1 2 3])
you'll get back
([1 1] [1 2] [1 3] [2 1] [2 2] [2 3] [3 1] [3 2] [3 3])
This will work with any collection. If you invoke combos as
(combos '("abc" 1 [0 9]))
you'll get back
(["abc" "abc"] ["abc" 1] ["abc" [0 9]] [1 "abc"] [1 1] [1 [0 9]] [[0 9] "abc"] [[0 9] 1] [[0 9] [0 9]])
So I think you can see where we're going here. Rather than running a nested loop against a collection, you can just create a collection of combinations of elements and run a simple loop over those combinations:
(defn find-gap-in-course [mycourses]
(loop [course-combos (combos mycourses)]
(let [combi (first course-combos)
[course1 course2] combi]
; ...processing of course1 and course2 here...
(recur (rest mycourses)))))
But what if we don't want to consider the cases where a course is matched against itself? In that case another function to only return the desired cases is useful:
(defn different-combos [c] ; all combinations where [1] <> [2]
(filter #(not= (% 0) (% 1)) (combos c)))
Use whatever works best for you.
1 About here the Clojure cognoscenti are probably screaming "NO! NO! Use clojure.math.combinatorics!". When teaching I like to give useful examples which the student can see, read, and learn from. YMMV.
Here is how I would write the above code, starting from my favorite template project. I have included some unit tests to illustrate what is occurring in the code:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:import
[java.time LocalDate]))
(defn days-between
"Find the (signed) interval in days between two LocalDate strings."
[localdate-1 localdate-2]
(.between java.time.temporal.ChronoUnit/DAYS
(LocalDate/parse localdate-1)
(LocalDate/parse localdate-2)))
(dotest ; from tupelo.test
(is= -5 (days-between "2021-01-25" "2021-01-20"))
(is= 5 (days-between "2021-01-25" "2021-01-30")))
(defn course-pairs-with-30-day-gap
"Return a list of course pairs where the start date of the first course
is at least 30 days after the end of the second."
[courses]
(for [c1 courses
c2 courses
:let [start-1 (:start-date c1)
end-2 (:end-date c2)
gap-days (days-between end-2 start-1)]
:when (< 30 gap-days)]
[(:course-type c1) (:course-type c2) gap-days]))
with result
(dotest
(let [all-courses [{:course-type "clojure"
:start-date "2021-01-25"
:end-date "2021-02-06"}
{:course-type "r"
:start-date "2021-01-15"
:end-date "2021-02-06"}
{:course-type "python"
:start-date "2020-12-05"
:end-date "2021-01-05"}
{:course-type "java"
:start-date "2020-09-15"
:end-date "2020-10-20"}]]
(is= (course-pairs-with-30-day-gap all-courses)
[["clojure" "java" 97]
["r" "java" 87]
["python" "java" 46]])))
In the output, I left the names of course-1, course-2, and the gap in days to verify the calculation is the intended one. This could be modified or extended for production use, of course.
In clojure we normally use pre-existing functions like for (technically a macro) instead of low-level tools like loop/recur. The modifiers :let and :when make them extra-powerful for analyzing & transforming data structures.
Please see this list of documentation sources,
especially books like Getting Clojure and the Clojure CheatSheet.
Related
I'm trying to fill a Clojure vector with values from a map.
I have another vector of specific keys in the map the store the values I need.
I need to iterate the key-vec, get the values from the map and store them in another vector.
I've tried using loop+recur:
(let [keys-vec (:keys-vec item)
my-map (:my-map item)]
(loop [newV []
i 0]
(if (< i (count keys-vec))
(recur
(conj newV (get my-map (get keys-vec i)))
(inc i))
newV)))
And it worked.
But I know Clojure is known for it's minimalistic/efficient code writing style and I wanted to know whether there's a better way.
Any ideas?
I'd say, that the most idiomatic way will be to use original vector. I don't see any reason to explicitly clone an immutable datastructure.
You want the select-keys function to extract only the keys of interest from your map. See: http://clojuredocs.org/clojure.core/select-keys
Then, use the vals function to extract all of the values from the filtered map:
> (def my-map {:a 1 :b 2 :c 3})
> (def my-keys [:a :b])
> (select-keys my-map my-keys)
{:a 1, :b 2}
> (def filtered-map (select-keys my-map my-keys))
> filtered-map
{:a 1, :b 2}
> (vals filtered-map)
(1 2)
You should keep a browser tab open to the Clojure Cheatsheet at all times. It is invaluable for finding the functions you want. Keep studying it repeatedly, as you'll keep finding new things for years. This is my favorite version:
http://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html
Using Alan Thompson's example:
(def my-map {:a 1 :b 2 :c 3})
(def my-keys [:a :b])
... a simple and fast solution is
(mapv my-map my-keys)
;[1 2]
The alternative
(vals (select-keys my-map my-keys))
;(2 1)
... may not (and, in this case, does not) maintain the order given by my-keys.
generally i want to know when we have array of object that have some property can same the "Object Literal in JavaScript" can calculated with specific function. i want to create that property for my array in clojure to apply some calculation on them such as sorting or more simpler finding maximum according to that property.for example how try find maximum in this example?
(def aSqh (fn [x] (* x x)))
(def maSqh (max (apply aSqh [1 2 3 4])))
the have error that output is object and not number
You seem to be thinking of a mapping operation (take a function of one argument and a collection, replace every element with the result of the function on that element), which in Clojure is called map. apply is a function for plumbing collections into functions as if they were given each element as a separate argument. Usually you want to use it with variadic functions (i.e. functions such as max, that take a variable number of arguments). For instance
(def maSqh (apply max (map aSqh [1 2 3 4]))) ;;=> 16
If you want to preserve the datatype of a collection after performing a mapping, you can use into and empty:
(defn preserving-map [f coll]
(into (empty coll) (map f coll)))
(preserving-map aSqh [1 2 3 4]) ;;=>[1 4 9 16]
(preserving-map aSqh #{1 2 3 4}) ;;=> #{1 4 9 16}
but this removes the (useful) laziness that map usually gives us. For the particular case of vectors (like [1 2 3 4]), this use case is common enough that there is mapv which eagerly performs mappings and puts them into a vector.
Is there any simplier way to find the last element of an array in clojure except this function?
(fn [l] (if (empty? (rest l)) (first l) (recur (rest l))))
For vectors, use peek for constant time
user=> (peek [1 2 3 4 5])
5
For Java arrays,
user=> (let [a (to-array [1 2 3 4 5])] (aget a (dec (alength a))))
5
For a general collection, you can get the last item in linear time with last. It is defined similarly to what you have done.
user=> (source last)
(def
^{:arglists '([coll])
:doc "Return the last item in coll, in linear time"
:added "1.0"
:static true}
last (fn ^:static last [s]
(if (next s)
(recur (next s))
(first s))))
The simplest way is to use (last l) that works in linear time (http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/last)
Another possibility is to reverse your collection and take the first element: ((comp first reverse) l). But that's rather slow as reverse returns a non-lazy sequence. Note: comp returns a composition of its arguments (functions) (http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/comp)
You can also convert the collection to a vector first and then apply peek: ((comp peek vec) l). This should have a better performance.
Another one: determine the length of your collection and take the last element (#(nth % (dec (count %))) l).
These functions work for all collection types (e.g. vectors, lists, ...). There are no arrays per se in Clojure (except you want to use Java arrays).
I'm trying to parse a nested array structure of the following form:
[element [[child1] [child2] [child3 [[subchild1] [subchild2]]]]]
I would also like to return a list with all symbols (and nothing else), regardless of nesting depth; however, I'm not looking for flatmap or flatten etc, since I need to perform more complicated additional work on every element.
This is what I came up with so far:
(defn create-element [rs element]
(if (symbol? element)
(cons element rs)
rs))
(defn parse
([rs element] (create-element rs element))
([rs element [children & more]] (if (nil? more)
(parse (parse rs element) (first children))
(parse (parse rs element) (first children) more))))
(defn n-parse [element]
(apply parse () element))
This works fine for the following input:
=> (n-parse ['bla [['asd] ['kkk] ['sss]]])
(sss kkk asd bla)
But this doesn't work:
=> (n-parse ['bla [['asd] ['kkk [['ooo]]] ['sss]]])
(sss kkk asd bla)
I'm still trying to wrap around my head around the types but can't seem to manage to get it right. For example, Haskell makes this easy with pattern matching etc, whereas Clojure doesn't allow same arity function overloading.
Also is there a more concise / idiomatic way (without having to resort to if?) I'd prefer pure Clojure solutions (no external libs) since this is actually for a Clojurescipt project.
Many thanks for any help!
I don't see whats wrong with flatten. If you want to do some work on the items first, do the work first and then flatten the result:
(defn map-tree
"Example: (map-tree + [1 2 [3 5]] [3 4 [5 6]])"
[f & trees]
(if (every? coll? trees)
(apply map (partial map-tree f) trees)
(apply f trees)))
(defmulti transformator identity)
;; transform 'sss element into something special
(defmethod transformator 'sss [_] "sss")
;; default transformation
(defmethod transformator :default [v] v)
Test:
user> (flatten (map-tree transformator '[bla [[asd] [kkk] [sss]]]))
(bla asd kkk "sss")
user>
Would that not work?
I am trying to write a macro in Clojure that allows for evaluation of a series of simple "def" expressions. I am a n00b when it comes to macros. The idea is that
(my-defs y1 1
y2 "taco")
should expand to
(do (def y1 1) (def y2 "taco"))
The following code accomplishes this for the special case of two defs
(defmacro my-defs
[& args]
`(do
(def ~(first args) ~(second args))
(def ~(nth args 2) ~(nth args 3) )))
which is nice, but I am having trouble generalizing this. I tried out a few naive things involving looping through bindings of the elements of (partition 2 args) but I always got garbage (I know this isn't very specific but the diversity and extent of the garbage seemed a bit too much to report here). How do I loop over these are and evaluate my defs?
P.S.
The macro my-defs is a toy. What i really want to accomplish in the end is a littel helper macro to instantiate a bunch of multimethod instances. Currently I have large chunks of code that look like
(defmethod f [A B] [x] "AB")
(defmethod f [A A] [x] "AA")
(defmethod f [C B] [x] "CB")
which is a little unsightly. It would be nice if I could do something like
(defmethods f
[A B] [x] "AB"
[A A] [x] "AA"
[C B] [x] "CB")
instead.
It looks to me like you're looking for the ~# macro expansion/unquote.
(defmacro defmethods [n & defs]
`(do ~#(map (fn [[a1 a2 a3]]
`(def ~n ~a1 ~a2 ~a3))
(partition 3 defs))))