3

I implemented a naive solution for printing a Pascal's Triangle of N depth which I'll include below. My question is, in what ways could this be improved to make it more idiomatic? I feel like there are a number of things that seem overly verbose or awkward, for example, this if block feels unnatural: (if (zero? (+ a b)) 1 (+ a b)). Any feedback is appreciated, thank you!

(defn add-row [cnt acc]
  (let [prev (last acc)]
    (loop [n 0 row []]
      (if (= n cnt)
        row
        (let [a (nth prev (- n 1) 0)
              b (nth prev n 0)]
          (recur (inc n) (conj row (if (zero? (+ a b)) 1 (+ a b)))))))))


(defn pascals-triangle [n]
  (loop [cnt 1 acc []]
    (if (> cnt n)
      acc
      (recur (inc cnt) (conj acc (add-row cnt acc))))))
maxcountryman
  • 1,562
  • 1
  • 24
  • 51

3 Answers3

8
(defn pascal []
  (iterate (fn [row]
             (map +' `(0 ~@row) `(~@row 0)))
           [1]))

Or if you're going for maximum concision:

(defn pascal []
  (->> [1] (iterate #(map +' `(0 ~@%) `(~@% 0)))))

To expand on this: the higher-order-function perspective is to look at your original definition and realize something like: "I'm actually just computing a function f on an initial value, and then calling f again, and then f again...". That's a common pattern, and so there's a function defined to cover the boring details for you, letting you just specify f and the initial value. And because it returns a lazy sequence, you don't have to specify n now: you can defer that, and work with the full infinite sequence, with whatever terminating condition you want.

For example, perhaps I don't want the first n rows, I just want to find the first row whose sum is a perfect square. Then I can just (first (filter (comp perfect-square? sum) (pascal))), without having to worry about how large an n I'll need to choose up front (assuming the obvious definitions of perfect-square? and sum).

Thanks to fogus for an improvement: I need to use +' rather than just + so that this doesn't overflow when it gets past Long/MAX_VALUE.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 1
    your use of unquote-splicing might need expanding-on too - it's cool, but magic – Hendekagon Jun 28 '13 at 11:08
  • 2
    This will int overflow fairly quickly. You could add `1N` to the seed vector to avoid it, but it doesn't look as pretty. Although running `(take 500 (pascal))` shows some interesting sine-like patterns in the output as it scrolls by. – fogus Jun 28 '13 at 12:55
  • Good point, fogus. I always forget to do that in my mathematical-examples code. I prefer `+'` to `N` though, personally; I'm curious if you specifically prefer `N`, and if so why? – amalloy Jun 28 '13 at 18:24
  • Thanks for the explanation and beautiful implementation (also @Ankur's similar approach is great): I feel extremely stupid about my approach now... – maxcountryman Jun 28 '13 at 18:44
  • Also, as @fogus mentioned: `(pprint (take 500 (->> '(1) (iterate #(map +' \`(0 ~@%) \`(~@% 0))))))` is quite nice to look at. – maxcountryman Jun 28 '13 at 18:56
  • @amalloy: for the sake of completeness, it's great that +' rather than just + allows to dodge the overflow, but why is it so? – Cedric Martin Jun 30 '13 at 00:11
  • @amalloy My only reason for choosing `1N` was that it was the first thing that popped into my head. :-) – fogus Jul 03 '13 at 12:33
5
(defn next-row [row]
  (concat [1] (map +' row (drop 1 row)) [1]))

(defn pascals-triangle [n]
  (take n  (iterate next-row '(1))))
Mongus Pong
  • 11,337
  • 9
  • 44
  • 72
Ankur
  • 33,367
  • 2
  • 46
  • 72
1

Not as terse as the others, but here's mine:)

(defn A [] 
  (iterate 
    (comp (partial map (partial reduce +)) 
      (partial partition-all 2 1) (partial cons 0))
        [1]))
Hendekagon
  • 4,565
  • 2
  • 28
  • 43