Если вы написали достаточное количество кода Clojure и не знакомы с clojure.set, скорее всего, вы, вероятно, заново изобрели несколько функций, которые уже доступны в стандартной библиотеке. В этом посте я приведу несколько примеров обычно написанного кода и покажу функции clojure.set, которые уже делают все, что вам нужно.
Удаление элементов из коллекции — очень распространенная задача программирования. Иногда коллекция должна быть вектором или списком, и удаление элемента из коллекции будет выглядеть аналогично приведенному ниже примеру.
user=> (remove #{1 2} [1 2 3 4 3 2 1]) (3 4 3)
В тех случаях, когда вы начинаете со списка и хотите вернуть seq, удаление является хорошим решением. Тем не менее, вы также можете начать с набора или попытаться вернуть набор.
Если вы начинаете с наборов, вы, вероятно, получите выигрыш в производительности, используя clojure.set / разность, а если вам понадобится вернуть возвращаемый набор, он будет содержать меньше кода и, вероятно, будет более производительным для использования clojure.set / разности, а не чем вызывать clojure.core / установить по результатам clojure.core / remove.
clojure.set / разница проста в использовании — из документов
Usage: (difference s1) (difference s1 s2) (difference s1 s2 & sets) Return a set that is the first set without elements of the remaining sets
Простой пример использования clojure.set / diff может быть найден ниже.
user=> (clojure.set/difference #{1 2 3 4 5} #{1 2} #{3}) #{4 5}
Преобразование данных в clojure — это то, что я делаю очень часто. Во многих случаях у меня был список карт, и я хотел, чтобы они были проиндексированы по 1 или более значениям. Это довольно легко сделать с помощью Reduce и Update, как показано в примере ниже.
user=> (def jay {:name "jay fields" :employer "drw"}) #'user/jay user=> (def mike {:name "mike jones" :employer "forward"}) #'user/mike user=> (def john {:name "john dydo" :employer "drw"}) #'user/john user=> (reduce #(update-in %1 [{:employer (:employer %2)}] conj %2) {} [jay mike john]) {{:employer "forward"} ({:name "mike jones", :employer "forward"}), {:employer "drw"} ({:name "john dydo", :employer "drw"} {:name "jay fields", :employer "drw"})}
Комбинация Reduce + update-in хороша, но clojure.set / index еще лучше — так как она более краткая и не требует определения анонимной функции. clojure.set / index также очень прост в использовании — из документов
Usage: (index xrel ks) Returns a map of the distinct values of ks in the xrel mapped to a set of the maps in xrel with the corresponding values of ks.
Приведенный ниже пример демонстрирует, как вы можете получить результаты, очень похожие на те, что указаны выше, используя clojure.set / index
user=> (clojure.set/index [jay mike john] [:employer])
{{:employer "forward"} #{{:name "mike jones", :employer "forward"}},
{:employer "drw"} #{{:name "john dydo", :employer "drw"}
{:name "jay fields", :employer "drw"}}}
Стоит отметить, что в приведенном в примере приведениях + обновлении значениях используются значения seqs, и они могут содержать дубликаты, а в примере clojure.set / index значения задаются как наборы и не содержат дубликатов. На практике это никогда не было проблемой для меня.
Другой распространенный случай при работе с коллекциями — это нахождение элементов в обеих коллекциях. Поскольку наборы являются функциями (и могут использоваться предикаты), найти общие элементы так же просто, как в следующем соглашении.
user=> (filter (set [1 2 3]) [2 3 4]) (2 3)
Как и в примере с clojure.set / diff, если у вас есть списки или векторы, и вы хотите использовать seq out, вы можете использовать фильтр. Однако, если вы уже работаете с наборами или можете легко преобразовать их в наборы, вы, вероятно, захотите взглянуть на clojure.set / пересечение.
Usage: (intersection s1) (intersection s1 s2) (intersection s1 s2 & sets) Return a set that is the intersection of the input sets
Чтобы получить результаты, аналогичные приведенному выше, просто вызовите clojure.set / intersection аналогично приведенному ниже примеру.
user=> (clojure.set/intersection #{1 2 3} #{2 3 4}) #{2 3}
В кодовой базе, над которой я когда-то работал, я наткнулся на следующий код, который переворачивает карту.
user=> (reduce #(assoc %1 (val %2) (key %2)) {} {1 :one 2 :two 3 :three}) {:three 3, :two 2, :one 1}
Код достаточно прост, но всегда предпочтительнее один вызов функции.
Usage: (map-invert m) Returns the map with the vals mapped to the keys.
Название функции должно быть самоочевидным; однако ниже приведен пример для полноты.
user=> (clojure.set/map-invert {1 :one 2 :two 3 :three}) {:three 3, :two 2, :one 1}
Другая распространенная задача, которую я выполняю при работе с clojure, — это обрезка наборов данных. Следующий код отображает список сотрудников и отфильтровывает информацию работодателя.
user=> (def jay {:fname "jay" :lname "fields" :employer "drw"}) #'user/jay user=> (def mike {:fname "mike" :lname "jones" :employer "forward"}) #'user/mike user=> (def john {:fname "john" :lname "dydo" :employer "drw"}) #'user/john user=> (map #(select-keys %1 [:fname :lname]) [jay mike john]) ({:lname "fields", :fname "jay"} {:lname "jones", :fname "mike"} {:lname "dydo", :fname "john"})
Комбинация map + select-keys выполняет свою работу, но clojure.set дает нам одну функцию, clojure.set / project, которая обеспечивает практически тот же результат — использование меньшего количества кода.
Usage: (project xrel ks) Returns a rel of the elements of xrel with only the keys in ks
Пример ниже демонстрирует сходство в функциональности.
user=> (clojure.set/project [jay mike john] [:fname :lname]) #{{:lname "fields", :fname "jay"} {:lname "dydo", :fname "john"} {:lname "jones", :fname "mike"}}
Как и в случае с clojure.set / index, вы захотите принять к сведению, что результат является набором, а не списком, и, как и clojure.set / index, на практике это не вызывает проблем.
Функции переименования и переименования ключей в clojure.set очень похожи, и они могут быть полезны, когда вы передаете сходные структуры данных и просто нуждаетесь в нескольких переименованиях, чтобы хорошо играть с существующим кодом.
Ниже приведены несколько простых примеров того, как можно добиться успеха без переименования и переименования ключей.
user=> (def jay {:fname "jay" :lname "fields" :employer "drw"}) #'user/jay user=> (def mike {:fname "mike" :lname "jones" :employer "forward"}) #'user/mike user=> (def john {:fname "john" :lname "dydo" :employer "drw"}) #'user/john user=> (map (fn [{:keys [fname lname] :as m}] (-> m (assoc :first-name fname :last-name lname) (dissoc :fname :lname))) [jay mike john]) ({:last-name "fields", :first-name "jay", :employer "drw"} {:last-name "jones", :first-name "mike", :employer "forward"} {:last-name "dydo", :first-name "john", :employer "drw"}) user=> (reduce #(assoc %1 ({1 "one" 2 "two"} (key %2)) (val %2)) {} {1 :one 2 :two}) {"two" :two, "one" :one}
Функции переименования и переименования ключей очень просты, и вы можете найти их документацию и примеры использования ниже.
Usage: (rename xrel kmap) Returns a rel of the maps in xrel with the keys in kmap renamed to the vals in kmap Usage: (rename-keys map kmap) Returns the map with the keys in kmap renamed to the vals in kmap
user=> (clojure.set/rename [jay mike john] {:fname :first-name :lname :last-name}) #{{:last-name "jones", :first-name "mike", :employer "forward"} {:last-name "dydo", :first-name "john", :employer "drw"} {:last-name "fields", :first-name "jay", :employer "drw"}} user=> (clojure.set/rename-keys {1 :one 2 :two} {1 "one" 2 "two"}) {"two" :two, "one" :one}
Если вы зашли так далеко, я предполагаю, что вы уже понимаете, как использовать фильтр. Пространство имен clojure.set имеет функцию, которая очень похожа на фильтр, но возвращает набор. Если вам не нужен набор, лучше придерживаться фильтра; однако, если вы работаете с наборами, вы можете сэкономить несколько нажатий клавиш и микросекунд, используя вместо этого clojure.set / select.
Ниже приведена документация и пример.
Usage: (select pred xset) Returns a set of the elements for which pred is true
user=> (clojure.set/select odd? #{1 2 3 4}) #{1 3}
Clojure.set / subset? и clojure.set / superset? Функции также являются функциями, которые просты в использовании, и, вероятно, не получат пользу от примера того, как создать те же результаты самостоятельно. Тем не менее, я предоставлю документы и 2 кратких примера их использования.
Usage: (subset? set1 set2) Is set1 a subset of set2? Usage: (superset? set1 set2) Is set1 a superset of set2?
user=> (clojure.set/superset? #{1 2 3} #{2 3}) true user=> (clojure.set/subset? #{1 2} #{1 2 3}) true
Последняя функция, которую я запишу, — это clojure.set / union. Если вам нужен список уникальных элементов, полученных в результате объединения двух или более списков, вы можете выполнить работу с помощью комбинаций concat, Reduce и / или Set. В приведенном ниже примере показано, как действовать без использования функции set или структуры данных set. примечание: использование набора, скорее всего, будет более эффективным и более читабельным. Этот пример предназначен для того, чтобы показать, что вы можете делать вещи без наборов, но я не рекомендую вам кодировать таким образом.
(reduce #(if (some (partial = %2) %1) %1 (conj %1 %2)) [] (concat [1 2 1] [2 4 3 1])) [1 2 4 3]
По правде говоря, я не склонен думать о «объединении», если я уже не думаю о сетах. В Clojure определено, что clojure.set / union принимает несколько наборов и возвращает объединение каждого из этих наборов (как и следовало ожидать).
Usage: (union) (union s1) (union s1 s2) (union s1 s2 & sets) Return a set that is the union of the input sets
Наконец, пример ниже показывает функцию объединения в действии.
user=> (clojure.set/union #{1 2} #{2 4 3 1}) #{1 2 3 4}
Пространство имен clojure.set определяет одну дополнительную функцию, clojure.set / join. Честно говоря, я не использовал join в работе и не верю, что пишу свои собственные низшие версии в своих базах кода. Итак, у меня нет примера для вас, но мне нравятся примеры на clojuredocs.org, и я бы посоветовал вам проверить их:
http://clojuredocs.org/clojure_core/1.2.0/clojure.set /присоединиться