Удаление соседних и равных элементов в коллекции
Скажи, что у меня есть функция:
(defn get-token [char]
(defn char->number? []
(re-matches #"\d" (str char)))
(defn whitespace? []
(or
(= \space char)
(= \newline char)))
(defn valid-ident-char? []
(re-matches #"[a-zA-Z_$]" (str char)))
(cond
(whitespace?)
nil
(= \' char)
:quote
(= \) char)
:rparen
(= \( char)
:lparen
(char->number?)
:integer
(valid-ident-char?)
:identifier
:else
(throw (Exception. "invalid character"))))
Когда я запускаю эту функцию, например, в строке "(test 1 2)"
Я получаю список символов для каждого символа:
'(:lparen :identifier :identifier :identifier nil :integer nil :integer :rparen)
Видя, что это не совсем то, что я хочу, я пытаюсь написать функцию, которая берет коллекцию и "уплотняет" коллекцию, чтобы объединить смежные элементы, которые равны.
Последний пример может сделать это:
(defn combine-adjacent [coll]
implementation...)
(->>
"(test 1 2)"
(map get-token)
(combine-adjacent)
(remove nil?))
; => (:lparen :identifier :integer :integer :rparen)
Каков идиоматический способ Clojure для достижения этой цели?
3 ответа
Не уверен, насколько это идиоматично, но один из способов сделать это - использовать partition-by
сгруппировать элементы входящей последовательности в списки, содержащие подпоследовательности того же элемента, а затем использовать map
чтобы получить первый элемент из каждого из этих списков.
Итак, в коде
(defn combine-adjacent [input]
(->> input (partition-by identity) (map first)))
или же
(defn combine-adjacent [input]
(->> (partition-by identity input) (map first))
должно сработать.
Clojure 1.7 представит новую функцию под названием dedupe
чтобы выполнить именно это:
(dedupe [0 1 1 2 2 3 1 2 3])
;= (0 1 2 3 1 2 3)
Если вы готовы использовать 1.7.0-alpha2, вы можете использовать его сегодня.
Реализация опирается на преобразователи (dedupe
выдает преобразователь при вызове без аргументов; Унарная перегрузка определяется просто как (sequence (dedupe) coll)
), поэтому было бы несложно сделать бэкпорт.
Есть несколько приемов для сравнения предметов с соседними:
во-первых, мы можем сравнить его с хвостом:
(defn combine-adjacent
[s]
(mapcat #(when (not= % %2) [%]) (rest s) s))
в качестве альтернативы, мы можем взять последовательность по два, и отбросить повторы
(defn combine-adjacent
[s]
(mapcat (fn [[a b]] (if (not= a b) [a]) ())
(partition 2 1[0 1 2 2 2 3 2 3])))
оба они используют в своих интересах полезное свойство concat
в сочетании с map
что вы можете вернуть ноль или более элементов для последовательности результатов для каждого входа. Пустой список для ложного случая во второй версии не нужен, но может помочь с ясностью.