Статьи

Замена общего кода вызовами функций clojure.set


Если вы написали достаточное количество кода 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 /присоединиться