PurelyFunctional.tv Newsletter 322: Tip: Avoid flatten when possible

Issue 322 - April 15, 2019 · Archives · Subscribe

Clojure Tip 💡

avoid flatten when possible

I see a lot of people using flatten, but I wonder if they know that it's kind of dangerous. It's dangerous because it indiscriminately flattens all nested sequences. People usually call it just to flatten one level.

Here's an example scenario:

Remove every nth element from a sequence

(defn remove-nth [n coll]
  (->> coll
    (cons ::dummy)  ;; add a dummy element
    (partition-all n)   ;; break into groups n-long
    (map rest)           ;; drop the first of each group
    flatten))               ;; squash it into a single sequence

A few test cases show that it's working:

(remove-nth 2 [0 1 2 3 4]) ;=> (0 2 4)
(remove-nth 3 [0 1 2 3 4 5 6]) ;=> (0 1 3 4 6)
(remove-nth 5 (range 20)) ;=> (0 1 2 3 5 6 7 8 10 11 12 13 15 16 17 
18)

Awesome! Some weeks go by and you are using this useful function all over. Then there are bugs. You track it down to this:

(remove-nth 3 [[:a 1] [:b 2] [:c 3] [:d 4] [:e 5]])

What does that give you? I'll wait here while you try it at the repl.

(:a 1 :b 2 :d 4 :e 5)

The flatten flattens too much. It will flatten deeply nested sequences all the way. It may feel useful, but it's a blunt tool.

What should you use instead?

You should more precisely work with the structure you are trying to transform. It depends on what you have and what you want. But in this case, you really just want to flatten a single nesting. In that case, mapcat is your friend. In fact, we just did a map on the previous line, so we can change that into a mapcat.

(defn remove-nth [n coll]
  (->> coll
    (cons ::dummy)  ;; add a dummy element
    (partition-all n)   ;; break into groups n-long
    (mapcat rest)))   ;; drop the first of each group

Or you could add the mapcat at the end. (mapcat identity %) will flatten a single layer.

Do you have a tip you'd like to share? Let me know. Just hit reply.

Follow-up 🙃

a few notes after last-week's issue

A couple of people reminded me of the relatively new function bounded-count. If the collection does not have a constant-time count, it will return the count of a sequence or the number you give it, whichever is smaller.

;; vector has a count, so it is returned
(bounded-count 2 [1 2 3 4 5 6 7]) ;=> 7
;; (range) is an infinite sequence
(bounded-count 100 (range)) ;=> 100

Currently Recording 🎥

I am currently recording a course called Repl-Driven Development in Clojure. There are no new lessons ready this week, but there are a couple coming early next week. Sorry! Taxes got the better part of my time.

I've been getting lots of great advice from people watching it. Thanks! This is going to be a much better course because of it. If you watch it, please let me know how it can improve.

The course is available as of right now as part of an early access program (read serious discount). If you buy now, you'll receive updates to the course as new lessons come out. There is already 4 hours of video, and many more coming. If I had to guess, I'd say 6-8 hours total when I'm done. But I can't be sure. That's what the discount is for. The price is going up this week! Buy now. It will never be this cheap again.

Of course, members of PurelyFunctional.tv will get the course as part of their membership for no extra cost. Just another benefit of being a member.

Check out the course. The first lesson is free.

Clojure Media 🍿

I've always enjoyed Rich Hickey's talks. I've revamped my page collecting his talks and other media output. See it at Rich Hickey's Programmer Media. You can filter by media type and by topic. So you can go straight to those interviews about Datomic, for example.

Brain skill 😎

Create an artifact. Build something. Get your hands dirty. Share it with others. Even creating a blog post about something you've learned can be very helpful for cementing knowledge.

Clojure Puzzle 🤔

Last week's puzzle

The puzzle in Issue 321 was to parse binary numbers and return those that are divisible by 3. (I made a typo and said 5 in the header, but I meant 3. No real harm, since the logic is the same.)

You can see the submissions here.

I think Steve Miner's approach should get special mention because it is so succinct and direct.

(require '[clojure.string :as str])

(defn bdiv5 [bstr]
  (->> (when-not (str/blank? bstr) (str/split bstr #","))
       (map #(Long/parseLong % 2))
       (filter #(zero? (rem % 5)))
       (map #(Long/toBinaryString %))
       (str/join ",")))

Thanks to everyone who submitted!

This week's puzzle

coin sums

(This one is from Project Euler).

In England the currency is made up of pound, £, and pence, p, and there are eight coins in general circulation:

1p, 2p, 5p, 10p, 20p, 50p, £1 (100p) and £2 (200p).

It is possible to make £2 in the following way:

1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p

How many different ways can £2 be made using any number of coins? Can you make a lazy sequence of these ways?

As usual, please send me your implementations. I'll share them all in next week's issue. If you send me one, but you don't want me to share it publicly, please let me know.

Rock on!
Eric Normand