what is the clojure way to do things - clojure

As part of a larger program, I'm testing a function that will turn a string of days on which a class occurs (such as "MWF") into a list of seven numbers: (1 0 1 0 1 0 0).
I first translate"TH" (Thursday) to "R" and "SU" (Sunday) to "N" to make things a bit easier.
I came up with the following code:
(defn days-number-maker
"Recursively compare first item in days of week with
first item in string of days. If matching, add a 1,
else add a zero to the result"
[all-days day-string result]
(if (empty? all-days) (reverse result)
(if (= (first all-days) (first day-string))
(recur (rest all-days)(rest day-string) (conj result 1))
(recur (rest all-days) day-string (conj result 0)))))
(defn days-to-numbers
"Change string like MTTH to (1 1 0 1 0 0 0)"
[day-string]
(let [days (clojure.string/replace
(clojure.string/replace day-string #"TH" "R") #"SU" "N")]
(days-number-maker "MTWRFSN" days (list))))
The good news: the code works. The bad news: I'm convinced I'm doing it wrong, in the moral purity sense of the word. Something inside of me says, "You could have just used (map...) to do this the right way," but I can't see how to do it with (map). So, my two questions are:
1) Is there such a thing as "the Clojure way," and if so,
2) How can I rewrite the code to be more Clojure-ish?

You can use map and sets
Using map and sets:
(defn days-number-maker
[all-days day-string]
(let [day-set (set day-string)]
(map (fn [day]
(if (day-set day)
1
0))
all-days)))
(defn days-to-numbers
"Change string like MTTH to (1 1 0 1 0 0 0)"
[day-string]
(let [days (clojure.string/replace
(clojure.string/replace day-string #"TH" "R") #"SU" "N")]
(days-number-maker "MTWRFSN" days)))

This is how I would do it a bit more succinctly:
(defn days-to-numbers
"Change string like MTTH to (1 1 0 1 0 0 0)"
[week-string]
(let [char-set (set (clojure.string/replace
(clojure.string/replace week-string "TH" "R") "SU" "N"))]
(map #(if (char-set %) 1 0)
"MTWRFSN")))
Tests:
=> (days-to-numbers "")
(0 0 0 0 0 0 0)
=> (days-to-numbers "MTWTHFSSU")
(1 1 1 1 1 1 1)
=> (days-to-numbers "MTHSU")
(1 0 0 1 0 0 1)
=> (days-to-numbers "FM")
(1 0 0 0 1 0 0)

Following on from #TheQuickBrownFox's answer ...
You don't need to recode "TH" and "SU": the second letters will
do.
Use false or nil instead of 0, so that you can apply logical tests directly.
Return the result as a vector, as you're quite likely to want to
index into it.
Giving ...
(defn days-to-numbers [ds]
(let [dns (->> ds
(partition-all 2 1)
(remove #{[\S \U] [\T \H]})
(map first)
set)]
(mapv dns "MTWHFSU")))
For example,
(days-to-numbers "MTTH")
;[\M \T nil \H nil nil nil]
Though the function is mis-named, as the elements are logical values, not numbers.
I'd prefer to return the set of day numbers:
(def day-index (into {} (map-indexed (fn [x y] [y x]) "MTWHFSU")))
;{\M 0, \T 1, \W 2, \H 3, \F 4, \S 5, \U 6}
(defn day-numbers [ds]
(->> ds
(partition-all 2 1)
(remove #{[\S \U] [\T \H]})
(map (comp day-index first))
set))
For example,
(day-numbers "MTTH")
;#{0 1 3}

Related

Clojure Fibonacci series using while in atom

How to achieve this using Clojure atom on the Fibonacci series.
(def x 0)
(def y 0)
(def z 1)
(defn problem []
(def a (atom 0))
(while (< #a 10 )
(do
;logic code need to write to achieve the output
; how can I add b,c == c,b???????.)
(swap! a inc)))
(problem)
output should be 0 1 1 2 3 5 8 13 21 34 55
As mentioned in the comments, defining and using an atom in a function is a totally non-clojure way of solving the question which makes it all the more difficult to solve. You should get familiar with the concept of immutability and higher order functions like map, filter, reduce, etc.
The code below gives you what you want.
(def fib1
(fn [n]
(cond
(= n 0) 1
(= n 1) 1
:else (+ (fib1 (dec n)) (fib1 (- n 2))))))
since this gives you just nth Fibonacci number, you should take + map with it if you want to get what you want.
(take 10 (map fib1 (range))) => (1 1 2 3 5 8 13 21 34 55)
take a look at this beautiful solution I found on internet.
(def fib-seq-iterate (map first (iterate (fn [[n m]] [m (+ n m)]) [0 1])))
This returns a lazy sequence, which can be called with
(take 5 fib-seq-iterate) => (0 1 1 2 3)
(def x 0)
(def y 0)
(def z 1)
(defn problem []
(def a (atom 0))
(while (< #a 10 )
(do
(println x)
(def x (+ y z))
(def y z)
(def z x)
(swap! a inc))))
(problem)
Output:
0 1 2 3 5 8 13 21 34 55

Find elements in list and also keep adjacent element

i have a list like '(1 2 3 1 4 1 1 6 8 9 0 1) (not actually of numbers, just as an example)
I want to keep all "1" and the element next to the "1".
So the result i would want is (1 2 1 4 1 1 6 1).
Coming from an imperative point of view i would iterate over the list with a for loop, find the "1" at a certain index i and then also keep the element at index i+1.
What would a functional, Clojure idiomatic way of solving this problem be?
Using reduce you can move along the original list building a new list as you go. The reducing function f is passed the new list up until now and the next element from the old list. If the list up until now ends with a 1, or the next element is a 1, add the element to the new list. Otherwise keep the new list as is and move along.
user> (def xs [1 2 3 1 4 1 1 6 8 9 0 1])
#'user/xs
user> (defn f [x y] (if (or (= 1 y) (= 1 (peek x))) (conj x y) x))
#'user/f
user> (reduce f [] xs)
[1 2 1 4 1 1 6 1]
When you can't think of anything clever with sequence combinators, write the recursion by hand. It's not exactly elegant, but it's lazy:
(defn keep-pairs [pred coll]
(lazy-seq
(if (empty? coll)
[]
(let [x (first coll)
xs (next coll)]
(if (pred x)
(cons x (when xs
(let [y (first xs)]
(concat (when-not (pred y) [y])
(keep-pairs pred xs)))))
(when xs
(keep-pairs pred xs)))))))
user> (keep-pairs #{1} [1 2 3 1 4 1 1 6 8 9 0 1])
(1 2 1 4 1 1 6 1)
user> (take 10 (keep-pairs #{1} (cycle [1 2 3])))
(1 2 1 2 1 2 1 2 1 2)
I think I'd prefer reduce for something like this, but here's another 'functional' way of looking at it:
You have a sequence of values that should produce a potentially smaller sequence of values based on some predicate (i.e. filtering) and that predicate needs look-ahead/-behind behavior.
A less common use for map is mapping over multiple sequences at once e.g. (map f coll1 coll2 coll3). If you pass in an "offset" version of the same collection it can be used for the look-ahead/-behind logic.
(defn my-pairs [coll]
(mapcat
(fn [prev curr]
(when (or (= 1 prev) (= 1 curr))
[curr]))
(cons ::none coll) ;; these values are used for look-behind
coll))
This is (ab)using mapcat behavior to combine the mapping/filtering into one step, but it could also be phrased with map + filter.
here's one more solution with clojure's seq processors composition:
(defn process [pred data]
(->> data
(partition-by pred)
(partition-all 2 1)
(filter (comp pred ffirst))
(mapcat #(concat (first %) (take 1 (second %))))))
user> (process #{1} [1 2 1 1 3 4 1 5 1])
;;=> (1 2 1 1 3 1 5 1)
user> (process #{1} [0 1 2 1 1 1 3 4 1 5 1 6])
;;=> (1 2 1 1 1 3 1 5 1 6)
Another idea that does not work since it misses a last one:
(def v [1 2 3 1 4 1 1 6 8 9 0 1])
(mapcat (fn [a b] (when (= a 1) [a b])) v (rest v))
;; => (1 2 1 4 1 1 1 6 1)
So use two arity version of mapcat over the vector and the vector shifted one to the right.
You could check that last 1 explicitly and add, then you get a less elegant working version:
(concat
(mapcat (fn [a b] (when (= a 1) [a b])) v (rest v))
(when (= (peek v) 1) [1]))
;; => (1 2 1 4 1 1 1 6 1)
When you need to loop over data and retain state, I think a plain-old loop/recur is the most straightforward technique:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn keep-pairs
[data]
(loop [result []
prev nil
remaining data]
(if (empty? remaining)
result
(let [curr (first remaining)
keep-curr (or (= 1 curr)
(= 1 prev))
result-next (if keep-curr
(conj result curr)
result)
prev-next curr
remaining-next (rest remaining)]
(recur result-next prev-next remaining-next)))))
(dotest
(let [data [1 2 3 1 4 1 1 6 8 9 0 1]]
(is= [1 2 1 4 1 1 6 1]
(keep-pairs data))))
(defn windowed-pred [n pred]
(let [window (atom [])]
(fn [rf]
(fn ([] (rf))
([acc] (rf acc))
([acc v]
(let [keep? (or (pred v) (some pred #window))]
(swap! window #(vec (take-last n (conj %1 %2))) v)
(if keep?
(rf acc v)
acc)))))))
(let [c [1 2 3 1 4 1 1 6 8 9 0 1]
pred #(= % 1)]
(eduction (windowed-pred 1 pred) c))
(defn last-or-first? [obj pair] (or (= obj (last pair)) (= obj (first pair))))
; to test, whether previous element or element is object
(defn back-shift [l] (cons nil (butlast l))) ;; back-shifts a list
(defn keep-with-follower
[obj l]
(map #'last ; take only the element itself without its previous element
(filter #(last-or-first? obj %) ; is element or previous element the object?
(map #'list (back-shift l) l)))) ; group previous element and element in list
(def l '(1 2 3 1 4 1 1 6 8 9 0 1))
(keep-with-follower 1 l)
;; => (1 2 1 4 1 1 6 1)
A functional solution using only cons first last butlast list map filter = and defn and def.

Single duplicate in a vector

Given a list of integers from 1 do 10 with size of 5, how do I check if there are only 2 same integers in the list?
For example
(check '(2 2 4 5 7))
yields yes, while
(check '(2 1 4 4 4))
or
(check '(1 2 3 4 5))
yields no
Here is a solution using frequencies to count occurrences and filter to count the number of values that occur only twice:
(defn only-one-pair? [coll]
(->> coll
frequencies ; map with counts of each value in coll
(filter #(= (second %) 2)) ; Keep values that have 2 occurrences
count ; number of unique values with only 2 occurrences
(= 1))) ; true if only one unique val in coll with 2 occurrences
Which gives:
user=> (only-one-pair? '(2 1 4 4 4))
false
user=> (only-one-pair? '(2 2 4 5 7))
true
user=> (only-one-pair? '(1 2 3 4 5))
false
Intermediate steps in the function to get a sense of how it works:
user=> (->> '(2 2 4 5 7) frequencies)
{2 2, 4 1, 5 1, 7 1}
user=> (->> '(2 2 4 5 7) frequencies (filter #(= (second %) 2)))
([2 2])
user=> (->> '(2 2 4 5 7) frequencies (filter #(= (second %) 2)) count)
1
Per a suggestion, the function could use a more descriptive name and it's also best practice to give predicate functions a ? at the end of it in Clojure. So maybe something like only-one-pair? is better than just check.
Christian Gonzalez's answer is elegant, and great if you are sure you are operating on a small input. However, it is eager: it forces the entire input list even when itcould in principle tell sooner that the result will be false. This is a problem if the list is very large, or if it is a lazy list whose elements are expensive to compute - try it on (list* 1 1 1 (range 1e9))! I therefore present below an alternative that short-circuits as soon as it finds a second duplicate:
(defn exactly-one-duplicate? [coll]
(loop [seen #{}
xs (seq coll)
seen-dupe false]
(if-not xs
seen-dupe
(let [x (first xs)]
(if (contains? seen x)
(and (not seen-dupe)
(recur seen (next xs) true))
(recur (conj seen x) (next xs) seen-dupe))))))
Naturally it is rather more cumbersome than the carefree approach, but I couldn't see a way to get this short-circuiting behavior without doing everything by hand. I would love to see an improvement that achieves the same result by combining higher-level functions.
(letfn [(check [xs] (->> xs distinct count (= (dec (count xs)))))]
(clojure.test/are [input output]
(= (check input) output)
[1 2 3 4 5] false
[1 2 1 4 5] true
[1 2 1 2 1] false))
but I like a shorter (but limited to exactly 5 item lists):
(check [xs] (->> xs distinct count (= 4)))
In answer to Alan Malloy's plea, here is a somewhat combinatory solution:
(defn check [coll]
(let [accums (reductions conj #{} coll)]
(->> (map contains? accums coll)
(filter identity)
(= (list true)))))
This
creates a lazy sequence of the accumulating set;
tests it against each corresponding new element;
filters for the true cases - those where the element is already present;
tests whether there is exactly one of them.
It is lazy, but does duplicate the business of scanning the given collection. I tried it on Alan Malloy's example:
=> (check (list* 1 1 1 (range 1e9)))
false
This returns instantly. Extending the range makes no difference:
=> (check (list* 1 1 1 (range 1e20)))
false
... also returns instantly.
Edited to accept Alan Malloy's suggested simplification, which I have had to modify to avoid what appears to be a bug in Clojure 1.10.0.
you can do something like this
(defn check [my-list]
(not (empty? (filter (fn[[k v]] (= v 2)) (frequencies my-list)))))
(check '(2 4 5 7))
(check '(2 2 4 5 7))
Similar to others using frequencies - just apply twice
(-> coll
frequencies
vals
frequencies
(get 2)
(= 1))
Positive case:
(def coll '(2 2 4 5 7))
frequencies=> {2 2, 4 1, 5 1, 7 1}
vals=> (2 1 1 1)
frequencies=> {2 1, 1 3}
(get (frequencies #) 2)=> 1
Negative case:
(def coll '(2 1 4 4 4))
frequencies=> {2 1, 1 1, 4 3}
vals=> (1 1 3)
frequencies=> {1 2, 3 1}
(get (frequencies #) 2)=> nil

Clojure flat sequence into tree

I have the following vector, [-1 1 2 -1 3 0 -1 2 -1 4 0 3 0 0]
which represents the tree [[1 2 [3] [2 [4] 3]]]
where -1 begins a new branch and 0 ends it. How can I convert the original vector into a usable tree-like clojure structure (nested vector, nested map)? I think clojure.zip/zipper might do it but I'm not sure how to build those function args.
Zippers are a good tool for this:
(require '[clojure.zip :as zip])
(def in [-1 1 2 -1 3 0 -1 2 -1 4 0 3 0 0])
(def out [[1 2 [3] [2 [4] 3]]])
(defn deepen [steps]
(->> steps
(reduce (fn [loc step]
(case step
-1 (-> loc
(zip/append-child [])
(zip/down)
(zip/rightmost))
0 (zip/up loc)
(zip/append-child loc step)))
(zip/vector-zip []))
(zip/root)))
(assert (= (deepen in) out))
Somehow this feels like cheating:
[(read-string
(clojure.string/join " "
(replace {-1 "[" 0 "]"}
[-1 1 2 -1 3 0 -1 2 -1 4 0 3 0 0])))]
This is not too hard with some recursion:
(defn numbers->tree [xs]
(letfn [(step [xs]
(loop [ret [], remainder xs]
(if (empty? remainder)
[ret remainder]
(let [x (first remainder)]
(case x
0 [ret (next remainder)]
-1 (let [[ret' remainder'] (step (next remainder))]
(recur (conj ret ret'), remainder'))
(recur (conj ret x) (next remainder)))))))]
(first (step xs))))
The idea is to have a function (step) that finds a sub-tree, and returns that tree as well as what numbers are left to be processed. It proceeds iteratively (via loop) for most inputs, and starts a recursive instance of itself when it runs into a -1. The only tricky part is making sure to use the remainder returned from these recursive invocations, rather than proceeding on with the list you were in the middle of.

Clojure macros and quotes

I'm trying to 'get' clojure macros and write a tweaked version of the are macro in terms of the existing are macro.
The tweak I want is to have signature [argv expr args] instead of [argv expr & args]
So I've tried
(defmacro are2 [argv expr args] `(clojure.test/are ~arg ~expr ~#args))
which kind of works, except it expects an unquoted list:
(are2 [input] (= 0 input) (1 2 3))
where I'd rather it expect a quoted list:
(are2 [input] (= 0 input) '(1 2 3))
But that results in:
Unable to resolve symbol: quote in this context.
If I try
(are2 [input] (= 0 input) (list 1 2 3))
instead then list itself gets processed as an test case.
What have I not understood / how can I get past the quote in my macro
'(1 2 3) is expanding into (quote (1 2 3)) which has an extra quote symbol and one too many levels of list which you can see with macroexpand-1:
user> (macroexpand-1 '(are2 [input] (= 0 input) '(1 2 3)))
(clojure.test/are [input] (= 0 input) quote (1 2 3))
you can drop the quote from the list by wrapping it int first and rest
user> (defmacro are2 [argv expr args]
`(clojure.test/are ~argv ~expr ~#(first (rest args))))
#'user/are2
user> (macroexpand-1 '(are2 [input] (= 0 input) '(1 2 3)))
(clojure.test/are [input] (= 0 input) 1 2 3)
which then runs as a test:
user> (are2 [input] (= 0 input) '(1 2 3)
FAIL in clojure.lang.PersistentList$EmptyList#1 (NO_SOURCE_FILE:1)
expected: (= 0 1)
actual: (not (= 0 1))
FAIL in clojure.lang.PersistentList$EmptyList#1 (NO_SOURCE_FILE:1)
expected: (= 0 2)
actual: (not (= 0 2))
FAIL in clojure.lang.PersistentList$EmptyList#1 (NO_SOURCE_FILE:1)
expected: (= 0 3)
actual: (not (= 0 3))
false