The Quest
Remove every substring in first-array for every string in second-array that matches.
What is the best way to do it in Clojure?
Example:
first-array: ["Adam" "Goolan" "silly"]
second-array: [ "a" "oo" "ll"]
result: ["Adm" "Gln" "siy"]
Note that the result should be the same if
second-array: [ "oo" "ll" "a"]
If it is by matched elements (i.e. first item in first matches first item in second and so on):
> (defn the-quest [xs ys]
(map #(clojure.string/replace (first %1) (second %1) "") (map vector xs ys)))
#'sandbox1427/the-quest
> (the-quest ["Adam" "Goolan" "silly"] ["a" "oo" "ll"])
("Adm" "Glan" "siy")
See #Lee's comment below:
> (map #(clojure.string/replace %1 %2 "") ["Adam" "Goolan" "silly"] ["a" "oo" "ll"])
("Adm" "Glan" "siy")
>
Note - the above courtesy of http://www.tryclj.com/
With any-to-any matching:
user=> (defn repl-all [x ys]
#_=> (reduce #(clojure.string/replace %1 %2 "") x ys))
user=> (defn the-quest [xs ys]
#_=> (map #(repl-all %1 ys) xs))
user=> (the-quest ["Adam" "Goolan" "silly"] ["a" "oo" "ll"])
("Adm" "Gln" "siy")
There's two slightly different formulations of the problem and your example doesn't quite indicate which you want:
Make one pass, removing any substring of the input strings which matches any of the second strings.
Make one pass for each string to remove in sequence. This can remove more characters than option 1, as each removal can create new substrings which otherwise wouldn't qualify.
If what you want is the second case, that seems to be taken care of. If it's the first case, I'd suggest something like
(import java.util.regex.Pattern)
(defn the-quest [strs to-remove]
(let[my-pattern (->> to-remove
(map #(Pattern/quote %))
(clojure.string/join "|")
re-pattern)]
(map #(clojure.string/replace % my-pattern "") strs)))
Here I just create a regular expression matching any of the to-remove strings and do one replace on instances of the regex. You have to bring in Pattern/quote if you want to be able to use regex control characters in the to-removes.
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.
So I have a vector of questions and want to increment and return a number based on the user's input. It's giving me trouble and I figure it's because of a lack of understanding of Clojure and it's ideals. Here is as close as I've gotten, but all I get returned is 0.
(defn print-questions [questions]
(let [number 0]
(doseq [question questions]
(println question)
(let [x (read-line)]
(if (= (.toLowerCase x) "y")
(inc number)
(println "No"))))
number))
Clojure does not use variables as you encounter in imperative languages so statements like (inc x) return a new value one higher than x, while leaving x alone rather than changing x in place.
As written this code means:
(defn print-questions [questions]
(let [number 0]
;; start with zero every time
;; don't carry any information forward between iterations of the loop
(doseq [question questions]
(println question)
(let [x (read-line)]
(if (= (.toLowerCase x) "y")
(inc number) ;; this DOES NOT change the value in number
(println "No"))))
number)) ;; when you are all done, return the original value of number
This is great for cases where many threads are working on the same data, though it does lead to a somewhat different way of looking at things.
One way to write something very similar would be to loop through the questions while passing the current value of number from each iteration to the next like so:
user=> (defn print-questions [questions]
#_=> (loop [number 0 remaining-questions questions]
#_=> (println remaining-questions)
#_=> (if (seq remaining-questions)
#_=> (let [x (read-line)]
#_=> (if (= x "y")
#_=> (do (println "yes")
#_=> (recur (inc number) (rest remaining-questions)))
#_=> (do (println "No")
#_=> (recur number (rest remaining-questions)))))
#_=> number)))
#'user/print-questions
user=> (print-questions ["who" "what" "when" "why"])
[who what when why]
y
yes
(what when why)
y
yes
(when why)
n
No
(why)
y
yes
()
3
which works, though it's a bit verbose. If instead we look at this as reducing a collection of questions into a number where each reduction stage adds either one of zero to the outcome it's a bit more compact:
user=> (defn print-questions [questions]
#_=> (reduce (fn [answer question]
#_=> (println question)
#_=> (if (= "y" (read-line))
#_=> (inc answer)
#_=> answer))
#_=> 0
#_=> questions))
#'user/print-questions
user=> (print-questions ["who" "what" "when" "why"])
who
y
what
n
when
y
why
y
3
reduce takes a function to do the actual work, a value to start with, and a list of inmputs. It then uses that function with the first value to create the new result, then uses the function with the second value to produce a new result and the third and so on until every value in the input has had a chance to affect the final result.
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 am trying to get a link for each photoset. It should look like this:
[:p (link-to (str "/album?photosetid="photosetid) photoset-name)
In the following code I get a map of all photoset ids and names:
(def ids (map #(str "/album?photosetid=" %1) photoset-id))
(def names (map #(str %1) photoset-name))
After that i try to create the links:
(loop [x (count ids)]
(when (> x 0)
[:p (link-to (nth ids x "") name) (nth names x "")]
(recur (- x 1))
)
)
The problem is that I don't get any output.
Thanks for any help!
(map #(vector :p (link-to (str "/album?photosetid=" %1) %2)) ids names)
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?