Roman numerals kata in clojure - clojure

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).

Related

Clojure: I am trying to use 'some' instead of 'doseq' but I am not really sure how to use it

How to replace the "doseq" with "some" in this scenario. I am new to clojure.
(def handle (atom ()))
;; #'user/players
;; conjoin a keyword into that list
(swap! handlers conj [:report "handles"])
;;=> ([:report "handles"])
;; conjoin a second keyword into the list
(swap! handlers conj [:demo "handles2"])
;;=> ([:demo "handles2"] [:report "handle"])
(doseq [[a b] #handlers] (println a "--" b))
;;=> :demo -- handles2
;;=> :report -- handles
The Clojure docs for doseq and some are loaded with examples that can help you figure out what to use and how to use it.
There are several things I don't know about your situation, but maybe I can help with these examples.
some
Detects if something exists based on a condition. Returns the result of the predicate, if the predicate returns truthy.
Takes a predicate and a collection
Predicate examples:
#(= 2 %) ; Equals 2
(fn [val] (= val "user3438838")) ; equals your username
Collection examples:
[1 2 3 4 5 6 7 8]
["user3438838" "programs" "in" "Clojure"]
Let's evaluate the combinations of these:
(some #(= 2 %) [1 2 3 4 5 6 7 8]) ; => true
(some #(= 2 %) ["user3438838" "programs" "in" "Clojure"]) ; => nil
(some (fn [val] (= val "user3438838")) [1 2 3 4 5 6 7 8]) ; => nil
(some (fn [val] (= val "user3438838")) ["user3438838" "programs" "in" "Clojure"]) => true
doseq
Implement an expression for all elements of a sequence, for side effects. This is the first function I looked for when coming from JS, but it's usually not the right thing (it doesn't take advantage of lazily evaluating, decreasing performance). Generally want to apply a recursive expression, like loop with recur, but doseq may make sense here.
We'll take the same approach as with some
doseq takes (a) sequence(s) and and expression that ostensibly uses each element of the sequence.
Sequence examples:
[x ["user3438838" "programs" "in" "Clojure"]]
[x [1 2 3 4 5 6 7 8]]
; Note: Can use multiple [x (range 10) y (range 10 20)]
Body expression examples:
(println x)
(println (str "The number/word is: " x))
And now we'll combine these:
(doseq [x ["user3438838" "programs" "in" "Clojure"]] (println x)) ; Prints "user3438838\nprograms\nin\nClojure"
(doseq [x ["user3438838" "programs" "in" "Clojure"]] (println (str "The number/word is: " x))) ; Prints "The word is: user3438838 ..."
(doseq [x [1 2 3 4 5 6 7 8]] (println x)) ; Prints "1\n2\n3\n4\n5\n6\n7\n8
(doseq [x [1 2 3 4 5 6 7 8]] (println (str "The number/word is: " x))) ; Prints "The number/word is: 1 ..."
Hope this helps you understand the two.
And if you're new, I think the go-to book for learning Clojure is Daniel Higginbotham's (2015) Clojure for the Brave and True where he describes some (and not doseq b/c you generally want to use lazily/recursively evaluated expressions).

Clojure: Manipulating certain items in list

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")

Untuple a Clojure sequence

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

Using let inside ->> macro

I have started learning clojure. I am stuck at using let inside ->> macro
The code is :
(defn make-summary [wordStr]
;// split string into words
(let [words (clojure.string/split wordStr #"[\[\]\(\),.\s+]")
;// convert words to lowercase.
lowerCaseWords (map clojure.string/lower-case words)]
;// remove stop words
(->> (remove-stop-words lowerCaseWords)
;// count the frequency of words
;// ---------- HERE IS THE PROBLEM ------------------------------
(let [totalWords (count )] ;// <--- HOW TO MAKE MACRO PUT THE THING HERE ???
(count-frequency)
;// sort on the basis of frequency
(sort #(> (get %1 1) (get %2 1)))
;// find the keywords
)
)))
I am stuck at the second let inside the defn function.
How can i code it ?
You can use your original code with the as-> threading macro added in clojure 1.5
instead of inserting its argument to the first (->) or to the last (->>) position of each form, it lets you specify the position:
(as-> [1 2 3] x
(conj x 4)
(map inc x)) ;=> '(2 3 4 5)
I think, the advice is to use -> or ->> when you can and only fall back to as-> if this is not easily done. The summarise function by #Thumbnail is a nice example of the readability you get with ->>.
You can think of as-> as a convenient shorthand for the following code:
(let [x [1 2 3]
x (conj x 4)
x (map inc x)] x)
Here is the relevant part of your code written with as->:
(as-> (remove-stop-words lowerCaseWords) x
(let [totalWords (count x)] .....
Following #DiegoBasch's advice ...
If you're looking for
the words in decreasing order of frequency of use
eliminating stop words and
exploiting the ->> macro
then the following might suit:
(defn summarise [text stop-words]
(->> text
(re-seq #"[a-zA-Z]+")
(map clojure.string/lower-case)
(remove stop-words)
frequencies
(sort-by (comp - val))
(map key)))
For examples
(summarise "Mary had a HUGE a lamb" #{})
("a" "mary" "had" "huge" "lamb")
(summarise "Mary had a HUGE a lamb" #{"huge"})
("a" "mary" "had" "lamb")
Notes
The function detects words as sequences of letters instead of
detecting specific separator characters. You can reverse this change
if you prefer.
I'd be inclined to make sure that the stop words too are lower case:
use (set (map clojure.string/lower-case stop-words)) instead of
stop-words in the remove. Otherwise stop words with upper case
letters will be ineffective.
It is not possible to have ->> insert the argument at any place other than last item of the form. But this can be used in a trick to make it happen :
(defn make-summary [wordStr]
;// split string into words
(let [words (clojure.string/split wordStr #"[\[\]\(\),.\s+]")
;// convert words to lowercase.
lowerCaseWords (map clojure.string/lower-case words)]
;// remove stop words
(->> (remove-stop-words lowerCaseWords)
;// count the frequency of words
(fn [LCW] (let [totalWords (count LCW)] ;// <--- HOW TO MAKE MACRO PUT THE THING HERE ???
(count-frequency)
;// sort on the basis of frequency
(sort #(> (get %1 1) (get %2 1)))
;// find the keywords
)
))))
What i have done is wrapped the things inside the function, and now you can put the argument at any place.

Building a lazy, impure id generator

I'd like to know how to create an infinite, impure sequence of unique values in Clojure.
(def generator ...) ; def, not defn
(take 4 generator) ; => (1 2 3 4)
(take 4 generator) ; => (5 6 7 8). note the generator's impurity.
I think that such a design could be more convenient than e.g. wrapping a single integer value into a reference type and increment it from its consumers, as:
The proposed approach reduces the implementation details to a single point of change: the generator. Otherwise all the consumers would have to care about both the reference type (atom), and the concrete function that provides the next value (inc)
Sequences can take advantage many clojure.core functions. 'Manually' building a list of ids out of an atom would be a bit bulky: (take 4 (repeatedly #(swap! _ inc)))
I couldn't come up with a working implementation. Is it possible at all?
You can wrap a lazy sequence around an impure class (like a java.util.concurrent.atomic.AtomicLong) to create an id sequence:
(def id-counter (java.util.concurrent.atomic.AtomicLong.))
(defn id-gen []
(cons
(.getAndIncrement id-counter)
(lazy-seq
(id-gen))))
This works, but only if you don't save the head of the sequence. If you create a var that captures the head:
(def id-seq (id-gen))
Then call it repeatedly, it will return ids from the beginning of the sequence, because you've held onto the head of the sequence:
(take 3 id-seq)
;; => (0 1 2)
(take 3 id-seq)
;; => (0 1 2)
(take 3 id-seq)
;; => (0 1 2)
If you re-create the sequence though, you'll get fresh values because of the impurity:
(take 3 (id-gen))
;; (3 4 5)
(take 3 (id-gen))
;; (6 7 8)
(take 3 (id-gen))
;; (9 10 11)
I only recommend doing the following for educational purposes (not production code), but you can create your own instance of ISeq which implements the impurity more directly:
(def custom-seq
(reify clojure.lang.ISeq
(first [this] (.getAndIncrement id-counter))
(next [this] (.getAndIncrement id-counter))
(cons [this thing]
(cons thing this))
(more [this] (cons
(.getAndIncrement id-counter)
this))
(count [this] (throw (RuntimeException. "count: not supported")))
(empty [this] (throw (RuntimeException. "empty: not supported")))
(equiv [this obj] (throw (RuntimeException. "equiv: not supported")))
(seq [this] this)))
(take 3 custom-seq)
;; (12 13 14)
(take 3 custom-seq)
;; (15 16 17)
I had a fun time discovering something during answering your question. The first thing that occured to me was that perhaps, for whatever ultimate goal you need these IDs for, the gensym function might be helpful.
Then, I thought "well hey, that seems to increment some impure counter to generate new IDs" and "well hey, what's in the source code for that?" Which led me to this:
(. clojure.lang.RT (nextID))
Which seems to do what you need. Cool! If you want to use it the way you suggest, then I would probably make it a function:
(defn generate-id []
(. clojure.lang.RT (nextID)))
Then you can do:
user> (repeatedly 5 generate-id)
=> (372 373 374 375 376)
I haven't yet tested whether this will produce always unique values "globally"--I'm not sure about terminology, but I'm talking about when you might be using this generate-id function from within different threads, but want to still be sure that it's producing unique values.
this is another solution, maybe:
user=> (defn positive-numbers
([] (positive-numbers 1))
([n] (cons n (lazy-seq (positive-numbers (inc n))))))
#'user/positive-numbers
user=> (take 4 (positive-numbers))
(1 2 3 4)
user=> (take 4 (positive-numbers 5))
(5 6 7 8)
A way that would be more idiomatic, thread-safe, and invites no weirdness over head references would be to use a closure over one of clojures built in mutable references. Here is a quick sample I worked up since I was having the same issue. It simply closes over a ref.
(def id-generator (let [counter (ref 0)]
(fn [] (dosync (let [cur-val #counter]
(do (alter counter + 1)
cur-val))))))
Every time you call (id-generator) you will get the next number in the sequence.
Here's another quick way:
user> (defn make-generator [& [ii init]]
(let [a (atom (or ii 0 ))
f #(swap! a inc)]
#(repeatedly f)))
#'user/make-generator
user> (def g (make-generator))
#'user/g
user> (take 3 (g))
(1 2 3)
user> (take 3 (g))
(4 5 6)
user> (take 3 (g))
(7 8 9)
This is hack but it works and it is extremely simple
; there be dragons !
(defn id-gen [n] (repeatedly n (fn [] (hash #()))))
(id-gen 3) ; (2133991908 877609209 1060288067 442239263 274390974)
Basically clojure creates an 'anonymous' function but since clojure itselfs needs a name for that, it uses uniques impure ids to avoid collitions. If you hash a unique name then you should get a unique number.
Hope it helps
Creating identifiers from an arbitrary collection of seed identifiers:
(defonce ^:private counter (volatile! 0))
(defn- next-int []
(vswap! counter inc))
(defn- char-range
[a b]
(mapv char
(range (int a) (int b))))
(defn- unique-id-gen
"Generates a sequence of unique identifiers seeded with ids sequence"
[ids]
;; Laziness ftw:
(apply concat
(iterate (fn [xs]
(for [x xs
y ids]
(str x y)))
(map str ids))))
(def inf-ids-seq (unique-id-gen (concat (char-range \a \z)
(char-range \A \Z)
(char-range \0 \9)
[\_ \-])))
(defn- new-class
"Returns an unused new classname"
[]
(nth inf-ids-seq (next-int)))
(repeatedly 10 new-class)
Demonstration:
(take 16 (unique-id-gen [\a 8 \c]))
;; => ("a" "8" "c" "aa" "a8" "ac" "8a" "88" "8c" "ca" "c8" "cc" "aaa" "aa8" "aac" "a8a")