Как я могу преобразовать карту в вектор?

#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 не является хорошей функцией, которую можно рекомендовать новичкам.