In Clojure, second argument in map-indexed - clojure

SPOILER ALERT: This is about answers to 4Clojure question 157, indexing sequences.
The fast version of this question: in (map-indexed #(vector %2 %1) [:a :b :c]), what is %2?
Indexing Sequences #157
Transform a sequence into a sequence of pairs containing the original elements along with their index.
(= (__ [:a :b :c]) [[:a 0] [:b 1] [:c 2]])
(= (__ [0 1 3]) '((0 0) (1 1) (3 2)))
(= (__ [[:foo] {:bar :baz}]) [[[:foo] 0] [{:bar :baz} 1]])
I pretty quickly managed an answer: #(map reverse (map-indexed vector %)), but saw seemingly better answers such as:
map-indexed #(vector %2 %1)
by user tomdmitriev.
The question: where does the second argument come from?
So, for (map-indexed #(vector %2 %1) [:a :b :c]), what is %2?
The docs for map-indexed state:
Returns a lazy sequence consisting of the result of applying f to 0
and the first item of coll, followed by applying f to 1 and the second
item in coll, etc, until coll is exhausted. Thus function f should
accept 2 arguments, index and item. Returns a stateful transducer when
no collection is provided. -- my emphasis
Well, I also only provided one argument, the collection from the 4Clojure question. I'm not sure how this is working... is there some kind of an implied or implicit argument?
Thanks for any help clearing this up!

The function passed to map-indexed is a function of [index item]. Easy to understand with:
(map-indexed (fn [idx itm] [idx itm]) "item")
; ([0 "item"])
It is actually a apply_map_with_index
So back to your example, with:
(map-indexed #(vector %2 %1) [:a :b :c])
; ([:a 0] [:b 1] [:c 2])
Will create a small sequence of vector, made from %2 the item, %1 the index.
You can also compare the above with the easy to understand:
(map-indexed #(vector %1 %2) [:a :b :c])
; ([0 :a] [1 :b] [2 :c])
which creates a sequence of small vector of [index item].
EDIT:
If we decompose this in two steps, it gives:
; we need to define a function
; with two parameters
(defn my-function [index item]
(vector item index))
; map-indexed uses a function with
; two parameters
(map-indexed
my-function
[:a :b :c])

Related

Clojure loop with count

I am fairly new to Clojure and would help help with some code. I have a function which takes a vector and i would like to loop through the vector and get the value at an index 'i' and the value of 'i' itself. 'i' is the value which is incremented in the loop.
I have checked 'for' at the clojure docs at for and wrote the following code.
(for [i some-vector]
(print (get-intersec i (.length some-vector) loop-count)))
The loop-count variable is supposed to be the loop count.
I have also checked loop but it does not seem like a feasible solution. Can someone help me with a clojure function i can use or help me write a macro or function that can do that.
Thank you.
Ps: To solve my problem, i use my own counter but would like a better solution.
First, keep in mind that for is for list comprehension, that is, creating new sequences. For looping through a sequence for some side effect, like printing, you probably want to use doseq.
To include a numeric count with each element as you loop through, you can use map-indexed:
(def xs [:a :b :c :d])
(doseq [[n elem] (map-indexed #(vector %1 %2) xs)]
(println n "->" elem))
Output:
0 -> :a
1 -> :b
2 -> :c
3 -> :d
If you find yourself doing this a lot, like I did, you can create a macro:
(defmacro doseq-indexed [[[item idx] coll] & forms]
`(doseq [[~idx ~item] (map-indexed #(vector %1 %2) ~coll)]
~#forms))
And use it like this:
> (doseq-indexed [[n elem] xs] (println n "->" elem))
0 -> :a
1 -> :b
2 -> :c
3 -> :d
Don't forget dotimes for simple stuff like this:
(let [data [:a :b :c :d]]
(dotimes [i (count data)]
(println i " -> " (data i))
; or (nth data i)
; or (get data i)
))
with result
0 -> :a
1 -> :b
2 -> :c
3 -> :d
Using loop/recur would look like this:
(let [data [:a :b :c :d]]
(loop [i 0
items data]
(let [curr (first items)]
(when curr
(println i "->" curr)
(recur (inc i) (rest items))))))
Update:
If you need this a lot, I already wrote a function that will add the index value to the beginning of each entry in a sequence:
(ns tst.demo.core
(:use tupelo.test)
(:require [tupelo.core :as t]) )
(dotest
(let [data [:a :b :c :d]]
(t/spy-pretty :indexed-data
(t/indexed data))))
with result
:indexed-data =>
([0 :a]
[1 :b]
[2 :c]
[3 :d])
The general signature is:
(indexed & colls)
Given one or more collections, returns a sequence of indexed tuples
from the collections like:
(indexed xs ys zs) -> [ [0 x0 y0 z0]
[1 x1 y1 z1]
[2 x2 y2 z2]
... ]
If your not set on using for, you could use map-indexed e.g.
(map-indexed (fn [i v]
(get-intersect v (.length some-vector) i))
some-vector))
I don't know what get-intersect is and assume .length is java interop? Anyway, map-indexed expects a function of 2 arguments, the 1st is the index and the second is the value.

Difference between keep-indexed and map-indexed?

What is the difference between map-indexed and keep-indexed?
map-indexed is like map, except that the index of each element in the coll is passed as the first arg to the function that map-indexed takes, and the element is passed as the second arg to the function.
So
(map-indexed + [1 2 3 4]) ;=> ((+ 0 1) (+ 1 2) (+ 2 3) (+ 3 4)) => (1 3 5 7)
keep-indexed works the same way as map-indexed with the difference that if (f index value) returns nil, it is not included in the resulting seq.
So for example:
(keep-indexed #(and %1 %2) [1 2 3 nil 4]) ;;=> (1 2 3 4)
You can think of keep-indexed as map-indexed wrapped in filter as follows:
(filter (complement nil?) (map-indexed f coll))
Keep-indexed will keep the result of fn if result is not nil
(keep-indexed #(if (odd? %1) %2) [:a :b :c :d :e])
;;(:b :d)
map-indexed will keep all result of applying fn to coll regardless return value is nil or not
(map-indexed #(if (odd? %1) %2) [:a :b :c :d :e])
;; (nil :b nil :d nil)

Clojure zip function

I need to build a seq of seqs (vec of vecs) by combining first, second, etc elements of the given seqs.
After a quick searching and looking at the cheat sheet. I haven't found one and finished with writing my own:
(defn zip
"From the sequence of sequences return a another sequence of sequenses
where first result sequense consist of first elements of input sequences
second element consist of second elements of input sequenses etc.
Example:
[[:a 0 \\a] [:b 1 \\b] [:c 2 \\c]] => ([:a :b :c] [0 1 2] [\\a \\b \\c])"
[coll]
(let [num-elems (count (first coll))
inits (for [_ (range num-elems)] [])]
(reduce (fn [cols elems] (map-indexed
(fn [idx coll] (conj coll (elems idx))) cols))
inits coll)))
I'm interested if there is a standard method for this?
(apply map vector [[:a 0 \a] [:b 1 \b] [:c 2 \c]])
;; ([:a :b :c] [0 1 2] [\a \b \c])
You can use the variable arity of map to accomplish this.
From the map docstring:
... Returns a lazy sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the set
of second items in each coll, until any one of the colls is exhausted.
Any remaining items in other colls are ignored....
Kyle's solution is a great one and I see no reason why not to use it, but if you want to write such a function from scratch you could write something like the following:
(defn zip
([ret s]
(let [a (map first s)]
(if (every? nil? a)
ret
(recur (conj ret a) (map rest s)))))
([s]
(reverse (zip nil s))))

Clojure - map-indexed is it possible to start with an index other than 0?

Usually map-indexed function maps each list item to a respective index where the first index is 0, the second is 1 etc.
Is it possible to have the index start at another number and proceed from there?
Easiest way is to just remember that you can pass multiple sequences to map.
(map vector [:a :b :c] (iterate inc 100))
=> ([:a 100] [:b 101] [:c 102])
You simply wrap the index with another function in the receiving function
For example if we wanted to start at 1 instead of zero we would simply use inc
(map-indexed (fn [i v] (vector (inc i) v)) ["one" "two" "three"])
Will return
([1 "one"] [2 "two"] [3 "three"])
map-indexed does not allow this. However, it's easy to write your own version that lets you do it.
(defn map-indexed-from [n f coll]
(map f (range n Double/POSITIVE_INFINITY) coll))
Example usage:
user> (map-indexed-from 5 vector [:a :b :c])
([5 :a] [6 :b] [7 :c])

concatenating function that takes arbitrary inputs

I want to write a function that concatenates vectors or matrices, which can take arbitrary inputs. To combine two vectors I've written the follow code. It also also matrices to be combined such that columns are lengthened.
(defn concats
([x y] (vec (concat x y))))
Where I am stuck is extending the input to n vectors or matrices, and combining matrices to make longer rows.
Ex) (somefunction [[:a :b] [:c :d]] [[1 2] [3 4]] 2]
[[:a :b 1 2] [:c :d 3 4]]
The 2 in the input designates level to concatenate.
If you're not interested in "how it works", here's the solution right up front (note that level is zero-indexed, so what you've called the 1st level I'm calling the 0th level):
(defn into* [to & froms]
(reduce into to froms))
(defn deep-into*
[level & matrices]
(-> (partial partial mapv)
(iterate into*)
(nth level)
(apply matrices)))
The short answer for how it works is this: it iteratively builds up a function that will nest the call to into* at the correct level, and then applies it to the supplied matrices.
Regular old into, given a vector first argument, will concatenate the elements of the second argument onto the end of the vector. The into* function here is just the way I'm doing vector concatting on a variable number of vectors. It uses reduce to iteratively call into on some accumulated vector (which starts as to) and the successive vectors in the list froms. For example:
user> (into* [1 2] [3 4] [5 6])
> [1 2 3 4 5 6]
Now for deep-into*, I had to recognize a pattern. I started by hand-writing different expressions that would satisfy different "levels" of concatenation. For level 0, it's easy (I've extrapolated your example somewhat so that I can make it to level 2):
user> (into* [[[:a :b] [:c :d]]] [[[1 2] [3 4]]])
> [[[:a :b] [:c :d]] [[1 2] [3 4]]]
As for level 1, it's still pretty straightforward. I use mapv, which works just like map except that it returns a vector instead of a lazy sequence:
user> (mapv into* [[[:a :b] [:c :d]]] [[[1 2] [3 4]]])
> [[[:a :b] [:c :d] [1 2] [3 4]]]
Level 2 is a little more involved. This is where I start using partial. The partial function takes a function and a variable number of argument arguments (not a typo), and returns a new function that "assumes" the given arguments. If it helps, (partial f x) is the same as (fn [& args] (apply f x args)). It should be clearer from this example:
user> ((partial + 2) 5)
> 7
user> (map (partial + 2) [5 6 7]) ;why was six afraid of seven?
> (7 8 9)
So knowing that, and also knowing that I'll want to go one level deeper, it makes some sense that level 2 looks like this:
user> (mapv (partial mapv into*) [[[:a :b][:c :d]]] [[[1 2][3 4]]])
> [[[:a :b 1 2] [:c :d 3 4]]]
Here, it's mapping a function that's mapping into* down some collection. Which is kind of like saying: map the level 1 idea of (mapv into* ...) down the matrices. In order to generalize this to a function, you'd have to recognize the pattern here. I'm going to put them all next to each other:
(into* ...) ;level 0
(mapv into* ...) ;level 1
(mapv (partial mapv into*) ...) ;level 2
From here, I remembered that (partial f) is the same as f (think about it: you have a function and you're giving it no additional "assumed" arguments). And by extending that a little, (map f ...) is the same as ((partial map f) ...) So I'll re-write the above, slightly:
(into* ...) ;level 0
((partial mapv into*) ...) ;level 1
((partial mapv (partial mapv into*)) ...) ;level 2
Now an iterative pattern is becoming clearer. We're calling some function on ... (which is just our given matrices), and that function is an iterative build-up of calling (partial mapv ...) on into*, iterating for the number of levels. The (partial mapv ...) part can be functionalized as (partial partial mapv). This is a partial function that returns a partial function of mapving some supplied arguments. This outer partial isn't quite necessary because we know that the ... here will always be one thing. So we could just as easily write it as #(partial mapv %), but I so rarely get a chance to use (partial partial ...) and I think it looks pretty. As for the iteration, I use the pattern (nth (iterate f initial) n). Perhaps another example would make this pattern clear:
user> (nth (iterate inc 6) 5)
> 11
Without the (nth ...) part, it would loop forever, creating an infinite list of incrementing integers greater than or equal to 5. So now, the whole thing abstracted and calculated for level 2:
user> ((nth (iterate (partial partial mapv) into*) 2)
[[[:a :b][:c :d]]] [[[1 2][3 4]]])
> [[[:a :b 1 2] [:c :d 3 4]]]
Then, using the -> macro I can factor out some of these nested parantheses. This macro takes a list of expressions and recursively nests each into the second position of the successive one. It doesn't add any functionality, but can certainly make things more readable:
user> ((-> (partial partial mapv)
(iterate into*)
(nth 2))
[[[:a :b][:c :d]]] [[[1 2][3 4]]])
> [[[:a :b 1 2] [:c :d 3 4]]]
From here, generalizing to a function is pretty trivial--replace the 2 and the matrices with arguments. But because this takes a variable number of matrices, we will have to apply the iteratively-built function. The apply macro takes a function or macro, a variable number of arguments, and finally a collection. Essentially, it prepends the function or macro and the supplied arguments onto the final list, then evaluates the whole thing. For example:
user> (apply + [1 5 10]) ;same as (+ 1 5 10)
> 16
Happily, we can stick the needed apply at the end of the (-> ...). Here's my solution again, for the sake of symmetry:
(defn deep-into*
[level & matrices]
(-> (partial partial mapv)
(iterate into*)
(nth level)
(apply matrices)))
Using the concats function you listed in the question:
user=> (map concats [[:a :b] [:c :d]] [[1 2] [3 4]])
([:a :b 1 2] [:c :d 3 4])
this doesn't take into account the level as you listed, but it handles the input given
Taking arbitrary number of arguments needs a replacement concats function
(defn conc [a b & args]
(if (nil? (first args))
(concat a b)
(recur (concat a b) (first args) (rest args))))
Here are two examples
user=> (map conc [[:a :b] [:c :d]] [[1 2] [3 4]] [["w" "x"] ["y" "z"]])
((:a :b 1 2 "w" "x") (:c :d 3 4 "y" "z"))
user=> (map conc [[:a :b] [:c :d] [:e :f]] [[1 2] [3 4] [5 6]] [["u" "v"] ["w" "x"] ["y" "z"]])
((:a :b 1 2 "u" "v") (:c :d 3 4 "w" "x") (:e :f 5 6 "y" "z"))
Here are two different solutions for a function which will return a vector that's the concatenation of an arbitrary number of input collections:
(defn concats [& colls]
(reduce (fn [result coll]
(into result coll))
[]
colls))
(defn concats [& colls]
(vec (apply concat colls)))
The [& arg-name] notation in the argument lists is how you specify that the function is "variadic" - meaning it can accept a variable number of arguments. The result is that colls (or whatever name you pick) will be a sequence of all the arguments in excess of the positional arguments.
Functions can have multiple arities in Clojure, so you can also do things like this:
(defn concats
([x]
(vec x))
([x y]
(vec (concat x y)))
([x y & colls]
(vec (apply concat (list* x y colls)))))
However, only one of the overloads can be variadic, and its variadic part must come last (i.e. you can't do [& more n], only [n & more].
The Clojure.org page on special forms has more useful information on argument lists in Clojure (in the section on fn).
The function below correctly handles the example input/output you provided. Unfortunately I don't think I understand how you want the levels (and associated numeric input) to work well enough to generalize it as far as you're looking for.
(defn concats [x y]
;; only works with two inputs
(vec (map-indexed (fn [i v] (into v (nth y i)))
x)))
(concats [[:a :b] [:c :d]] [[1 2] [3 4]]) ;=> [[:a :b 1 2] [:c :d 3 4]]
But maybe it will give you some ideas anyway, or if you can add more information (especially examples of how different levels should work) I'll see if I can be more help.