Clojure - Joining Strings from separate lists - clojure

I have 3 lists ("Hello" "Hi" "Hey") ("How's it" "What's" "Hey") ("going?" "up?" "Hey!")
How can I join the lists to get Hello How's it going? Hi What's up? Hey Hey Hey!
I know I can use clojure.string/join " " to join the strings in a single list but I want to join the first elements in each list, the second elements in each list, the third elements in each list ...
Any help would be much appreciated. Thanks

You can use map. If you pass in multiple collections, it will take an element from each collection and pass it to the fn:
(def a ["Hello" "Hi" "Hey"])
(def b ["How's it" "What's" "Hey"])
(def c ["going?" "up?" "Hey!"])
(map (fn [& args] (clojure.string/join " " args)) a b c)
This will result in:
("Hello How's it going?" "Hi What's up?" "Hey Hey Hey!")
But I'm sure with a little more thought a more elegant solution can be thought of :-).

You can use map:
(map (fn [& args] (join " " args)) '("Hello" "Hi" "Hey") '("How's it" "What's" "Hey") '("going?" "up?" "Hey!"))

Just concatenate your lists into the whole one and them join it:
(def data '[("Hello" "Hi" "Hey")
("How's it" "What's" "Hey")
("going?" "up?" "Hey!")])
(clojure.string/join " " (apply concat data))
Hello Hi Hey How's it What's Hey going? up? Hey!

Related

