Scala Seq has the zipWithIndex method:
def zipWithIndex[A1 >: A, That](implicit bf: CanBuildFrom[Seq[A], (A1, Int), That]): That
Zips this sequence with its indices.
returns: A new sequence containing pairs consisting of all elements of this sequence paired with their index. Indices start at 0.
Example: List("a", "b", "c").zipWithIndex = List(("a", 0), ("b", 1), ("c", 2))
What is the equivalent function in Clojure?
Clojure's map-indexed will give you an indexed list of elements in a collection.
user=> (map-indexed vector "foo")
([0 \f] [1 \o] [2 \o])
As #jmargolisvt answered map-indexed is a good solution.
As your example got index inverted with collection items, you can always compose with reverse:
(map-indexed (comp reverse list) ["a", "b", "c"])
Or map over two sequences:
(map list ["a", "b", "c"] (range))
Related
I have a vector of vectors that contains some strings and ints:
(def data [
["a" "title" "b" 1]
["c" "title" "d" 1]
["e" "title" "f" 2]
["g" "title" "h" 1]
])
I'm trying to iterate through the vector and return(?) any rows that contain a certain string e.g. "a". I tried implementing things like this:
(defn get-row [data]
(for [d [data]
:when (= (get-in d[0]) "a")] d
))
I'm quite new to Clojure, but I believe this is saying: For every element (vector) in 'data', if that vector contains "a", return it?
I know get-in needs 2 parameters, that part is where I'm unsure of what to do.
I have looked at answers like this and this but I don't really understand how they work. From what I can gather they're converting the vector to a map and doing the operations on that instead?
(filter #(some #{"a"} %) data)
It's a bit strange seeing the set #{"a"} but it works as a predicate function for some. Adding more entries to the set would be like a logical OR for it, i.e.
(filter #(some #{"a" "c"} %) data)
=> (["a" "title" "b" 1] ["c" "title" "d" 1])
ok you have error in your code
(defn get-row [data]
(for [d [data]
:when (= (get-in d[0]) "a")] d
))
the error is here:
(for [d [data] ...
to traverse all the elements you shouldn't enclose data in brackets, because this syntax is for creating vectors. Here you are trying to traverse a vector of one element. that is how it look like for clojure:
(for [d [[["a" "title" "b" 1]
["c" "title" "d" 1]
["e" "title" "f" 2]
["g" "title" "h" 1]]] ...
so, correct variant is:
(defn get-row [data]
(for [d data
:when (= "a" (get-in d [0]))]
d))
then, you could use clojure' destructuring for that:
(defn get-row [data]
(for [[f & _ :as d] data
:when (= f "a")]
d))
but more clojuric way is to use higher order functions:
(defn get-row [data]
(filter #(= (first %) "a") data))
that is about your code. But corretc variant is in other guys' answers, because here you are checking just first item.
(defn get-row [data]
(for [d data ; <-- fix: [data] would result
; in one iteration with d bound to data
:when (= (get-in d[0]) "a")]
d))
Observe that your algorithm returns rows where the first column is "a". This can e. g. be solved using some with a set as predicate function to scan the entire row.
(defn get-row [data]
(for [row data
:when (some #{"a"} row)]
row))
Even better than the currently selected answer, this would work:
(filter #(= "a" (% 0)) data)
The reason for this is because for the top answer you are searching all the indexes of the sub-vectors for your query, whereas you might only wantto look in the first index of each sub-vector (in this case, search through each position 0 for "a", before returning the whole sub-vector if true)
I am using the following code to extract data by index of [1 2], is there any shorter solution?
(vec (map #(nth ["a" "b" "c"] % ) [1 2]))
mapv maps into a vector, and vectors when applied as functions do index lookup
(mapv ["a" "b" "c"] [1 2])
If you want ONLY the first and second index of a vector, there are many ways...
A simple sub vector can be used to persist the first index until the third index.
(subvec ["a" "b" "c"] 1 3)
You can map the vector and apply your vector to the first and second index to return the last two indices as a vector.
(mapv ["a" "b" "c"] [1 2])
Using the thread-last macro, you can take 3 indices and drop the first.
(->> ["a" "b" "c"] (take 3) (drop 1))
If you have a vector defined with n indices, and all you need is the last n indices, drop base 0 to return the last n.
(drop 1 ["a" "b" "c"])
Just started learning Clojure, so I imagine my main issue is I don't know how to formulate the problem correctly to find an existing solution. I have a map:
{[0 1 "a"] 2, [0 1 "b"] 1, [1 1 "a"] 1}
and I'd like to "transform" it to:
{[0 1] "a", [1 1] "a"}
i.e. use the two first elements of the composite key as they new key and the third element as the value for the key-value pair that had the highest value in the original map.
I can easily create a new map structure:
=> (into {} (for [[[x y z] v] {[0 1 "a"] 2, [0 1 "b"] 1, [1 1 "a"] 1}] [[x y] {z v}]))
{[0 1] {"b" 1}, [1 1] {"a" 1}}
but into accepts no predicates so last one wins. I also experimented with :let and merge-with but can't seem to correctly refer to the map, eliminate the unwanted pairs or replace values of the map while processing.
You can do this by threading together a series of sequence transformations.
(->> data
(group-by #(->> % key (take 2)))
vals
(map (comp first first (partial sort-by (comp - val))))
(map (juxt #(subvec % 0 2) #(% 2)))
(into {}))
;{[0 1] "a", [1 1] "a"}
... where
(def data {[0 1 "a"] 2, [0 1 "b"] 1, [1 1 "a"] 1})
You build up the solution line by line. I recommend you follow in the footsteps of the construction, starting with ...
(->> data
(group-by #(->> % key (take 2)))
;{(0 1) [[[0 1 "a"] 2] [[0 1 "b"] 1]], (1 1) [[[1 1 "a"] 1]]}
Stacking up layers of (lazy) sequences can run fairly slowly, but the transducers available in Clojure 1.7 will allow you to write faster code in this idiom, as seen in this excellent answer.
Into tends to be most useful when you just need to take a seq of values and with no additional transformation construct a result from it using only conj. Anything else where you are performing construction tends to be better suited by preprocessing such as sorting, or by a reduction which allows you to perform accumulator introspection such as you want here.
First of all we have to be able to compare two strings..
(defn greater? [^String a ^String b]
(> (.compareTo a b) 0))
Now we can write a transformation that compares the current value in the accumulator to the "next" value and keeps the maximum. -> used somewhat gratuitusly to make the update function more readable.
(defn transform [input]
(-> (fn [acc [[x y z] _]] ;; take the acc, [k, v], destructure k discard v
(let [key [x y]] ;; construct key into accumulator
(if-let [v (acc key)] ;; if the key is set
(if (greater? z v) ;; and z (the new val) is greater
(assoc acc key z) ;; then update
acc) ;; else do nothing
(assoc acc key z)))) ;; else update
(reduce {} input))) ;; do that over all [k, v]s from empty acc
user> (def m {[0 1 "a"] 2, [0 1 "b"] 1, [1 1 "a"] 1})
#'user/m
user> (->> m
keys
sort
reverse
(mapcat (fn [x]
(vector (-> x butlast vec)
(last x))))
(apply sorted-map))
;=> {[0 1] "a", [1 1] "a"}
I have data like the following:
[["i2" "i1"]
['("red" "blue" "green" "yellow") '("C" "D" "A" "B")]]
I am trying to get it into the following format:
[["i1" "i2"]
['("A" "B" "C" "D") '("blue" "green" "red" "yellow")]]
Basically, I am trying to:
Sort each list alphabetically
Sort the first inner vector alphabetically
Sort the rearrange the second inner vector to have the same, new order as the first inner vector
This is proving to be a difficult task for me. I tried constructing something like a three-level walk, but that did not work. I am trying to be idiomatic using the various map and walk functions, but maybe I just use "Do" and define a custom sorting map. It's pretty challenging.
Here's how I would approach a problem like this to come up with a solution.
(defn rearrange [[cols vals]]
First, I'd note that I'm trying to sort ["i2" "i1"] and rearrange the following list similarly. So I'll just tack the cols and value lists together:
(let [pairs (map list cols vals)]
Now pairs is a collection of data structures looking like: ("i2" ("red" "blue" "green" "yellow")). You want the function to return a vector.
(vector
Let's sort the pairs and grab the first item from each.
(mapv first (sort-by first pairs))
mapv is just the same as (vec (map ...)). sort-by sorts pairs by comparing (first item).
If we replace the first instance of first in the line above with second, then we get the corresponding lists of values in the correct order. But those lists won't themselves be sorted. So we add that:
(mapv (comp sort second) (sort-by first pairs)))))
End result:
(defn rearrange [[cols vals]]
(let [pairs (map list cols vals)]
(vector
(mapv first (sort-by first pairs))
(mapv (comp sort second) (sort-by first pairs)))))
It's not the nicest version of the function you can write, but half the battle is getting to this point. If you understand how this works, you can start improving it.
(def data [["i3" "i1" "i2"]
['("a" "c" "d" "b")
'("elephant" "zebra" "tiger" "cat")
'("red" "yellow" "blue" "green")]])
user=>(rearrange data)
[["i1" "i2" "i3"]
[("cat" "elephant" "tiger" "zebra")
("blue" "green" "red" "yellow")
("a" "b" "c" "d")]]
(def data [["i2" "i1"] ['("red" "blue" "green" "yellow") '("C" "D" "A" "B")]])
(defn transpose [m] (apply mapv vector m))
(defn arrange [[x y]] (->> [x (map sort y)] transpose (sort-by first) transpose))
(arrange data)
;=> [["i1" "i2"] [("A" "B" "C" "D") ("blue" "green" "red" "yellow")]]
The replace fn is what you are looking for:
(defn my-sort [[cols vals]]
(let [ vals-sorted (map sort vals)
cols-sorted (sort cols)
cols->sorted-vals (zipmap cols vals-sorted)]
[cols-sorted
(replace cols->sorted-vals cols-sorted)]))
Having a list of equally sized lists, e.g.:
(def d [["A" "B"] ["A" "C"] ["H" "M"]])
How can it be transformed into a list of sets, each set for the indexes above:
[#{"A" "H"} #{"B" "C" "M"}]
(map set (apply map vector d))
"(apply map vector)" is what is called "zip" in other languages like Python. It calls vector on the first item of each element of d, then the second item of each element, etc.
Then we call set on each of those collections.
if hash-set allowed duplicate keys, you could use:
(apply map hash-set d)
instead, you can do the uglier
(apply map (fn [& s] (set s)) d)
I'd suggest the following:
(reduce
(fn [sets vals]
(map conj sets vals))
(map hash-set (first d))
(rest d))