jdeisen berg
jdeisen berg

Reputation: 13

what is the clojure way to do things

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

Answers (3)

TheQuickBrownFox
TheQuickBrownFox

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

Thumbnail
Thumbnail

Reputation: 13473

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}

Upvotes: 0

SamLosAngeles
SamLosAngeles

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

Related Questions