Удаление соседних и равных элементов в коллекции

Скажи, что у меня есть функция:

(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 что вы можете вернуть ноль или более элементов для последовательности результатов для каждого входа. Пустой список для ложного случая во второй версии не нужен, но может помочь с ясностью.

Другие вопросы по тегам