Skip to content

Instantly share code, notes, and snippets.

@marksto
Last active January 23, 2024 14:13
Show Gist options
  • Save marksto/b862ad0bb5584246d12004b5febac205 to your computer and use it in GitHub Desktop.
Save marksto/b862ad0bb5584246d12004b5febac205 to your computer and use it in GitHub Desktop.
The One and Only Truely Functional FizzBuzz (in Clojure)
(ns fizz-buzz
"Approaching the infamous 'FizzBuzz' code problem with cyclic sequences and declarative rules")
;; SOLUTION
;; Core ideas:
;; 1. Note that the logic is essentially sequential and cyclical.
;; 2. Delay the actual computation (execution) by opting for lazy sequences.
;; 3. Separate control flow and logic, extracting the latter into a ruleset data structure.
;; NB: Here I prioritized the readability by newcomers, and thus tend to use less idiomatic code and don't require any libs.
(defn cyclic-seq-samples
"Transforms `rules` into samples of cyclic sequences.
[{:period 3, :literal \"Fizz\"} {:period 5, :literal \"Buzz\"}]
=>
[[nil nil \"Fizz\"] [nil nil nil nil \"Buzz\"]]"
[rules]
(reduce (fn [seq-samples {:keys [period literal] :as rule}]
(let [seq-sample (conj (vec (repeat (dec period) nil)) literal)]
(conj seq-samples seq-sample)))
[]
rules))
(defn compose-cyclic-seqs
"Composes `cyclic-seqs` into a single (infinite) lazy sequence."
[cyclic-seqs]
(apply map (fn [& seqs-one-step-slice]
(let [literals (remove nil? seqs-one-step-slice)]
(if (empty? literals) nil literals)))
cyclic-seqs))
(defn transform-seq-members
"For a `seq` of literals and `nil`s, substitutes `nil`s with indices.
This is the main output transformation logic of 'FizzBuzz' problem."
[seq]
(map-indexed (fn [idx member]
(if (some? member) (apply str member) (inc idx)))
seq))
(defn fizzBuzz
"For a given set of `rules`, produces a 'FizzBuzz' lazy sequence."
[rules]
(->> (cyclic-seq-samples rules)
(map cycle)
(compose-cyclic-seqs)
(transform-seq-members)))
;; CALL SITE / USING IT
(def rules [{:period 3
:literal "Fizz"}
{:period 5
:literal "Buzz"}])
;; so far nothing is computed yet
(def infinite-seq (fizzBuzz rules))
;; limit if necessary, but do not consume just yet
(def limit 100)
(def limited-seq (take limit infinite-seq))
;; finally, consume the lazy seq to achieve desired side effects
(doseq [member limited-seq] (println member))
@marksto
Copy link
Author

marksto commented Jan 23, 2024

Second of all, one may notice that things tend to change in the output periodically, with a period that is the result of multiplying all prime periods, i.e. 3 * 5 = 15 in our example case. Thus, we can substitute the main sequence with a cyclic one with a suitable period. And here comes the major concept of Functional (and, in fact, any other style of) programming — composition. So, in essence, the solution boils down to figuring out a proper way of composing cyclic sequences, i.e. (x, x, "Fizz") and (x, x, x, x, "Buzz") in our example case, where x is a placeholder, into a (x, x, "Fizz", x, "Buzz", "Fizz", x, x, "Fizz", "Buzz", x, "Fizz", x, x, ["Fizz" "Buzz"]) sequence. Let me re-write it in a bit more practical way — (x, x, ["Fizz"], x, ["Buzz"], ["Fizz"], x, x, ["Fizz"], ["Buzz"], x, ["Fizz"], x, x, ["Fizz" "Buzz"]) — so that final transformation is uniform for all non-x members — we'll just "join" any number of sting literals in them in the end.

@marksto
Copy link
Author

marksto commented Jan 23, 2024

Lastly, we still lack numbers on the x's places. And the nature of a cyclic sequence kinda prevents us from having them, right? No. It only prevents us from having them "in place", i.e. as a cyclic sequence members. Happily, we are able to decomplect (or decouple) these two things — the sequence members and, well, their indexes — by indexing the sequence that we derive from a cyclic one. This resulting sequence, depending on the implementation details of a specific library (in a specific programming language), can be indexed quite effectively, e.g. by storing only one number, an index of the previous seq member, in memory at a time.

This "specific" is crucial, so let's pick a truly Functional language to illustrate this whole approach, say Clojure/Script. I'll then use nil as the x placeholder. So, this is the final piece of the puzzle for the solution given in the above Gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment