2016-02-15 8 views
7

Als ein Beispiel, sagen wir, ich habe eine aufzählbare collection von Paaren {first, second}. Gruppierungs diese PaareWie mappe ich gleichzeitig und group_by?

Enum.group_by(collection, fn {first, second} -> first end) 

Verwendung in einem Map deren Schlüssel bestimmt werden durch die übergebene anonyme Funktion führen. Seine Werte sind Sammlungen von Paaren. Allerdings möchte ich, dass seine Werte stattdessen die second Elemente des Paares enthalten.


Im Allgemeinen eine zählbare gegeben, würde Ich mag Gruppe sowohl einen Schlüssel-Extraktor und Wert Mapper bereitstellt, damit ich feststellen kann, was in die resultierenden Map ‚s Werte setzen wird. Das heißt, würde ich so etwas wie

map_group_by(
    collection, 
    fn {_first, second} -> second end, 
    fn {first, _second} -> first end 
) 

wo collection ‚s Werte abgebildet werden, bevor sie gruppiert mögen, noch, wo der Schlüssel Mapper arbeitet immer noch auf den ursprünglichen Elementen.

Gibt es eine solche Funktion in der Standardbibliothek? Wenn nicht, was ist der idiomatische Weg, dies zu erreichen?


Ich weiß, dass ich etwas tun könnte, wie

Enum.reduce(
    collection, 
    %{}, 
    fn({key, value}, acc) -> Dict.update(acc, key, [value], &([value | &1])) end 
) 

aber dies scheint klobig und schafft [value] Listen präventiv (ist das wirklich wahr?). Gibt es einen besseren Weg, der sowohl prägnant als auch effizient ist?

Antwort

4

Seit Elixir 1.3 gibt es jetzt Enum.group_by/3, die ein mapper_fun Argument nimmt, das genau dieses Problem löst.


Veraltet Antwort:

In diesem Moment gibt es keine solche Funktion in der Standardbibliothek. Ich beendete diese mit bis:

def map_group_by(enumerable, value_mapper, key_extractor) do 
    Enum.reduce(Enum.reverse(enumerable), %{}, fn(entry, categories) -> 
    value = value_mapper.(entry) 
    Map.update(categories, key_extractor.(entry), [value], &[value | &1]) 
    end) 
end 

, die (für mein Beispiel) kann dann wie folgt aufgerufen werden:

map_group_by(
    collection, 
    fn {_, second} -> second end, 
    fn {first, _} -> first end 
) 

Es wird aus der Standardbibliothek Enum.group_by angepasst. In Bezug auf die [value]: Ich weiß nicht, was der Compiler kann oder nicht optimieren kann, aber zumindest das ist, was Enum.group_by auch tut.

Beachten Sie die Enum.reverse Aufruf, der nicht in dem Beispiel aus meiner Frage war. Dadurch wird sichergestellt, dass die Reihenfolge der Elemente in den resultierenden Wertelisten erhalten bleibt. Wenn Sie nicht möchten, dass diese Reihenfolge beibehalten wird (wie ich es in meinem Fall getan habe, in dem ich sowieso nur aus dem Ergebnis abtasten wollte), kann es gelöscht werden.

5

Um Ihre Frage zu beantworten, Ich glaube nicht, dass es eine native Funktion, dies zu tun ist.

Aber ich gebe dir meine Lösung (Disclaimer: Ich bin neu in Elixir).

aus zu starten, ist es wichtig, zu bemerken, wie Sie in Elixir Docs sehen, dass eine Liste von Tupeln das gleiche wie ein Schlüssel-Wert-Liste ist:

iex> list = [{:a, 1}, {:b, 2}] 
[a: 1, b: 2] 
iex> list == [a: 1, b: 2] 
true 

Also in diesem Sinne die benutzen ist es einfach, Enum.map darüber hinweg.

Dies macht zwei Pässe es machen, aber es ist ein wenig sauberer suchen als das, was man hatte:

defmodule EnumHelpers do 
    def map_col(lst) do 
    lst 
    |> Enum.group_by(fn {x, _} -> x end) 
    |> Enum.map(fn {x, y} -> {x, Dict.values y} end) 
    end 
end 

IO.inspect EnumHelpers.map_col([a: 2, a: 3, b: 3]) 

die ausgedruckt werden:

[a: [3, 2], b: [3]] 

Edit: Schnellere Version :

defmodule EnumHelpers do 

    defp group_one({key, val}, categories) do 
    Dict.update(categories, key, [val], &[val|&1]) 
    end 

    def map_col_fast(coll) do 
    Enum.reduce(coll, %{}, &group_one/2) 
    end 
end 

IO.inspect EnumHelpers.map_col_fast([a: 2, a: 3, b: 3]) 
+1

Nur zur Klarstellung, dies kann nicht 'Map.values' verwenden, da es nicht an einer Tupelliste arbeitet, sondern an einer echten" Map ". – JustGage

+0

Vielen Dank für Ihre Antwort, aber das ist nicht, was ich gesucht habe: wie Sie bemerken, macht dies einen zweiten Durchgang, aber noch wichtiger, es schafft auch eine Zwischenkollektion. Es ist daher unnötig ineffizient. – user4235730

+0

@ user4235730, ich habe eine Version hinzugefügt, die Ihrer Fantasie entsprechen sollte. Beachten Sie jedoch, dass die Erlang VM den Speicher niemals an Ort und Stelle verändert und somit Zwischensammlungen erstellt, obwohl dies bei unveränderlichen Werten kein Problem ist, da sie die unveränderten Teile der alten Version in gewissem Maße referenzieren kann (falls dies der Fall ist) funktioniert wie Clojure). – JustGage

Verwandte Themen