#clojure #clojurescript
#clojure #clojurescript
Вопрос:
Если у меня есть карта, которая выглядит как:
{:a 1 :b 2 :c 3}
как я могу преобразовать ее в вектор, подобный:
[:a 1 :b 2 :c 3]
Комментарии:
1.
[:c 3 :a 1 :b 2]
Также был бы допустимый результат (с ключами, посещенными в другом порядке)?2. Да, поскольку карты не дают никаких обещаний относительно порядка
MapEntry
элементов (т. Е. пар ключ-значение). Ключ в том, что(apply hash-map (t/keyvals m1))
всегда является идемпотентным.
Ответ №1:
Объединение into
с cat
преобразователем ting довольно лаконично:
(into [] cat {:a 1 :b 2 :c 3})
;;=> [:a 1 :b 2 :c 3]
Комментарии:
1. Почему
reverse
в ClojureScript? Если это действительно необходимо, это требует объяснения …2. @glts Это хороший момент! Это редактирование было предложено Marcellinus, но я не знаю, зачем это нужно. Я снова удалю его, пока это не будет объяснено.
3. Чтобы сохранить порядок возвращаемых данных, порядок в ClojureScript с использованием
(into [] ...)
работает немного иначе.4. @Marcellinus Карты представляют собой неупорядоченную структуру данных, поэтому порядок не должен иметь значения в любом случае.
5. Я думаю, лучше всего предположить, что порядок не важен, и это
[:b 2 :a 1 :c 3]
также является вполне допустимым результатом, даже если это не указано в вопросе, потому что, как указывает @glts, обычно карты не упорядочены в Clojure (хотя в Clojure также естьsorted-map
). И если вам нужен отсортированный вывод, я считаю, что его было бы более надежным в использовании,sort
чемreverse
для достижения этой цели, потому что он не делает никаких предположений о типе карты, которую мы выравниваем.
Ответ №2:
Вы можете использовать reduce-kv
:
(defn kv-vec [m] (reduce-kv conj [] m))
Ответ №3:
Используйте mapcat
и vec
для достижения этого:
(vec (mapcat identity {:a 1 :b 2 :c 3}))
;; => [:a 1 :b 2 :c 3]
Комментарии:
1. Лучше использовать
identity
вместо#'identity
.2. @gits это потому, что я пришел из common lisp. Но разве в clojure также не неправильно использовать
#'
перед именами функций, чтобы указать, что это функция?3. Я знаю, что это не обязательно. Но это не так — и я довольно часто видел, как это используется… Или я ошибаюсь в том, как я это вижу?
4.
#'identity
возвращает переменнуюclojure.core/identity
, а не содержащуюся в ней функцию. Var реализуетIFn
, предполагая, что содержащееся значение является функцией, и вызывает ее, поэтому ваш пример работает, но его чаще используютidentity
, поэтому var сначала разыменовывается перед передачейmapcat
.5. @Lee спасибо за объяснение. Тогда я адаптирую свой ответ.
Ответ №4:
Вот простая функция для этой цели
(ns demo.core
(:require
[schema.core :as s]
[tupelo.schema :as tsk]))
(s/defn keyvals :- [s/Any]
"For any map m, returns the (alternating) keys amp; values of m as a vector, suitable for reconstructing m via
(apply hash-map (keyvals m)). (keyvals {:a 1 :b 2} => [:a 1 :b 2] "
[m :- tsk/Map]
(reduce into [] (seq m)))
с помощью модульного теста:
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(dotest
(let [m1 {:a 1 :b 2 :c 3}
m2 {:a 1 :b 2 :c [3 4]}]
(is= [:a 1 :b 2 :c 3] (t/keyvals m1))
(is= m1 (apply hash-map (t/keyvals m1)))
(is= m2 (apply hash-map (t/keyvals m2)))))
Как показывает модульный тест, keyvals
является обратным к (apply hashmap ...)
и может использоваться для деконструкции карты. Это может быть полезно при вызове функций, требующих аргументов с ключевыми словами.
Ответ №5:
Я предлагаю использовать flatten
и vec
(vec (flatten (vec {:a 1 :b 2 :c 3})))
В repl это будет выглядеть так:
user=> (vec (flatten (vec {:a 1 :b 2 :c 3})))
[:a 1 :b 2 :c 3]
Комментарии:
1.
flatten
это никогда не правильный ответ. Попробуйте это на входной карте, подобной{:x [1 2], :y [3 4]}
, и вас ждет неприятный сюрприз.2. Я не согласен. Мы должны проверить, чего хочет OP в случае
{:x [1 2], :y [3 4]}
. Может быть, они хотят[:x 1 2 :y 3 4]
, и в этом случае это решение работает для них. Или, может быть, у них никогда не бывает вложенности коллекций.3. Алан задал этот вопрос с явной целью предоставить свой собственный ответ (основанный на недавнем закрытом вопросе с аналогичной предпосылкой, но плохо заданном). Он предоставил тот, который работает (по модулю его настойчивости в использовании его библиотеки tupelo), с идентификатором, который
(= m (apply hash-map (keyvals m)))
, и тестовым примером сбоя вашей функции. Но по большому счету неважно, чего хочет OP, важно, что они на самом деле спросили; Задача Stack Overflow — упростить поиск высококачественных ответов через поисковые системы, поэтому мы хотим, чтобы ответ был привлекательным для людей, которые попадают сюда через Google.4. Я считаю, что предоставленный мной ответ полезен для некоторых, и людям, которые приходят сюда из Google, может понадобиться этот ответ, а не другие. Вот общий пример использования: рассмотрим дерево с использованием карт, как в
{:a {:x :m} :b {:y :n} :c :z}
программе может потребоваться вектор всех узлов дерева по ряду причин. В этом случае результат[:a :x :m :b :y :n :c :z]
был бы правильным результатом.5. Комментарии Stack Overflow на самом деле не лучшее место для расширенного обсуждения. Но функция flatten в целом не очень хорошо работает: иногда она выглядит хорошо, когда вы думаете о простых данных (без вложенных коллекций), но разваливается, когда расширяется до вложенных коллекций. За 5 лет профессионального использования Clojure у меня никогда не было причин использовать flatten. Кроме того, я могу сказать, что вы не запускали приведенный вами пример, потому что на самом деле он не дает ожидаемого результата, о котором вы говорите. Примите это как еще одно доказательство того, что flatten не является хорошей функцией, которую можно рекомендовать новичкам.