What's the one-level sequence flattening function in Clojure?

10,252

Solution 1

My general first choice is apply concat. Also, don't overlook (for [subcoll coll, item subcoll] item) -- depending on the broader context, this may result in clearer code.

Solution 2

There's no standard function. apply concat is a good solution in many cases. Or you can equivalently use mapcat seq.

The problem with apply concat is that it fails when there is anything other than a collection/sequential is at the first level:

(apply concat [1 [2 3] [4 [5]]])
=> IllegalArgumentException Don't know how to create ISeq from: java.lang.Long...

Hence you may want to do something like:

(defn flatten-one-level [coll]  
  (mapcat  #(if (sequential? %) % [%]) coll))

(flatten-one-level [1 [2 3] [4 [5]]])
=> (1 2 3 4 [5])

As a more general point, the lack of a built-in function should not usually stop you from defining your own :-)

Solution 3

i use apply concat too - i don't think there's anything else in the core.

flatten is multiple levels (and is defined via a tree-walk, not in terms of repeated single level expansion)

see also Clojure: Semi-Flattening a nested Sequence which has a flatten-1 from clojure mvc (and which is much more complex than i expected).

update to clarify laziness:

user=> (take 3 (apply concat (for [i (range 1e6)] (do (print i) [i]))))
012345678910111213141516171819202122232425262728293031(0 1 2)

you can see that it evaluates the argument 32 times - this is chunking for efficiency, and is otherwise lazy (it doesn't evaluate the whole list). for a discussion of chunking see comments at end of http://isti.bitbucket.org/2012/04/01/pipes-clojure-choco-1.html

Share:
10,252
missingfaktor
Author by

missingfaktor

I am no longer active on this site, but if my posts help you and/or you need further help, do let me know on Twitter at @missingfaktor. I will try my best to respond! Note: This profile description was written for StackOverflow, and was copied to all other StackExchange sites as-is.

Updated on June 15, 2022

Comments

  • missingfaktor
    missingfaktor almost 2 years

    What's the one-level sequence flattening function in Clojure? I am using apply concat for now, but I wonder if there is a built-in function for that, either in standard library or clojure-contrib.

  • missingfaktor
    missingfaktor almost 12 years
    I gather apply should be used rarely? Is this usage justified? Isn't it slower than necessary?
  • andrew cooke
    andrew cooke almost 12 years
    the only reason i can think for saying it should be used rarely is that if a single alternative exists then it should be used. as for speed, i have no idea, but don't expect it to be particularly slow.
  • amalloy
    amalloy almost 12 years
    It's definitely not the case that apply should be used rarely. Use it all you want; sprinkle it liberally on your pasta, whatever. Sequences are lazy, including function-arg sequences, so (apply (fn [& args] (first args)) (range 1e5)) is still very fast, just passing the function a pointer to the start of the list. concat has a similar "shape" so it's just as cheap.
  • missingfaktor
    missingfaktor almost 12 years
    Thank you for your answer. I want the behavior that's given by apply concat.
  • missingfaktor
    missingfaktor almost 12 years
    Okay, I think mapcat identity was what I needed. Thank you for the answer!
  • Timothy Pratley
    Timothy Pratley over 8 years
    mapcat is implemented as apply concat github.com/clojure/clojure/blob/clojure-1.7.0/src/clj/clojur‌​e/…, which is lazy through the magic of handling args as a sequence.
  • Didier A.
    Didier A. over 5 years
    It's worth knowing that apply realizes a minimum of 4 elements, even without chunking. stackoverflow.com/q/51959298/172272