Reputation: 1813
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
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
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
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