Clojure csv to map like {:key1 #{a, b, c}, :key2 #{n, m, k}}

After loading :
(csv/read-csv "Fetch, get, bring \n Take, receive, accept")
I get :
(["Fetch" " get" " bring "] [" Take" " receive" " accept"])
Now, I want to turn it into a map with unique keys and sets as values like:
[:key1 #{Fetch, get, bring}, :key2 #{Take, receive, accept}]
My goal is to be able to look up a word, say get, and return "Fetch, bring"
My goal is to be able to look up a word, say get, and return "Fetch, bring"
Not completely sure about your goal, but that did not stop me from implementing a function that gets all siblings of a word. I don't think you need a map with random keys, or do you? Note that a set is implemented as a hash-map where the values are the same as the keys (e.g., #{:a :b} is a wrapping around {:a :a, :b :b}).
Now, first parse the data to a list of words-sets:
(def word-sets
(map (comp set #(map string/trim %))
(csv/read-csv "Fetch, get, bring \n Take, receive, accept")))
;; => (#{"bring" "Fetch" "get"} #{"accept" "Take" "receive"})
Then the function to get the siblings of a word:
(defn siblings [word]
(mapcat #(when (contains? % word) (disj % word))
word-sets))
Using the set operator contains? we check every word-set if it contains the word, if so we return that set with that word disjoined. Because of the when, a word-set that does not contain the word becomes nil and mapcat removes the nil entries and concats the rest to one flat list.
E.g.,
(siblings "get")
;; => ("bring" "fetch")
(siblings "Take")
;; => ("accept" "receive")
(siblings "non existing")
;; => '()
I was able to get something similar, does the trick for me.
inp being (["Fetch" " get" " bring "] [" Take" " receive" " accept"]) .
(def x (map #(into #{} %) inp))
-> [#{Fetch, get, bring} #{Take, receive, accept}]
(map #(hash-map (gensym ":key") %) x)
-> ({:key6393 #{" bring " " get" "Fetch"}} {:key6394 #{" Take" " receive" " accept"}})

How do I join string set into a single string with positions prepended?

Assume I have this
(def base ["one" "two" "three"])
I want to convert it to:
1. one
2. two
3. three
(aka 1. one \n2. two \n3. three)
with join, I am not sure I can append a counter before joining:
(clojure.string/join " \n" base)
=> "one \ntwo \nthree"
and with doseq or similar, plus an atom, I do get individual strings but then will have to concatenate later on, something like
(def base ["one" "two" "three"])
(def pos (atom 0))
(defn add-pos
[base]
(for [b base]
(do
(swap! pos inc)
(str #pos ". " b))))
(let [pos-base (add-pos base)]
(clojure.string/join " \n" pos-base))
=> "1. one \n2. two \n3. three"
While it works, I don't know if using an atom with a for statement is he best way to do this, it doesn't look very clojure-esque.
Is there a better way to do this please?
That's a job for keep-indexed:
user> (keep-indexed #(str (inc %1) ". " %2) ["one" "two" "three"])
("1. one" "2. two" "3. three")
user> (clojure.string/join "\n"
(keep-indexed
#(str (inc %1) ". " %2)
["one" "two" "three"]))
"1. one\n2. two\n3. three"
A minor alternative to schaueho's keep-indexed would be map-indexed (spotting a pattern?)
(def base ["one" "two" "three"])
(defn numbered-list [s]
(->> s
(map-indexed #(str (inc %1) ". " %2))
(interpose \newline)
(apply str)))
(numbered-list base) ; => "1. one\n2. two\n3. three"
Clearly a job for interleave.
(->> (interleave (rest (range)) (repeat ". ") base (repeat " \n"))
(apply str))
;-> "1. one \n2. two \n3. three \n"

Clojure say-hi with varargs

Input: "Michael" "Julia" "Joe" "Sam"
Output: Hi, Michael, Julia, Joe, and Sam. (pay attention to the commas and the word "and")
Input: nil
Output: Hi, world.
Here is my first attempt:
(defn say-hi [& name]
(print "Hi," name))
user> (say-hi "Michael")
Hi, (Michael)
nil
user> (say-hi "Michael" "Julia")
Hi, (Michael Julia)
nil
Question:
How to implement default: (no input, say "Hi World!")
How to get rid of the parents around names in output?
How to implement the commas separation and add the conjunction word "and"?
First off, Clojure supports multi-arity functions, so you could do something like this to achieve default behaviour:
(defn say-hi
([] (say-hi "World"))
([& names] ...))
Then, what you want is to take a seq and join all the strings it contains together, using ", " in between. The clojure.string namespaces contains lots of string manipulation functions, one of them being clojure.string/join:
(require '[clojure.string :as string])
(string/join ", " ["Michael", "Julia"])
;; => "Michael, Julia"
But the last element of the seq should be concatenated using " and " as a separator, so you'll end up with something like this:
(require '[clojure.string :as string])
(defn say-hi
([] (say-hi "World"))
([& names]
(if (next names)
(format "Hi, %s, and %s!"
(string/join ", " (butlast names))
(last names))
(format "Hi, %s!" (first names)))))
Note that you have to differentiate between the single- and multi-name cases and (next names) basically checks whether the seq contains more than one element. (You could achieve the same by adding another arity to the function.)
(say-hi)
;; => "Hi, World!"
(say-hi "Michael")
;; => "Hi, Michael!"
(say-hi "Michael" "Julia" "Joe" "Sam")
;; => "Hi, Michael, Julia, Joe, and Sam!"
You can use clojure.string/join:
(use '[clojure.string :only [join]])
(defn sentencify [& elems]
(->>
[(join ", " (butlast elems)) (last elems)]
(remove empty?)
(join " and ")))
(defn say-hi [& name]
(print "Hi," (if name
(sentencify name)
"World!")))
A concise solution:
(defn say-hi [& names]
(let [names (case (count names)
0 ["world"]
1 names
(concat (butlast names) (list (str "and " (last names)))))]
(->> names, (cons "Hi"), (interpose ", "), (apply str))))
(say-hi)
;"Hi, world"
(say-hi "Michael")
;"Hi, Michael"
(say-hi "Michael" "Julia" "Joe" "Sam")
;"Hi, Michael, Julia, Joe, and Sam"
For long lists of names, you would want to eschew count, last, and butlast, maybe by pouring names into a vector first.
To print (as the question does) rather than return the formatted string, append print to the final form:
(->> names, (cons "Hi"), (interpose ", "), (apply str), print)

Given a clojure vector, iteratively remove 1 element

I'm trying to build a set of functions to compare sentences to one another. So I wrote a function called split-to-sentences that takes an input like this:
"This is a sentence. And so is this. And this one too."
and returns:
["This is a sentence" "And so is this" "And this one too."]
What I am struggling with is how to iterate over this vector and get the items that aren't the current value. I tried nosing around with drop and remove but haven't quite figured it out.
I guess one thing I could do is use first and rest in the loop and conj the previous value to the output of rest.
(remove #{current-value} sentences-vector)
Just use filter:
(filter #(not= current-value %) sentences-vector)
I believe you may want something like this function:
(defn without-each [x]
(map (fn [i] (concat (subvec x 0 i) (subvec x (inc i))))
(range (count x))))
Use it like this:
>>> (def x ["foo" "bar" "baz"])
>>> (without-each x)
==> (("bar" "baz") ("foo" "baz") ("foo" "bar"))
The returned elements are lazily concatenated, which is why they are not vectors. This is desirable, since true vector concatenation (e.g. (into a b)) is O(n).
Because subvec uses sharing with the original sequence this should not use an excessive amount of memory.
The trick is to pass your sentences twice into the reduce function...
(def sentences ["abcd" "efg" "hijk" "lmnop" "qrs" "tuv" "wx" "y&z"])
(reduce
(fn [[prev [curr & foll]] _]
(let [aren't-current-value (concat prev foll)]
(println aren't-current-value) ;use it here
[(conj prev curr) foll]))
[[] sentences]
sentences)
...once to see the following ones, and once to iterate.
You might consider using subvec or pop because both operate very quickly on vectors.

How to print a list as a string without parentheses

I did:
user=> (println (for [line (range 1 5)] (str "line=" line)))
and got:
(line=1 line=2 line=3 line=4)
but I wanted only line=1 line=2 line=3 line=4 as a string. How do I do this?
You need 'apply'.
(apply println (for [line (range 1 5)] (str "line=" line)))
Alternatively,
(println (apply str (interpose " " (map #(str "line=" %) (range 1 5)))))
What about this one. doseq is about doing side-effect on sequences and printing is a side-effect.
(doseq [line (range 1 5)
:let [msg (str "line=" line " ")]]
(print msg))
Instead of apply, you could alternatively use reduce like so:
user> (reduce #(str %1 " line=" %2) "" (range 1 5))
=> " line=1 line=2 line=3 line=4"
The reduce function is a function that takes a function (let's call if f), a "starting value", and then a list of things that will be used as the second argument to f. It lazily calls f on the starting value and the first item in the list, then calls f on what this returns and the second item in the list, then calls f on what this returns and the third item in the list etc., until it has exhausted all the items in the list (or rather--since it's lazy, it will only go through the whole list if you "ask it to").
If you don't like starting space, you could wrap the whole thing in triml (you'd have to do (use 'clojure.string) first). Or you could do (reduce #(str %1 "line=" %2 " ") (range 1 5)), which would put the space at the end.
My experience has been that anytime you can do something with apply, you can do it slightly more elegantly with reduce. More importantly, my reduce alternative has always usually been faster than my apply one. I certainly can't vouch that this will be true always, and I haven't done speed tests for your particular problem.
Edit
I did some rough timings, using my version (reduce) versus JohnJ's second suggestion (apply), and found that they were pretty similar for up to (range 1 100), but that by (range 1 500), the apply version was at least 4x faster.