2010-07-08 18 views
43

Pytanie nowicjusza, ale tak naprawdę nie rozumiem, dlaczego jest tak wiele operacji na tworzenie map w clojure.Dlaczego w clojure jest tyle funkcji budowania map?

Masz conj, assoc i merge, ale wydają się mniej więcej robić to samo?

(assoc {:a 1 :b 2} :c 3) 
(conj {:a 1 :b 2} {:c 3}) 
(merge {:a 1 :b 2} {:c 3}) 

Jaka jest naprawdę różnica i dlaczego wszystkie te metody są wymagane, gdy robią mniej więcej to samo?

+6

Istnieje również '(w {: 1: b2} {: C 3})' – VitoshKa

Odpowiedz

47

assoc i conj zachowują się bardzo różnie dla innych struktur danych:

user=> (assoc [1 2 3 4] 1 5) 
[1 5 3 4] 
user=> (conj [1 2 3 4] 1 5) 
[1 2 3 4 1 5] 

Jeśli piszesz funkcję, która może obsługiwać wiele rodzajów zbiorów, to twój wybór będzie mieć duże znaczenie.

Leczenie merge jako funkcja tylko do map (jest podobna do conj w przypadku innych kolekcji).

Moja opinia:

  • assoc - używać gdy są 'zmieniających' istniejący par klucz/wartość
  • Conj - używać podczas 'dodawania' nowych par klucz/wartość
  • seryjnej - używać podczas łączenia dwóch lub więcej map
+4

'Merge' pobiera dowolną liczbę map i łączy je, nie tylko dwie. – ponzao

+1

Dobra uwaga. Zmieniono moje brzmienie. – dbyrne

+0

Bez prob, +1 za dobrą odpowiedź. – ponzao

6

Ponieważ mapy są tak wszechobecną strukturą danych w Clojure, warto mieć wiele narzędzi do ich manipulowania. Różne różne funkcje są wygodne pod względem składni w nieco innych okolicznościach.

Moje osobiste spojrzenie na poszczególnych funkcji można wymienić:

  • używam assoc dodać pojedynczą wartość na mapę danego klucza i wartości
  • używam seryjnej połączyć dwie mapy lub dodać wiele nowych wpisów naraz
  • Generalnie nie używam conj z mapami, ponieważ kojarzę to mentalnie z listami
+2

myślę idiomatycznym podejście byłoby użyć dowolnej funkcji odpowiada formy, w których nowe elementy mają być dodane do mapy początkowo stają się dostępne; jeśli jest to mapowanie, użyj 'scalania', jeśli jako zbiór kluczy i wartości niepołączonych w kolekcji, użyj' assoc' itd. Tym, co naprawdę chciałem podkreślić, jest to, że 'conj' jest * the * universal Funkcja budowania struktury danych Clojure - nie można jej naprawdę przechowywać w szufladzie na liście. –

+0

@Michal może masz rację - ale z jakiegoś powodu mam nieufność do funkcji, które czynią semantycznie bardzo różne rzeczy na różnych typach danych wejściowych bez ostrzeżeń :-) – mikera

+4

Twierdzę, że 'conj' ** nie ** robi semantycznie równoważne operacje na różnych typach danych wejściowych. ;) – dbyrne

21

W rzeczywistości te funkcje zachowują się zupełnie inaczej, gdy są używane z mapami.

  1. conj:

    Po pierwsze, przykład z zagadnieniu (conj {:a 1 :b 2} :c 3) nie działa w ogóle (ani przy 1,1 ani przy 1,2; IllegalArgumentException jest wyrzucany). Istnieje tylko kilka rodzajów, które mogą być conj ed na mapach, a mianowicie wektorów dwuelementowych, clojure.lang.MapEntry s (które są w zasadzie równoważne wektorom dwuelementowym) i map.

    Zauważ, że seq mapy zawiera grupę MapEntry s. W ten sposób możesz np.

    (into a-map (filter a-predicate another-map)) 
    

    (zauważ, że into wykorzystuje conj - lub conj!, jeśli to możliwe - wewnętrznie). Ani merge ani nie pozwalają na to.

  2. merge:

    To niemal dokładnie równoważne conj, ale zastępuje jego nil argumenty {} - Puste mapy hash - a tym samym powróci mapę kiedy pierwszy „map” w łańcuchu dzieje być nil.

    (apply conj [nil {:a 1} {:b 2}]) 
    ; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList 
    (apply merge [nil {:a 1} {:b 2}]) 
    ; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap 
    

    Uwaga nic (oprócz docstring ...), aby zatrzymać programator z użyciem merge z innymi rodzajami odpadów. Jeśli tak się dzieje, dochodzi do dziwactwa; Niepolecane.

  3. assoc:

    Znowu przykład z tekstu zapytania - (assoc {:a 1 :b 2} {:c 3}) - nie będzie działać; zamiast tego wyrzuci on IllegalArgumentException. assoc przyjmuje argument mapy, a następnie parzystą liczbę argumentów - te w pozycjach nieparzystych (załóżmy, że mapa znajduje się w pozycji 0) są kluczami, te na równych pozycjach są wartościami. Często znajduję rzeczy na mapach częściej niż I conj, ale kiedy I conj, assoc czułbym się nieporęczna. ;-)

  4. merge-with:

    Dla kompletności, jest to ostateczny Podstawową funkcją czynienia z mapami. Uważam, że jest to niezwykle przydatne. Działa tak, jak wskazuje docstring; Oto przykład:

    (merge-with + {:a 1} {:a 3} {:a 5}) 
    ; => {:a 9} 
    

    Zauważ, że jeśli mapa zawiera „nowe” klucz, który nie wystąpił w żadnym z mapami na lewo od niej, funkcja łączenie nie zostanie wywołana. Czasami jest to frustrujące, ale w wersji 1.2 sprytny reify może dostarczyć mapę z "domyślnymi wartościami" innymi niż nil.

+0

Aby zilustrować końcowy punkt 'merge-with', przygotowałem następujący dokument Gist: http://gist.github.com/468332 –

+0

Dziękuję za wyjaśnienia. Jak widzisz, miałem błąd w pisaniu i zmieniłem drugi argument pomiędzy conj i assoc. – grm

Powiązane problemy