How to iterate over two lists using an index variable? - clojure

I am trying to deliver a list of sentences to a list of promises.
Sentences Defintion:
(def sentences (repeatedly promise))
(future
(doseq [sentence (map deref sentences)]
(println sentence)))
Deliver:
(doall (map deliver (nth sentences n) (parsedSentences)))
Example:
n = 1
parsedSentences = ["This is a sentence." "Is this a sentence?"]
I want to pass every entry of parsedSentences to a corresponding promise in sentences. Since I am very new to clojure I cant find a way to upcount n
I am looking for a way to do something like
deliver(nth sentences 1)("This is a sentence")
deliver(nth sentences 2)("Is this a sentence?")
...
deliver(nth sentences n)( sentence n)
So basically I am looking for a Clojure way to iterate over two lists using a index variable or something.

You have already half-written the answer yourself, except that you wrote (nth sentences n) (a thing which is clearly impossible, as you know n is not in scope) instead of just sentences.
The map function is most commonly called with exactly one sequence argument, but when called with more than one it acts as zipWith in some other languages, returning [(f x0 y0) (f x1 y1) (f x2 y2) ...]. You can even call map with zero sequence arguments, and then it's a transducer instead.
So, you would simply write
(map deliver sentences parsedSentences)

Map-indexed is probably what you're looking for. The function it takes has two params so something along the lines of (fn [idx, el] (dosomething idx el). I believe you can also map over multiple collections at once and each set of elements will be delivered as a tuple like (c1e1, c2e1,..., cNe1) (c for collection and e for element ordinality within that collection).
clojure.core/map-indexed
([f] [f coll])
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.

Related

Mapping two string lists (in a short way) in Lisp?

