2010-07-08 5 views
43

Pregunta de principiante, pero realmente no entiendo por qué hay tantas operaciones para construir mapas en clojure.¿Por qué hay tantas funciones de construcción de mapas en clojure?

Tiene conj, assoc y merge, pero parece que hacen más o menos lo mismo?

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

lo que es realmente la diferencia y por qué se requiere que todos estos métodos cuando lo hacen más o menos lo mismo?

+6

Hay también '(en {: a 1: b 2} {: c 3})' – VitoshKa

Respuesta

47

assoc y conj se comportan de manera muy diferente de otras estructuras de datos:

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] 

Si está escribiendo una función que puede manejar varios tipos de colecciones, entonces su elección hará una gran diferencia.

Trate merge como una función de solo mapas (es similar a conj para otras colecciones).

Mi opinión:

  • Assoc - utilizar cuando se está pares clave/valor existente 'cambiante'
  • conj - utilizar cuando se está 'agregando' nuevos pares clave/valor
  • fusión - utiliza cuando se está combinando dos o más mapas
+4

'merge' toma una cantidad arbitraria de mapas y los combina, no solo dos. – ponzao

+1

Buen punto. Cambié mi redacción. – dbyrne

+0

No hay problema, +1 para una buena respuesta. – ponzao

6

Dado que los mapas son una estructura de datos tan ubicua en Clojure, tiene sentido tener múltiples herramientas para manipularlos. Las diferentes funciones son todas sintácticamente convenientes en circunstancias ligeramente diferentes.

Mi visión personal sobre las funciones específicas que mencionas:

  • utilizo Assoc para agregar un valor único a un mapa dado una clave y un valor
  • utilizo fusión combinar dos mapas o agregar varias entradas nuevas a la vez
  • No suelo utilizar conj con mapas en absoluto como lo asocio mentalmente con las listas
+2

creo que el enfoque idiomática sería utilizar cualquier función coincide con la forma en que los elementos nuevos que se añaden al mapa inicialmente disponible; si es como mapas, use 'merge', si como un conjunto de claves y valores no ensamblados en una colección, use' assoc' etc. Lo que realmente quería señalar, sin embargo, es que 'conj' es * the * universal Función de construcción de la estructura de datos de Clojure: no se puede mantener realmente en el cajón de la lista. –

+0

@Michal quizás tengas razón, pero por alguna razón desconfío de las funciones que hacen semánticamente cosas muy diferentes en diferentes tipos de entrada sin advertencias :-) – mikera

+4

Argumentaría que 'conj' ** does ** hace operaciones semánticamente equivalentes en diferentes tipos de entrada. ;) – dbyrne

21

En realidad, estas funciones se comportan de manera bastante diferente cuando se usan con mapas.

  1. conj:

    En primer lugar, el ejemplo (conj {:a 1 :b 2} :c 3) del texto de la pregunta no funciona en absoluto (ni con ni con 1.1 1.2; IllegalArgumentException se lanza). Solo hay un puñado de tipos que pueden ser conj ed en mapas, a saber, vectores de dos elementos, clojure.lang.MapEntry s (que son básicamente equivalentes a vectores de dos elementos) y mapas.

    Tenga en cuenta que seq de un mapa comprende un grupo de MapEntry s. Por lo tanto, puedes hacer, por ejemplo,

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

    (tenga en cuenta que into utiliza conj - o conj!, cuando sea posible - internamente). Ni merge ni assoc le permite hacer eso.

  2. merge:

    Esto es casi exactamente equivalente a conj, pero reemplaza sus argumentos con nil{} - mapas vacíos de hash - y por lo tanto devolverá un mapa cuando el primer "mapa" de la cadena ocurre ser 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 
    

    Nota que no hay nada (excepto la cadena de documentación ...) para detener el programador del uso de merge con otros tipos de colección. Si uno hace eso, surge la rareza; no recomendado.

  3. assoc:

    Una vez más, el ejemplo del texto de la pregunta - (assoc {:a 1 :b 2} {:c 3}) - no va a funcionar; en su lugar, lanzará un IllegalArgumentException. assoc toma un argumento de mapa seguido de un número par de argumentos: aquellos en posiciones impares (digamos que el mapa está en la posición 0) son claves, los que están en posiciones pares son valores. Me parece que I assoc cosas en los mapas con más frecuencia que I conj, aunque cuando I conj, assoc se sentiría incómodo. ;-)

  4. merge-with:

    Para la mayor abundamiento, esta es la función básica final se trata de mapas. Lo encuentro extremadamente útil. Funciona como lo indica la secuencia de docs; he aquí un ejemplo:

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

    Tenga en cuenta que si un mapa contiene una clave "nuevo", que no ha ocurrido en ninguno de los mapas a la izquierda de la misma, la función de fusión no será anunciado. Esto ocasionalmente es frustrante, pero en 1.2 una astuta reify puede proporcionar un mapa con "valores predeterminados" que no sean nil.

+0

Para ilustrar el punto final sobre 'merge-with', he preparado el siguiente Gist: http://gist.github.com/468332 –

+0

Gracias por las explicaciones. Como viste, tuve un error de tipeo y cambié el segundo argumento entre conj y assoc. – grm

Cuestiones relacionadas