2017-05-23 2 views
0

In Elixir, was wäre eine effiziente Möglichkeit, einen Map durch seine Werte zu filtern.Effiziente Methode zum Filtern einer Karte nach Wert in Elixir

Im Moment habe ich die folgende Lösung

%{foo: "bar", biz: nil, baz: 4} 
|> Enum.reject(fn {_, v} -> is_nil(v) end) 
|> Map.new 

Diese Lösung scheint ziemlich ineffizient zu mir. Wenn sie auf Map aufgerufen wird, gibt Enum.reject/2 eine Keywords zurück. Da ich eine Map möchte, muss ich Map.new/1 anrufen, um diese Keywords zurück zu mir zu konvertieren.

Dies scheint ineffizient, weil Enum.reject/2 die über Map einmal zu durchlaufen hat und dann vermutlich Map.new/1 hat über die Keywords ein anderes Mal zu wiederholen.

Was wäre eine effizientere Lösung?

Antwort

2

Sie können :maps.filter/2 verwenden, die eine Karte filtert und erzeugt keine Zwischen Liste:

iex(1)> :maps.filter fn _, v -> v != nil end, %{foo: "bar", biz: nil, baz: 4} 
%{baz: 4, foo: "bar"} 

Ein einfacher Benchmark bestätigt dies ist schneller als Enum.filter + Map.new:

map = for i <- 1..100000, into: %{}, do: {i, Enum.random([nil, 1, 2])} 

IO.inspect :timer.tc(fn -> 
    map 
    |> Enum.reject(fn {_, v} -> is_nil(v) end) 
    |> Map.new 
end) 

IO.inspect :timer.tc(fn -> 
    :maps.filter fn _, v -> v != nil end, map 
end) 
{44728, 
%{48585 => 1, 60829 => 2, 12995 => 1, 462 => 2, 704 => 2, 28954 => 2, 
    29635 => 2, 78798 => 1, 92572 => 1, 89750 => 2, 39389 => 2, 62855 => 2, 
    79313 => 1, 92062 => 2, 61871 => 1, 92856 => 2, 75920 => 1, 59922 => 1, 
    37912 => 2, 30420 => 2, 51211 => 2, 7994 => 2, 78269 => 2, 9765 => 2, 
    38352 => 2, 6653 => 1, 82555 => 2, 54031 => 2, 45138 => 1, 41351 => 1, 
    40746 => 1, 5961 => 1, 66704 => 2, 33823 => 1, 47603 => 1, 86873 => 1, 
    81009 => 2, 96255 => 1, 36219 => 1, 1328 => 2, 33314 => 1, 54477 => 2, 
    40189 => 2, 27028 => 1, 31676 => 1, 94037 => 1, 32388 => 1, 4351 => 1, 
    46309 => 1, ...}} 
{28638, 
%{48585 => 1, 60829 => 2, 12995 => 1, 462 => 2, 704 => 2, 28954 => 2, 
    29635 => 2, 78798 => 1, 92572 => 1, 89750 => 2, 39389 => 2, 62855 => 2, 
    79313 => 1, 92062 => 2, 61871 => 1, 92856 => 2, 75920 => 1, 59922 => 1, 
    37912 => 2, 30420 => 2, 51211 => 2, 7994 => 2, 78269 => 2, 9765 => 2, 
    38352 => 2, 6653 => 1, 82555 => 2, 54031 => 2, 45138 => 1, 41351 => 1, 
    40746 => 1, 5961 => 1, 66704 => 2, 33823 => 1, 47603 => 1, 86873 => 1, 
    81009 => 2, 96255 => 1, 36219 => 1, 1328 => 2, 33314 => 1, 54477 => 2, 
    40189 => 2, 27028 => 1, 31676 => 1, 94037 => 1, 32388 => 1, 4351 => 1, 
    46309 => 1, ...}} 
0

Es könnte ein wenig teuer sein, aber es ist deklarativer, was IMO mehr Wert hinzufügt. Überlegen Sie auch, wie groß Ihre Sammlung sein wird und ob es sinnvoll ist, diesen Filter zu optimieren.

Aber ich verstehe Ihre Sorge, hier so ist das, was ich getan habe:

%{foo: "bar", biz: nil, baz: 4} 
|> Enum.reduce(%{}, filter_nil_values/2) 

Wo filter_nil_values/2 definiert als

defp filter_nil_values({_k, nil}, accum), do: accum 
defp filter_nil_values({k, v}, accum), do: Map.put(accum, k, v) 

Ich habe versucht, dies zu tun in einer einzeiligen Funktion, aber es sieht schrecklich aus.

0

In diesem Fall wäre das Verständnis eine gute Idee, weil es auch nicht so ist Erstellen Sie eine Zwischenliste und gibt Ihnen eine Karte zurück:

+0

Dies sollte keine Zwischenliste erstellen, aber in der Praxis habe ich 'for ..., in:% {}' gefunden, um noch langsamer zu sein als das Erstellen eine Liste mit 'Enum.filter/2' und übergebe sie an' Map.new/1'. Ich habe es mit dem Benchmark versucht, den ich in meiner Antwort gepostet habe, und das läuft langsamer als der Code von OP um etwa 20-25% für mich. – Dogbert

+0

Danke :) Ich bin nicht überrascht, dass erlangs Lösung die schnellste ist. – PatNowak

Verwandte Themen