Lisp beginner here.
I have two string lists in this form with same length:
keys = ("abc" "def" "gh" ...)
values = ("qwe" "opr" "kmn" ...)
I need to construct hash-table or association lists (whichever is easy to construct and fast to get values from) from those lists. They are in the proper index due to their pair.
I know I can map them with iterating. But I want go with a more declarative way and I am looking for a clean way to this, if it can be done so.
There is a dedicated function named PAIRLIS that does exactly what what you want to build association lists:
USER> (pairlis '("abc" "def" "gh")
'("qwe" "opr" "kmn"))
(("gh" . "kmn") ("def" . "opr") ("abc" . "qwe"))
Note that the order is reversed, but this depends on the implementation. Here orders does not matter since your keys are unique.
Then, you can use the popular alexandria library to build a hash-table from that:
USER> (alexandria:alist-hash-table * :test #'equalp)
#<HASH-TABLE :TEST EQUALP :COUNT 3 {101C66ECA3}>
Here I am using a hash-table with test equalp because your keys are strings.
NB. The * symbol refers to the last primary value in a REPL
You could do something such as mapcar which will handle the iteration for you, vs. manually entering some sort of loop for iteration. For example:
(defvar *first-names* '("tom" "aaron" "drew"))
(defvar *last-names* '("brady" "rogers" "brees"))
(defvar *names-table* (make-hash-table))
We could create a list of the two sets of names and then a hashtable (or alist if you prefer). Then we can simply user mapcar to map through the list of us instead of manually entering a loop such as do, dolist, dotimes, loop ect…
(mapcar #'(lambda (first last)
(setf (gethash first *names-table*) last))
*first-names*
*last-names*)
mapping is particularly useful for lists in common lisp.
Note that as well as pairlis &c the normal mapping functions such as mapcar in fact take multiple list arguments and call the function being mapped on each of them. So a simple-minded version of (part of) what pairlis does might be:
(defun kv->alist (keys values)
(mapcar #'cons keys values))
(In fact this has an advantage over pairlis in some cases: the order of the result is determinate.)
And if you want to make a hashtable:
(defun kv->ht (keys values &key (test #'eql))
(let ((ht (make-hash-table :test test)))
(mapc (lambda (k v)
(setf (gethash k ht) v))
keys values)
ht))

building a hashmap from an array in clojure

First off, I am a student in week 5 of 12 at The Iron Yard studying Java backend engineering. The course is composed of roughly 60% Java, 25% JavaScript and 15% Clojure.
I have been given the following problem (outlined in the comment):
;; Given an ArrayList of words, return a HashMap> containing a keys for every
;; word's first letter. The value for the key will be an ArrayList of all
;; words in the list that start with that letter. An empty string has no first
;; letter so don't add a key for it.
(defn index-words [word-list]
(loop [word (first word-list)
index {}]
(if (contains? index (subs word 0 1))
(assoc index (subs word 0 1) (let [words (index (subs word 0 1))
word word]
(conj words word)))
(assoc index (subs word 0 1) (conj nil word)))
(if (empty? word-list)
index
(recur (rest word-list) index))))
I was able to get a similar problem working using zipmap but I am positive that I am missing something with this one. The code compiles but fails to run.
Specifically, I am failing to update my hashmap index in the false clause of the 'if'.
I have tested all of the components of this function in the REPL, and they work in isolation. but I am struggling to put them all together.
For your reference, here is the code that calls word-list.
(let [word-list ["aardvark" "apple" "zamboni" "phone"]]
(printf "index-words(%s) -> %s\n" word-list (index-words word-list)))
Rather than getting a working solution from the community, my hope is for a few pointers to get my brain moving in the right direction.
The function assoc does not modify index. You need to work with the new value that assoc returns. Same is true for conj: it does not modify the map you pass it.
I hope, this answer is of the nature you expected to get: just a pointer where your problem is.
BTW: If you can do with a PersistentList this becomes a one-liner when using reduce instead of loop and recur. An interesting function for you could be update-in.
Have fun with Clojure.
The group-by function does what you require.
You can use first as its discriminating function argument. It
returns the first character of a string, or nil if there isn't one:
(first word) is simpler than (subs word 0 1).
Use dissoc to remove the entry for key nil.
You seldom need to use explicit loops in clojure. Most common control patterns have been captured in functions like group-by. Such functions have function and possibly collection arguments. The commonest examples are map and reduce. The Clojure cheat sheet is a most useful guide to them.

Incrementing Values in Clojure Map

I've decided to take the plunge into Clojure. So far, I'm really enjoying thinking about things in immutable and functional ways. However, I've stumbled across a problem that I don't know how to solve elegantly.
I want to apply a map to a set of values, but I want the function being applied to increment for each new value in the collection
Eg.
(def data ["a" "b" "c"])
(map (fn [x] (str x " is item number " )) data)
I'm trying to map a function over my collection such that the result is:
("a is item number 1" "b is item number 2" "c is item number 3")
Any help our guidance would be appreciated.
What you need is a way to carry some state to your mapping function that contains the index of the current item.
One way to do this is to leverage the fact that map, when given more than one collection as input, acts as a zipper and calls your mapping function with one element from every list.
How does this help? If you make the second collection be an infinite sequence starting at zero, your function will be called with arguments "a" and 0, then "b" and 1 and so forth:
(map (fn [x i] (str x " is item number " (inc i))) data (range))
It turns out that this is a common pattern and Clojure provides a core function - map-indexed - for this purpose:
(map-indexed (fn [i x] (str x " is item number " (inc i))) data)
The only two differences in the second example: the order of arguments is reversed and you don't need to provide the second collection.

clojure reduce. no start value, empty collection

In a riddle I have to complete the following expressions in order to evaluate to true. There must be one single insertion, which fits to all of them.
(= 15 (reduce __ [1 2 3 4 5]))
(= 0 (reduce __ []))
(= 6 (reduce __ 1 [2 3]))
The third expression provides a start value. Hence my replacement cannot provide another one.
This function would pass the first and the third truth test:
#(+ %1 %2)
However, the second expression yields the following error:
clojure.lang.ArityException: Wrong number of args (0) passed to (...my function id)
It looks like usually reduce calls the given function only if the length of start value + collection is bigger than 2. If this length is 0, like in the case above, it is called as well - with 0 arguments.
Does anyone have a hint how to carry on here?
From the comments, the solution is clearly +, but maybe it's valuable to look at it in some detail to see why. As it turns out, there's a lot to it.
First, let's look at reduce to see what the requirements are:
(defn reduce
"f should be a function of 2 arguments. If val is not supplied,
returns the result of applying f to the first 2 items in coll, then
applying f to that result and the 3rd item, etc. If coll contains no
items, f must accept no arguments as well, and reduce returns the
result of calling f with no arguments. ..."
...
([f coll]
(if (instance? clojure.lang.IReduce coll)
(.reduce ... coll f)
...))
([f val coll]
(if (instance? clojure.lang.IReduceInit coll)
(.reduce ... coll f val)
...)))
This is a multi-arity function that either takes a function and a collection, or a function, initial value and a collection.
+ is also a multi-arity function that behaves depending on how many arguments you pass to it. The source below (edited for the parts we care about), shows reduce is satisfied by 0-arity and 2-arity.
(defn +
"Returns the sum of nums. (+) returns 0..."
...
([] 0)
...
([x y] (. clojure.lang.Numbers (add x y)))
...
Clearly (reduce + []) calls the first clause and returns 0. Test 2 is explained.
This works for the first test by applying the add function to each pair of Numbers, which happens in the Java internals for Clojure, in a tight for loop.
The final test works exactly like the first, except it calls the [v val coll] implementation of reduce. This applies a slightly different internal function, but with the same effect.
Notes
[1]: IFn is the Java interface for clojure functions

Clojure stack overflow using recur, lazy seq?

I've read other people's questions about having stack overflow problems in Clojure, and the problem tend to be a lazy sequence being built up somewhere. That appears to be the problem here, but for the life of me I can't figure out where.
Here is the code and after the code is a bit of explanation:
(defn pare-all []
"writes to disk, return new counts map"
(loop [counts (counted-origlabels)
songindex 0]
(let [[o g] (orig-gen-pair songindex)]
(if (< songindex *song-count*) ;if we are not done processing list
(if-not (seq o) ;if there are no original labels
(do
(write-newlabels songindex g);then use the generated ones
(recur counts (inc songindex)))
(let [{labels :labels new-counts :countmap} (pare-keywords o g counts)] ;else pare the pairs
(write-newlabels songindex labels)
(recur new-counts (inc songindex))))
counts))))
There is a map stored in "counts" originally retrieved from the function "counted-origlabels". The map have string keys and integer values. It is 600 or so items long and the values are updated during the iteration but the length stays the same, I've verified this.
The "orig-gen-pair" function reads from a file and returns a short pair of sequences, 10 or so items each.
The "write-newlabels" function just rite the passed sequence to the disk and doesn't have any other side effect nor does it return a value.
"Pare-keywords" returns a short sequence and an updated version of the "counts" map.
I just don't see what lazy sequence could be causing the problem here!
Any tips would be very much appreciated!
----EDIT----
Hello all, I've updated my function to be (hopefully) a little more idiomatic Clojure. But my original problem still remains. First, here is the new code:
(defn process-song [counts songindex]
(let [[o g] (orig-gen-pair songindex)]
(if-not (seq o) ;;if no original labels
(do
(write-newlabels songindex g);then use the generated ones
counts)
(let [{labels :labels new-counts :countmap} (pare-keywords o g counts)] ;else pare the pairs
(write-newlabels songindex labels)
new-counts))))
(defn pare-all []
(reduce process-song (counted-origlabels) (range *song-count*)))
This still ends with java.lang.StackOverflowError (repl-1:331). The stack trace doesn't mean much to me other than it sure seems to indicate lazy sequence mayhem going on. Any more tips? Do I need to post the code to the functions that process-song calls? Thanks!
I cannot quite grasp what you are trying to do without a little more concrete sample data, but it's very evident you're trying to iterate over your data using recursion. You're making things way more painful on yourself than you need to.
If you can generate a function, let's call it do-the-thing, that operates correctly with a single entry in your map, then you can call (map do-the-thing (counted-origlabels)), and it will apply (do-the-thing) to each map entry in (counted-origlabels), passing a single map entry to do-the-thing as it's sole argument and returning a seq of the return values from do-the-thing.
You also look like you need indexes, this is easily solved as well. You can splice in the lazy sequence (range) as the second argument to do-the-thing, and then you'll have a series of indexes generated with each map entry; however maps in clojure are not sorted by default, so unless you are using a sorted map, this index value is relatively meaningless.
Trying to abstract away what you've writen so far, try something like:
(defn do-the-thing [entry index counts]
(let [[o g] (orig-gen-pair index)]
(if-not (seq o)
(write-newlabels index g)
(let [{labels :labels new-counts :countmap} (pare-keywords o g counts)]
(write-newlabels index labels)))))
(map do-the-thing (counted-origlabels) (range) (constantly (counted-origlabels)))