I'm a Clojure newbie and having trouble with its immutable state. I'm trying to write a function that takes a list with the frames of a bowling game. For example, a list would look like ["X" "12" "2/" "X" "45" "X" "13" "33" "X" "81"]. I want the function to output a list that takes care of the frames that just have integers, and adds the numbers together. So, if the list above was inputted, the following list would be outputted: ["X" "3" "2/" "X" "9" "X" "4" "6" "X" "9"]. This was my attempt, but immutable state in Clojure is making it hard for me to understand how to go about this:
(defn eval-frames
[flist]
(loop [frames '()]
(if (not (= flist '()))
frames
(eval-normal (rest flist)))
(if (not (or (spare? flist) (strike? flist) (= flist ())))
(conj frames (+ (get (first flist) 0) ((get (first flist) 1))))
(conj frames (first flist)))
)
)
This just ends up outputting the first frame in the list, instead of the entire list. Any suggestions would be extremely appreciated!
There's a fair amount wrong with the code you posted:
The whole (if (not (= flist '())) part does nothing since you never use the results it gives back. You're thinking too imperative here. conj returns the "modified" list, it doesn't alter the original!
You never recur in the loop, so the loop only ever runs once.
I'd just use map for this. It iterates over a list, and "transforms" each element based on a function. I highly recommend getting as used to map and reduce as you can, because you'll be using them constantly.
My plan was this:
If all the characters in the frame are digits, sum the frame, else, leave the frame alone.
To sum the frame, I'm using parseLong to turn each character into a number, then(apply + to sum the parsed numbers, and then str to turn it back into a String.
(map
(fn [frame]
; Almost reads like English!
(if (every? #(Character/isDigit %) frame)
(->> frame ; Take the frame...
(map #(Long/parseLong (str %))) ; parse each character in the frame...
(apply +) ; then sum the parsed numbers...
(str)) ; and turn them back into a string.
frame)) ; Else, do nothing and leave the frame alone
["X" "12" "2/" "X" "45" "X" "13" "33" "X" "81"])
=> ("X" "3" "2/" "X" "9" "X" "4" "6" "X" "9")
This would have been simplified a bit if you had stored the scores as numbers originally instead of converting them to Strings. That would prevent the need for Long/parseLong, and the final call to str to turn each summed frame back into a String.
(map #(if (every? (fn [c] (<= (int \0) (int c) (int \9))) %)
(str (apply + (map read-string (map str %))))
%)
["X" "12" "2/" "X" "45" "X" "13" "33" "X" "81"])
or without the read-string:
(map #(if (every? (fn [c] (<= (int \0) (int c) (int \9))) %)
(str (apply + (map (fn [c] (- (int c) (int \0))) %)))
%)
["X" "12" "2/" "X" "45" "X" "13" "33" "X" "81"])
=> ("X" "3" "2/" "X" "9" "X" "4" "6" "X" "9")
(defn eval-frames [frames]
(map #(if (re-find #"\d\d" %)
(reduce + (map (fn [i](Integer. (str i))) (seq %))) %) frames))
So evaluating with a given frame would give :
(eval-frames ["X" "12" "2/" "X" "45" "X" "13" "33" "X" "81"])
=> ("X" "3" "2/" "X" "9" "X" "4" "6" "X" "9")
Related
Hi l'm trying to do a function that returns the 3 most commom strings
(take 3 (sort-by val > (frequencies s))))
(freq ["hi" "hi" "hi" "ola" "hello" "hello" "string" "str" "ola" "hello" "hello" "str"])
l've got this so far but a noticed that if there are more than 1 string with the same frenquency it won't return. Is there a way to filter the values of the frequencies funcition by their highest (eventually the top 3 highest)?
Thanks in advance.
I would propose slightly different solution which involves inverting frequencies map with group-by value (which is the items' count):
(->> data
frequencies
(group-by val))
;;{3 [["hi" 3]],
;; 2 [["ola" 2] ["str" 2]],
;; 4 [["hello" 4]],
;; 1 [["string" 1]]}
so the only thing you need is to just sort and process it:
(->> data
frequencies
(group-by val)
(sort-by key >)
(take 3)
(mapv (fn [[k vs]] {:count k :items (mapv first vs)})))
;;[{:count 4, :items ["hello"]}
;; {:count 3, :items ["hi"]}
;; {:count 2, :items ["ola" "str"]}]
frequencies gives you a map, where the keys are the original values to
investigate and the values in that map are the frequency of those
values. For your result you are interested for all original values,
that have the most occurrences including those original values with the
same occurrences.
One way would be to "invert" the frequencies result, to get a map from
occurrences to all original values with that occurrence. Then you can
get the highest N keys and from this map and select them (by using
a "sorted map" for inverting the map, we get the sorting by keys without
further steps).
(defn invert-map
([source]
(invert-map source {}))
([source target]
(reduce (fn [m [k v]]
(update m v (fnil conj []) k))
target
source)))
(assert (=
{1 ["do" "re"]}
(invert-map {"do" 1 "re" 1})))
(defn freq
[n s]
(let [fs (invert-map (frequencies s) (sorted-map-by >))
top-keys (take n (keys fs))]
(select-keys fs top-keys)))
(assert (=
{4 ["hello"], 3 ["hi"], 2 ["ola" "str"]}
(freq 3 ["hi" "hi" "hi" "ola" "hello" "hello" "string" "str" "ola" "hello" "hello" "str"])))
(defn shuffle-letters
[word]
(let [letters (clojure.string/split word #"")
shuffled-letters (shuffle letters)]
(clojure.string/join "" shuffled-letters)))
But if you put in "test" you can get "test" back sometimes.
How to modify the code to be sure that output will never be equal to input.
I feel embarrassing, I can solve it easily in Python, but Clojure is so different to me...
Thank you.
P.S. I thing we can close the topic now... The loop is in fact all I needed...
You can use loop. When the shuffled letters are the same as the original, recur back up to the start of the loop:
(defn shuffle-letters [word]
(let [letters (clojure.string/split word #"")]
(loop [] ; Start a loop
(let [shuffled-letters (shuffle letters)]
(if (= shuffled-letters letters) ; Check if they're equal
(recur) ; If they're equal, loop and try again
(clojure.string/join "" shuffled-letters)))))) ; Else, return the joined letters
There's many ways this could be written, but this is I think as plain as it gets. You could also get rid of the loop and make shuffle-letters itself recursive. This would lead to unnecessary work though. You could also use let-fn to create a local recursive function, but at that point, loop would likely be cleaner.
Things to note though:
Obviously, if you try to shuffle something like "H" or "HH", it will get stuck and loop forever since no amount of shuffling will cause them to differ. You could do a check ahead of time, or add a parameter to loop that limits how many times it tries.
This will actually make your shuffle less random. If you disallow it from returning the original string, you're reducing the amount of possible outputs.
The call to split is unnecessary. You can just call vec on the string:
(defn shuffle-letters [word]
(let [letters (vec word)]
(loop []
(let [shuffled-letters (shuffle letters)]
(if (= shuffled-letters letters)
(recur)
(clojure.string/join "" shuffled-letters))))))
Here's another solution (using transducers):
(defn shuffle-strict [s]
(let [letters (seq s)
xform (comp (map clojure.string/join)
(filter (fn[v] (not= v s))))]
(when (> (count (into #{} letters)) 1)
(first (eduction xform (iterate shuffle letters))))))
(for [_ (range 20)]
(shuffle-strict "test"))
;; => ("etts" "etts" "stte" "etts" "sett" "tste" "tste" "sett" "ttse" "sett" "ttse" "tset" "stte" "ttes" "ttes" "stte" "stte" "etts" "estt" "stet")
(shuffle-strict "t")
;; => nil
(shuffle-strict "ttttt")
;; => nil
We basically create a lazy list of possible shuffles, and then we take the first of them to be different from the input. We also make sure that there are at least 2 different characters in the input, so as not to hang (we return nil here since you don't want to have the input string as a possible result).
If you want your function to return a sequence:
(defn my-shuffle [input]
(when (-> input set count (> 1))
(->> input
(iterate #(apply str (shuffle (seq %))))
(remove #(= input %)))))
(->> "abc" my-shuffle (take 5))
;; => ("acb" "cba" "bca" "acb" "cab")
(->> "bbb" my-shuffle (take 5))
;; => ()
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 have a function that is deduplicating with preference, I thought of implementing the solution in clojure using flambo function thus:
From the data set, using the group-by, to group duplicates (i.e based on a specified :key)
Given a :val as input, using a filter to check if the some of values for each row are equal to this :val
Use a map to untuple the duplicates to return single vectors (Not very sure if that is the right way though, I tried using a flat-map without any luck)
For a sample data-set
(def rdd
(f/parallelize sc [ ["Coke" "16" ""] ["Pepsi" "" "5"] ["Coke" "2" "3"] ["Coke" "" "36"] ["Pepsi" "" "34"] ["Pepsi" "25" "34"]]))
I tried this:
(defn dedup-rows
[rows input]
(let [{:keys [key-col col val]} input
result (-> rows
(f/group-by (f/fn [row]
(get row key-col)))
(f/values)
(f/map (f/fn [rows]
(if (= (count rows) 1)
rows
(filter (fn [row]
(let [col-val (get row col)
equal? (= col-val val)]
(if (not equal?)
true
false))) rows)))))]
result))
if I run this function thus:
(dedup-rows rdd {:key-col 0 :col 1 :val ""})
it produces
;=> [(["Pepsi" 25 34]), (["Coke" 16 ] ["Coke" 2 3])]]
I don't know what else to do to handle the result to produce a result of
;=> [["Pepsi" 25 34],["Coke" 16 ],["Coke" 2 3]]
I tried f/map f/untuple as the last form in the -> macro with no luck.
Any suggestions? I will really appreciate if there's another way to go about this.
Thanks.
PS: when grouped
;=> [[["Pepsi" "" 5], ["Pepsi" "" 34], ["Pepsi" 25 34]], [["Coke" 16 ""], ["Coke" 2 3], ["Coke" "" 36]]]
For each group, rows that have"" are considered duplicates and hence removed from the group.
Looking at the flambo readme, there is a flat-map function. This is slightly unfortunate naming because the Clojure equivalent is called mapcat. These functions take each map result - which must be a sequence - and concatenates them together. Another way to think about it is that it flattens the final sequence by one level.
I can't test this but I think you should replace your f/map with f/flat-map.
Going by #TheQuickBrownFox suggestion, I tried the following
(defn dedup-rows
[rows input]
(let [{:keys [key-col col val]} input
result (-> rows
(f/group-by (f/fn [row]
(get row key-col)))
(f/values)
(f/map (f/fn [rows]
(if (= (count rows) 1)
rows
(filter (fn [row]
(let [col-val (get row col)
equal? (= col-val val)]
(if (not equal?)
true
false))) rows)))
(f/flat-map (f/fn [row]
(mapcat vector row)))))]
result))
and seems to work
I'm learning clojure and I wrote this code to resolve the roman numerals kata:
(def romans (sorted-map-by >
1000 "M"
500 "D"
400 "CD"
100 "C"
90 "XC"
50 "L"
40 "XL"
10 "X"
9 "IX"
5 "V"
4 "IV"
1 "I"))
(defn roman-digit [arabic]
(first (filter (fn [[key value]]
(>= arabic key)) romans)))
(defn arabic-to-roman [arabic]
(def roman (roman-digit arabic))
(if (> arabic 0)
(apply str (val roman) (arabic-to-roman (- arabic (key roman))))
""))
I want to know how could I get this code more efficient/idiomatic/clean. I'm sure I can to learn a lot of new stuff.
Thanks.
Here's my stab at it.
(defn roman-digit [arabic]
(first
(filter #(>= arabic (first %))
[[1000 "M" ]
[500 "D" ]
[400 "CD"]
[100 "C" ]
[90 "XC"]
[50 "L" ]
[40 "XL"]
[10 "X" ]
[9 "IX"]
[5 "V" ]
[4 "IV"]
[1 "I" ]])))
(defn arabic-to-roman [arabic]
(when (> arabic 0)
(let [[arabic-diff roman] (roman-digit arabic)]
(apply str roman (arabic-to-roman (- arabic arabic-diff))))))
What's going on here?
When you have an ordered closed set of values that you eventually use as sequence anyway, using a vector of pairs in the right order needs much less ceremony.
Never do def inside a condition like this. Think of def as declaration and definition of a global variable (symbol). For local scopes (bindings) use let.
Prefer when over if especially if there is only one branch. Note that both str and apply do the right thing for nil (which is returned at the bottom of the recursion when arabic is 0).