I'm learning clojure and I've written a small function that given a directory pathname returns a sorted-map of files in descending order according to their mtimes. Here it is:
(defn get-sorted-mtimes [dir]
(loop [sm (sorted-map-by >)
lst (for [f (.listFiles (File. dir))]
(let [k (.lastModified f)
v (.getName f)]
[k v]))]
(if (seq lst)
(recur (assoc sm ((first lst) 0) ((first lst) 1))
(rest lst))
sm)))
My question is: is there a way to pass the comparator method as a symbol name to the function and make it sort by asc or desc order accordingly? I mean something like:
(defn get-sorted-mtimes [dir <sym>]
(loop [sm (sorted-map-by <sym>)
...
Also, is there a more clojuresque way of accomplishing this task?
Well, for the record, this is the final form of the function that does exactly what I need it to:
(defn get-sorted-mtimes [dir comp]
(loop [sm (sorted-map-by (comparator comp))
lst (for [f (.listFiles (File. dir))]
(let [k (.lastModified f)
v (.getName f)]
[k v]))]
(if (seq lst)
(recur (assoc sm ((first lst) 0) ((first lst) 1))
(rest lst))
sm)))
If the (comparator) function isn't used, you get a java.lang.ClassCastException exception.
> is just a function like any other in Clojure. So there is nothing to stop you passing it as an argument.
In fact I'd say that is good Clojure style to do it this way. Clojure is a functional programming language at heart, so there's nothing wrong with using higher order functions where appropriate!
Some other minor suggestions:
Use comparator instead of <sym> as a name for your function parameter. I think that's more descriptive and also more consistent with Clojure's normal naming conventions.
You could also add another function parameter to determine what you are comparing so that you can pass a function like get-mtime (a simple function that returns the mtime of a file).
I would suggest making a sequence of files the input to the function rather than a directory. Then your function is more general and can del with things like e.g. recursive directory scans in exactly the same manner
I'd suggest using (into sm lst) - much simpler than your big loop/if/recur construct!
Then you can do really nice things like (get-sorted-files (list-files-recursive dir) > get-mtime) - or any similar combinations you can think of!
Related
At the end of CLOJURE for the BRAVE and TRUE's Chapter 4 there's an exercise: make an append function that appends a new entry to a list.
What's the most efficient way to do so?
From what I understand of datatypes in general, if conj prepends elements to a list, that simply means that consistently appending to a list is either silly or the choice of using a list type was silly.
Anyway, the solution I've written is this
(defn append
[lst item]
(into '() (conj (into '() lst) item)))
Well, that's actually the same as
(defn append
[lst item]
(reverse (conj (reverse lst) item)))
I believe, so probably is costly because I reverse the list twice?
Another solution I could think of is
(defn append
[lst item]
(apply list (conj (apply vector lst) item)))
But they all seem to traverse the sequence of values twice, so I don't see why any one shoiuld be better than another.
Is there the proper way to accomplish the task?
to avoid the double traversal, you could use the classic recursive approach. Something like this:
(defn append [lst item]
(loop [res [] lst lst]
(if (seq lst)
(recur (conj res (first lst))
(rest lst))
(seq (conj res item)))))
so, it just rebuilds the list from scratch.
To make it's performance better in clojure, you can optimize it with transient collection:
(defn append' [lst item]
(loop [res (transient []) lst lst]
(if (seq lst)
(recur (conj! res (first lst))
(rest lst))
(seq (persistent! (conj! res item))))))
but yes, as another answer proposes, you should carefully pick the proper data structure for every specific task, so in practice you would want to use vector.
As of concat variant, it comes for free (since it is lazy), but it has this known pitfall, which is stack overflow on applying a lot of stacked lazy functions:
user> (defn append-concat [lst item]
(concat lst [item]))
user> (reduce append-concat () (range 1000000))
;; Error printing return value (StackOverflowError) at clojure.lang.LazySeq/sval (LazySeq.java:42).
There is a reason, why conj adds a new item at the start of the list and not at its end. Because you have to traverse the list to add something at its end. This is not very efficient. And it is because of the nature of a linked list. That's why in lisp you cons new items onto a list and at the very end reverse it. This way, you traverse the list just once while building it.
In Clojure, if you want to add to a list at the end, it is more idiomatic not to use a list but instead the vector type. On a vector, conj adds right to the end.
But let's say, you want to traverse a list once and add to the end.
That is actually:
(defn my-append [lst item] (concat lst (list item)))
(my-append '(1 2 3) 4)
;; (1 2 3 4)
but as I said, if you want to add repeatedly at the end, don't use a list, but a vector and conj to the end.
;; more idiomatic clojure in thisst to add something at its end. This is not very efficient. And it is because of the nature of a linked list. Tha case
(def v [1 2 3])
(conj v 4) ;; [1 2 3 4] ;; unbeatable in efficience, because no traversal!
;; and convert to a list
;; e.g.
(def l (conj v 4))
(seq l) ;; (1 2 3 4)
Given the following data structure, I want to ask for "services-list" (a component) and receive back "entity-list" (a style).
(def style->components {"entity-list" ["services-list" "employee-list" "clients-list"]})
My solution is not so elegant:
(defn get-style-name [comp-name]
(-> (filter (fn [map-entry]
(let [v (val map-entry)
found-comp (some #(= % comp-name) v)]
found-comp
)) style->components)
first
first))
Is there a better way? Perhaps my problem started with the way I structured the data.
you can make it shorter and more clojurish this way:
(defn get-style-name [comp-name]
(ffirst (filter (fn [[_ v]]
(some #{comp-name} v))
component->style)))
there is a function ffirst, that works exactly like (first (first %))
using a destructuring in the filter function signature, you can retrieve the value of the map entry, avoiding unneeded let
instead of this function in some: #(= % comp-name) it is quite common to use the set: #{comp-name}
then you can use some instead of filter, as it returns the first logical true value returned by function, so you can remove ffirst:
(defn get-style-name [comp-name]
(some (fn [[k v]]
(when (some #{comp-name} v) k))
component->style))
also, if you change your data structure to use set instead of vector, you can make it even shorter:
(def component->style {"entity-list" #{"services-list"
"employee-list"
"clients-list"}})
(defn get-style-name [comp-name]
(some (fn [[k v]] (when (v comp-name) k))
component->style))
Just to add another alternative, nested sequence operations usually lend themselves to replacement with for:
(defn get-style-name
[comp-name]
(first
(for [[style-name comp-names] style->components
comp-name' comp-names
:when (= comp-name comp-name')]
style-name)))
Still, I'd prefer a solution where the mapping of component name to style name is pre-computed, e.g.
(def get-style-name
(->> (for [[style-name comp-names] style->components
comp-name comp-names]
[comp-name style-name])
(into {})))
This way, you avoid traversing the style->components map on every lookup.
I have this Lisp code, and I'm trying to convert it into Clojure code.
(defun copy-tree (tr)
(if (atom tr)
tr
(cons (copy-tree (car tr))
(copy-tree (crd tr)))))
It seems like that Clojure doesn't have Lisp's atom (or atom in Clojure has very different meaning), I had to modify the code as follows. (Am I using atom? wrong or there is something else....?)
(defn single-valued?
[x]
(not (or (nil? x)
(.. x getClass isArray)
(some #(instance? % x) [clojure.lang.Counted
clojure.lang.IPersistentCollection
java.util.Collection
java.util.Map]))))
(defn copy-tree [tr]
(if (or (= tr ()) (single-valued? tr))
tr
(cons (copy-tree (first tr))
(copy-tree (rest tr)))))
The code works fine, but is there better way to replace Lisp's atom function?
I think you'll find this behaves apropriately:
(def single-valued? (complement coll?))
the difference is that it will bottom out sooner for nil -- (rest nil) is () which finally does not recur, but ((complement coll?) nil) returns true, so stops the recursion one step sooner.
I'm working through a book on clojure and ran into a stumbling block with "->>". The author provides an example of a comp that converts camelCased keywords into a clojure map with a more idiomatic camel-cased approach. Here's the code using comp:
(require '[clojure.string :as str])
(def camel->keyword (comp keyword
str/join
(partial interpose \-)
(partial map str/lower-case)
#(str/split % #"(?<=[a-z])(?=[A-Z])")))
This makes a lot of sense, but I don't really like using partial all over the place to handle a variable number of arguments. Instead, an alternative is provided here:
(defn camel->keyword
[s]
(->> (str/split s #"(?<=[a-z])(?=[A-Z])")
(map str/lower-case)
(interpose \-)
str/join
keyword))
This syntax is much more readable, and mimics the way I would think about solving a problem (front to back, instead of back to front). Extending the comp to complete the aforementioned goal...
(def camel-pairs->map (comp (partial apply hash-map)
(partial map-indexed (fn [i x]
(if (odd? i)
x
(camel->keyword x))))))
What would be the equivalent using ->>? I'm not exactly sure how to thread map-indexed (or any iterative function) using ->>. This is wrong:
(defn camel-pairs->map
[s]
(->> (map-indexed (fn [i x]
(if (odd? i)
x
(camel-keyword x)))
(apply hash-map)))
Three problems: missing a parenthesis, missing the > in the name of camel->keyword, and not "seeding" your ->> macro with the initial expression s.
(defn camel-pairs->map [s]
(->> s
(map-indexed
(fn [i x]
(if (odd? i)
x
(camel->keyword x))))
(apply hash-map)))
Is this really more clear than say?
(defn camel-pairs->map [s]
(into {}
(for [[k v] (partition 2 s)]
[(camel->keyword k) v])))
(This is a question regarding style. I am aware this can be done with a bunch of conditionals, multimethods, etc.)
In the following function, null-vector is defined on each implementation. How can I set it once for the entire function? In general, is it possible to set a common binding to all implementations?
A closure won't work, since it null-vector needs an "argument", but I suppose I could partial it. However, that would still need computation of the size parameter. I'd like to avoid repeating code, obviously.
(defn path
"Returns a lazy sequence of vectors representing a monotonic path
walked over coll in n-dimensional space, where n is the cardinality
of coll's alphabet."
([coll]
(let [alphabet (set coll)
cardinality (count alphabet)
alpha-map (apply hash-map (interleave alphabet (range cardinality)))
null-vector (vec (repeat cardinality 0))]
(path coll null-vector alpha-map)))
([coll alpha-map]
(let [null-vector (vec (repeat (count (keys alpha-map)) 0))]
(path coll null-vector alpha-map)))
([coll origin alpha-map]
(let [null-vector (vec (repeat (count origin) 0))
unit-vector #(assoc null-vector (alpha-map %) 1)
sum-vectors #(vec (map + %1 %2))]
(reductions sum-vectors origin (map unit-vector coll)))))
I would create a "private" helper function:
(defn- null-copy-vector [coll]
(vec (repeat (count coll) 0)))
and then just call it in each branch of the function:
(defn path
"Returns a lazy sequence of vectors representing a monotonic path
walked over coll in n-dimensional space, where n is the cardinality
of coll's alphabet."
([coll]
(let [alphabet (set coll)
alpha-map (zipmap alphabet (iterate inc 0)) ;; note 1
null-vector (null-copy-vector alphabet)]
(path coll null-vector alpha-map null-vector)))
([coll alpha-map]
(let [null-vector (null-copy-vector alpha-map)] ;; note 2
(path coll null-vector alpha-map null-vector)))
([coll origin alpha-map]
(path coll origin alpha-map (null-copy-vector origin)))
([coll origin alpha-map null-vector]
(let [unit-vector #(assoc null-vector (alpha-map %) 1)
sum-vectors #(vec (map + %1 %2))]
(reductions sum-vectors origin (map unit-vector coll)))))
It may be this isn't satisfying to you because null-copy-vector isn't "inside" the overall function here, but I think this is pretty idiomatic. On a function that did not take multiple arities, I might use letfn to separate out an "internal" function but that won't work here.
Breaking things up like this also lets you a) reuse the basic building block functions elsewhere and b) lets you test in smaller chunks. You might want to skip the defn- and just use defn to make testing easier (although it is possible to test defn- with a bit more work).
I also broke out a new 4-arg form that takes the null-vector as the last arg, letting you pass it in directly if you know it so that you can avoid re-creating it from an already null vector. If you wanted to hide that 4-arg form, you could pull it into a separate defn- helper function.
Unrelated notes:
I modified your first branch to a simpler (imho) impl using zipmap and an infinite sequence.
Instead of (count (keys map)), just doing (count map) is sufficient (the count here is inside your helper function).