逆FizzBuzz問題

オートマトンっぽい問題なのは分かるけど、それを実装するのは面倒くさいので必要と思われる長さのFizzBuzzシーケンスを切り出して全チェックしてます。
与えられた「Fizz Buzz リスト」 の長さを len とすると、解が含まれる範囲は先頭から高々「7 + (len - 1)」...って考えて良いような気がします。
この程度なら全チェックしても大きなコストにはならないでしょう。

;; 逆 FizzBuzz

;; --------------------------------------------------------------------
;;   :dependencies [[org.clojure/clojure "1.4.0"]
;;                  [org.clojure/core.incubator "0.1.0"]
;;                  [org.clojure/math.numeric-tower "0.0.1"]]
;; ---------------------------------------------------------------------

(ns inv-fizzbuzz.core
  (:use [clojure.core.incubator :only (-?>>)])
  (:use [clojure.math.numeric-tower :only (lcm)]))

(def word-num-pairs
  [["fizz" 3]
   ["buzz" 5]])

(defn lcmx
  [& xs]
  (reduce (fn [acc x] (lcm acc x)) xs))

(def fizzbuzz-seq
  (let [nils #(repeat % nil)
        cyc #(cycle (cons %1 (nils (dec %2))))]
    (->> (map (fn [[w n]] (cyc w n)) word-num-pairs)
         (apply map str)
         (drop 1))))
  
(def N (count (->> fizzbuzz-seq
                   (take (apply lcmx (map second word-num-pairs)))
                   (filter not-empty))))

(def INF Double/POSITIVE_INFINITY)

(defn fizzbuzz
  [len]
  (let [n (+ N (dec len))]
    (->> (map list fizzbuzz-seq (range 1 INF))
         (filter (comp not-empty first))
         (take n))))

(defn inv-fizzbuzz
  [& args]
  (let [len (count args)]
    (-?>> (fizzbuzz len)
          (partition len 1)
          (filter #(= args (map first %)))
          (map #(map second %))
          (group-by #(- (last %) (first %)))
          sort first second first)))
(inv-fizzbuzz "fizz")                             ;=> (3)
(inv-fizzbuzz "fizzbuzz")                         ;=> (15)
(inv-fizzbuzz "fizz" "buzz")                      ;=> (9 10)
(inv-fizzbuzz "buzz" "fizz" "fizz")               ;=> (5 6 9)
(inv-fizzbuzz "fizz" "fizz" "buzz")               ;=> (6 9 10)
(inv-fizzbuzz "buzz" "fizz" "fizz" "buzz")        ;=> (5 6 9 10)
(inv-fizzbuzz "fizz" "buzz" "fizz" "buzz")        ;=> nil
(inv-fizzbuzz "fizz" "buzz" "fizz" "fizz" "buzz") ;=> (3 5 6 9 10)
(inv-fizzbuzz "fizz" "fizzbuzz" "fizz" "buzz")    ;=> (12 15 18 20)

あってるかなぁ?

FizzBuzzシーケンスの周期「7」はハードコードせずに、Fizz,Buzzの周期、3と5から算出しています。変数 N がそれにあたります。
したがって、word-num-pairs に任意の単語・周期ペアを登録することで拡張可能にしてあるつもりです。
たとえば、こんな風にすると

(def word-num-pairs
  [["fizz" 3]
   ["buzz" 5]
   ["hoge" 4]
   ["piyo" 6]])

こうなります

(inv-fizzbuzz "hoge" "fizz" "buzz")         ;=> (8 9 10)
(inv-fizzbuzz "buzz" "fizz" "hoge")         ;=> (50 51 52)
(inv-fizzbuzz "buzz" "fizzhogepiyo" "fizz") ;=> (35 36 39)

(inv-fizzbuzz "hoge" "fizzbuzzpiyo" "hoge"
              "fizz" "buzz" "fizzhogepiyo") ;=> (28 30 32 33 35 36)

あってるのかなぁ?

別に...

-?>> 使う意味はなかったな。「解がなければそこで終了」のつもりで書いたけど、empty なコレクションは nil じゃないから機能しない。やるならこうですね。

(defn inv-fizzbuzz
  [& args]
  (let [len (count args)]
    (-?>> (fizzbuzz len)
          (partition len 1)
          (filter #(= args (map first %)))
          not-empty
          (map #(map second %))
          (group-by #(- (last %) (first %)))
          sort first second first)))

not-empty は empty なコレクションを nil に、そうでないコレクションはそのまま返します。

バグ修正

単語と周期のペアを mapコレクションにしてしまうと要素の並びが保証されないので問題がありました。
対策として、ペアを順に並べたベクタに変更しました。