Как перебрать ключи и значения карты в Clojure?
У меня есть следующая карта, которую я хочу повторить:
(def db {:classname "com.mysql.jdbc.Driver"
:subprotocol "mysql"
:subname "//100.100.100.100:3306/clo"
:username "usr" :password "pwd"})
Я пробовал следующее, но вместо того, чтобы печатать ключ и значение один раз, он многократно печатает ключ и значения как различные комбинации:
(doseq [k (keys db)
v (vals db)]
(println (str k " " v)))
Я придумал решение, но Брайан (см. ниже) гораздо логичнее.
(let [k (keys db) v (vals db)]
(do (println (apply str (interpose " " (interleave k v))))))
Ответы
Ответ 1
Ожидаемое поведение. (doseq [x ... y ...])
будет перебирать каждый элемент в y
для каждого элемента в x
.
Вместо этого вы должны перебирать один раз по карте. (seq some-map)
вернет список векторов из двух элементов, по одному для каждой пары ключ/значение на карте. (Действительно, они clojure.lang.MapEntry
, но ведут себя как векторы с двумя предметами.)
user> (seq {:foo 1 :bar 2})
([:foo 1] [:bar 2])
doseq
может перебирать этот seq так же, как любой другой. Как и большинство функций в Clojure, которые работают с коллекциями, doseq
внутренне вызывает seq
в вашей коллекции перед ее итерацией по ней. Поэтому вы можете просто сделать это:
user> (doseq [keyval db] (prn keyval))
[:subprotocol "mysql"]
[:username "usr"]
[:classname "com.mysql.jdbc.Driver"]
[:subname "//100.100.100.100:3306/clo"]
[:password "pwd"]
Вы можете использовать key
и val
, или first
и second
, или nth
, или get
, чтобы получить ключи и значения из этих векторов.
user> (doseq [keyval db] (prn (key keyval) (val keyval)))
:subprotocol "mysql"
:username "usr"
:classname "com.mysql.jdbc.Driver"
:subname "//100.100.100.100:3306/clo"
:password "pwd"
Более сжато, вы можете использовать деструктурирование для привязки каждой половины записей карты к некоторым именам, которые можно использовать внутри формы doseq
. Это идиоматично:
user> (doseq [[k v] db] (prn k v))
:subprotocol "mysql"
:username "usr"
:classname "com.mysql.jdbc.Driver"
:subname "//100.100.100.100:3306/clo"
:password "pwd"
Ответ 2
Вы можете просто сделать
(map (fn [[k v]] (prn k) (prn v)) {:a 1 :b 2})
Результат:
:a
1
:b
2
Это то, что вы искали?
Ответ 3
Просто короткое дополнение к Брайану:
Ваша оригинальная версия также может быть записана следующим образом.
(doseq [[k v] (map vector (keys db) (vals db))]
(println (str k " " v)))
В этом случае это, очевидно, глупо. Но в целом это работает и для несвязанных входных последовательностей, которые не связаны с одной и той же картой.
Ответ 4
Не совсем ясно, пытаетесь ли вы решить что-то, кроме распечатки значений (побочных эффектов), и если это все, к чему вы стремитесь, то я думаю, что решение doseq
, приведенное выше, было бы наиболее идиоматичным. Если вы хотите выполнить некоторые операции с ключами и значениями карты и вернуть тот же тип структуры данных, вам следует взглянуть на reduce-kv
, для которого вы можете найти документы по здесь
Подобно reduce
, reduce-kv
принимает функцию, начальное значение/аккумулятор и некоторые данные, но в этом случае данные являются картой, а не последовательностью. Функция получает три аргумента: аккумулятор, ключ тока и значение тока. Если вы действительно хотите выполнить какое-то преобразование данных и вернуть некоторые данные, это может показаться мне подходящим инструментом для работы.