Reputation: 13
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?
Upvotes: 1
Views: 150
Reputation: 10624
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)
Upvotes: 1
Reputation: 13473
Following on from @TheQuickBrownFox's answer ...
"TH"
and "SU"
: the second letters will
do.false
or nil
instead of 0
, so that you can apply logical tests directly. 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}
Upvotes: 0
Reputation: 2958
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)))
Upvotes: 3