Clojure: Полуплющение вложенной последовательности - PullRequest
44 голосов
/ 08 марта 2011

У меня есть список со встроенными списками векторов, который выглядит следующим образом:

(([1 2]) ([3 4] [5 6]) ([7 8]))

С которым, я знаю, не идеально работать.Я хотел бы сгладить это до ([1 2] [3 4] [5 6] [7 8]).

сглаживание не работает: это дает мне (1 2 3 4 5 6 7 8).

Как мне это сделать?Я полагаю, что мне нужно создать новый список, основанный на содержимом каждого элемента списка, а не элементов, и именно эту часть я не могу узнать, как это сделать из документов.

Ответы [ 5 ]

59 голосов
/ 08 марта 2011

Если вы хотите сгладить только один уровень, вы можете использовать concat

(apply concat '(([1 2]) ([3 4] [5 6]) ([7 8])))
=> ([1 2] [3 4] [5 6] [7 8])
27 голосов
/ 08 марта 2011

Чтобы превратить список списков в единый список, содержащий элементы каждого подсписка, вам нужно 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)
8 голосов
/ 08 марта 2011

Код для 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))))

поэтому мы возвращаем все последовательности, которые не содержат последовательностей.

4 голосов
/ 09 марта 2011

Также вам может пригодиться эта общая функция выравнивания 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
3 голосов
/ 09 февраля 2016

Вот функция, которая будет выравниваться до уровня последовательности, независимо от неравномерного вложения:

(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])
...