2016-02-15 13 views
7

Jako przykład, załóżmy, że mam przeliczalne collection z par {first, second}. Grupowanie tych par użyciuJak jednocześnie mapować i group_by?

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

spowoduje Map którego klucze są określone przez przekazany funkcji anonimowej. Jego wartości to kolekcje par. Chciałbym jednak, aby jego wartości zawierały elementy tej pary: second.


Na ogół podawany jest przeliczalny, chciałbym grupa zapewniając zarówno kluczowy wyciąg i do wartości mapowania, tak, że mogę określić, co zostanie wprowadzone do uzyskanych wartości Map „s. To znaczy, chciałbym coś takiego

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

gdzie collection „s Wartości są odwzorowane przed zgrupowane, jeszcze gdzie klucz odwzorowujący nadal pracuje na oryginalnych elementów.

Czy istnieje taka funkcja w bibliotece standardowej? Jeśli nie, jaki jest najbardziej idiomatyczny sposób, aby to osiągnąć?


Wiem, że mógłbym zrobić coś takiego

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

ale to wydaje się niezgrabne i tworzy [value] list zapobiegawczo (jest to rzeczywiście prawda?). Czy istnieje lepszy sposób, który jest jednocześnie zwięzły i skuteczny?

Odpowiedz

4

Od Eliksir 1.3 jest teraz Enum.group_by/3, który przyjmuje argument mapper_fun, który rozwiązuje dokładnie ten problem.


odpowiedź Przestarzałe:

W tej chwili nie ma takiej funkcji w bibliotece standardowej. I skończyło się tak:

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 

które mogą (na moim przykładzie), to można nazwać tak:

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

To jest adaptacją średnia Biblioteki Enum.group_by. Jeśli chodzi o [value]: Nie wiem, co kompilator może lub nie może zoptymalizować, ale przynajmniej to samo robi Enum.group_by.

Zwróć uwagę na wywołanie Enum.reverse, którego nie było w przykładzie z mojego pytania. Gwarantuje to, że kolejność elementów zostanie zachowana na wynikowych listach wartości. Jeśli nie chcesz, aby to zamówienie zostało zachowane (tak jak zrobiłem to w moim przypadku, w którym chciałem tylko próbować z wyniku), to można je usunąć.

5

Aby odpowiedzieć na twoje pytanie, Nie sądzę, że istnieje natywna funkcja do wykonania tego.

Ale dam ci moje rozwiązanie (zrzeczenie się: Jestem nowy w Elixir).

Aby rozpocząć, ważne jest, aby zauważyć, jak widać w Elixir Docs że lista krotek jest taka sama jak lista wartości klucza:

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

więc mając to na uwadze, łatwo jest użyć Enum.map w poprzek.

To czyni dwóch przejściach It ale to trochę czystsze patrząc niż to, co miał:

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]) 

który wypisze:

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

Edit: szybsza wersja :

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

Dla wyjaśnienia nie można użyć wartości 'Map.values', ponieważ nie działa ona na liście krotek, a nie na prawdziwej" Mapie ". – JustGage

+0

Dziękuję za odpowiedź, ale to nie było to, czego szukałem: jak zauważyłeś, robi to drugie przejście, ale co ważniejsze, tworzy także kolekcję pośrednią. Jest zatem niepotrzebnie nieefektywna. – user4235730

+0

@ user4235730, dodałem wersję, która powinna pasować do twoich upodobań. Zauważ, że jednak maszyna wirtualna Erlang nigdy nie modyfikuje pamięci w miejscu, a zatem tworzy kolekcje pośrednie bez względu na wszystko, chociaż nie jest to taki problem z niezmiennymi wartościami, ponieważ może w pewnym stopniu odnosić się do niezmienionych części starej wersji (jeśli działa w ogóle jak Clojure). – JustGage

Powiązane problemy