Подтвердить что ты не робот

Как перебрать ключи и значения карты в 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))))))
4b9b3361

Ответ 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 принимает функцию, начальное значение/аккумулятор и некоторые данные, но в этом случае данные являются картой, а не последовательностью. Функция получает три аргумента: аккумулятор, ключ тока и значение тока. Если вы действительно хотите выполнить какое-то преобразование данных и вернуть некоторые данные, это может показаться мне подходящим инструментом для работы.