madeinQuant
madeinQuant

Reputation: 1813

function flatten in Clojure

When translate a function flatten from Scheme to Clojure, I have encountered an error "Execution error (IllegalArgumentException)", Please feel free to comment.

Clojure (Encounter error)

(defn flatten [x] 
  (cond
    (empty? x) '()
    (not (coll? x)) (list x)
    :else (conj (flatten (first x)) (flatten (rest x)))))

(flatten '((1) 2 ((3 4) 5) ((())) (((6))) 7 8 ()))

Scheme

> (define (flatten x)
    (cond ((null? x) '())
          ((not (pair? x)) (list x))
          (else (append (flatten (car x))
                        (flatten (cdr x))))))
 
> (flatten '((1) 2 ((3 4) 5) ((())) (((6))) 7 8 ()))
(1 2 3 4 5 6 7 8)

Upvotes: 0

Views: 155

Answers (3)

Martin Půda
Martin Půda

Reputation: 7576

One more version, based on Alan Thompson's solution:

(defn flat [data]
  (reduce (fn [result e]
            (if (not (sequential? e))
              (conj result e)
              (into result (flat e))))
          [] data))

Tests:

(clojure.test/are [result arg]
  (= result (flat arg))
  [1 2 3] [1 2 3]
  [1 2 3] [1 [2 3]]
  [1 2 3] [1 [2 [3]]]
  [0 1 2 3 4 5 6 7] [[0] [1 2 3] [4 5 [6 7]]])
=> true

Upvotes: 2

Alan Thompson
Alan Thompson

Reputation: 29976

Here is an alternate version that may be a bit more straightforward:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(defn flatten
  [data]
  (loop [result []
         items  data]
    (if (empty? items)
      result
      (let [item       (first items)
            items-next (rest items)]
        (if (not (sequential? item))
          (recur
            (conj result item)
            items-next)
          (recur
            (into result (flatten item))
            items-next))))))

(dotest
  (is= (flatten [1 2 3]) [1 2 3])
  (is= (flatten [1 [2 3]]) [1 2 3])
  (is= (flatten [1 [2 [3]]]) [1 2 3])
  (is= (flatten [[0] [1 2 3] [4 5 [6 7]]]) [0 1 2 3 4 5 6 7])

  ; Cannot flatten a scalar value - we don't want (flatten 3) => [3]
  (throws? (flatten 3)))

Note that we avoid tricks like wrapping every scalar value in a list, so we don't get the non-intuitive result (flatten 3) => [3].


Build using my favorite template project.

Upvotes: 1

dwarfy
dwarfy

Reputation: 3076

Here is an implementation that works for me

(defn flatten [x]
  (cond
    (not (coll? x)) (list x)
    (empty? x) '()
    :else (concat (flatten (first x)) (flatten (rest x)))))

(comment
  (flatten '((1) 2 ((3 4) 5) ((())) (((6))) 7 8 ())))

  ;returns (1 2 3 4 5 6 7 8)

The only difference is that I put first the (not (coll? x)) and then the empty? (because (empty? 3) will throw an error)

The other change is the concat instead of conj. I don't think it's recommended to use concat because it can throw a Stack Overflow Error (see : https://stuartsierra.com/2015/04/26/clojure-donts-concat) but it works in this case ...

Upvotes: 3

Related Questions