Ответ 1
Если вы хотите сгладить только один уровень, вы можете использовать concat
(apply concat '(([1 2]) ([3 4] [5 6]) ([7 8])))
=> ([1 2] [3 4] [5 6] [7 8])
У меня есть список со встроенными списками векторов, который выглядит так:
(([1 2]) ([3 4] [5 6]) ([7 8]))
Я знаю, что не идеален для работы. Я хотел бы сгладить это до ([1 2] [3 4] [5 6] [7 8])
.
flatten не работает: он дает мне (1 2 3 4 5 6 7 8)
.
Как мне это сделать? Я полагаю, что мне нужно создать новый список на основе содержимого каждого элемента списка, а не элементов, и эта часть не может найти, как это сделать из документов.
Если вы хотите сгладить только один уровень, вы можете использовать concat
(apply concat '(([1 2]) ([3 4] [5 6]) ([7 8])))
=> ([1 2] [3 4] [5 6] [7 8])
Чтобы преобразовать список списков в один список, содержащий элементы каждого под-списка, вы хотите apply concat
, как предлагает nickik.
Однако обычно лучшее решение: не начинайте с списка списков! Например, предположим, что у вас есть функция под названием get-names-for
, которая берет символ и возвращает список всех интересных вещей, которые вы могли бы назвать этим символом:
(get-names-for '+) => (plus add cross junction)
Если вы хотите получить все имена для некоторого списка символов, вы можете попробовать
(map get-names-for '[+ /])
=> ((plus add cross junction) (slash divide stroke))
Но это приводит к проблеме, с которой вы столкнулись. Вы можете склеить их вместе с apply concat
, но лучше использовать mapcat
вместо map
для начала:
(mapcat get-names-for '[+ /])
=> (plus add cross junction slash divide stroke)
Код для flatten
довольно короткий:
(defn flatten
[x]
(filter (complement sequential?)
(rest (tree-seq sequential? seq x))))
Он использует tree-seq
для перехода через структуру данных и возврата последовательности атомов. Поскольку нам нужны все нижестоящие последовательности, мы можем изменить их следующим образом:
(defn almost-flatten
[x]
(filter #(and (sequential? %) (not-any? sequential? %))
(rest (tree-seq #(and (sequential? %) (some sequential? %)) seq x))))
поэтому мы возвращаем все последовательности, не содержащие последовательностей.
Также вы можете найти полезную эту общую функцию выравнивания уровня 1, которую я нашел на clojuremvc:
(defn flatten-1
"Flattens only the first level of a given sequence, e.g. [[1 2][3]] becomes
[1 2 3], but [[1 [2]] [3]] becomes [1 [2] 3]."
[seq]
(if (or (not (seqable? seq)) (nil? seq))
seq ; if seq is nil or not a sequence, don't do anything
(loop [acc [] [elt & others] seq]
(if (nil? elt) acc
(recur
(if (seqable? elt)
(apply conj acc elt) ; if elt is a sequence, add each element of elt
(conj acc elt)) ; if elt is not a sequence, add elt itself
others)))))
Пример:
(flatten-1 (([1 2]) ([3 4] [5 6]) ([7 8])))
=>[[1 2] [3 4] [5 6] [7 8]]
concat
Разумеется, сделайте для вас работу, но этот flatten-1
также разрешает элементы не seq внутри коллекции:
(flatten-1 '(1 2 ([3 4] [5 6]) ([7 8])))
=>[1 2 [3 4] [5 6] [7 8]]
;whereas
(apply concat '(1 2 ([3 4] [5 6]) ([7 8])))
=> java.lang.IllegalArgumentException:
Don't know how to create ISeq from: java.lang.Integer
Здесь функция, которая сгладится до уровня последовательности, независимо от неравномерного вложенности:
(fn flt [s] (mapcat #(if (every? coll? %) (flt %) (list %)) s))
Итак, если ваша исходная последовательность была:
'(([1 2]) (([3 4]) ((([5 6])))) ([7 8]))
Вы все равно получите тот же результат:
([1 2] [3 4] [5 6] [7 8])