433 Stimmen

Wie man in Ruby Nullwerte zuordnet und entfernt

Tengo un map die entweder einen Wert ändert oder ihn auf null setzt. Ich möchte dann die Nulleinträge aus der Liste entfernen. Die Liste muss nicht beibehalten werden.

Dies ist mein derzeitiger Stand:

# A simple example function, which returns a value or nil
def transform(n)
  rand > 0.5 ? n * 10 : nil }
end

items.map! { |x| transform(x) } # [1, 2, 3, 4, 5] => [10, nil, 30, 40, nil]
items.reject! { |x| x.nil? } # [10, nil, 30, 40, nil] => [10, 30, 40]

Ich bin mir bewusst, ich könnte einfach eine Schleife tun und bedingt in einem anderen Array wie dieses sammeln:

new_items = []
items.each do |x|
    x = transform(x)
    new_items.append(x) unless x.nil?
end
items = new_items

Aber es scheint nicht so idiomatisch zu sein. Gibt es eine nette Art und Weise, eine Funktion über eine Liste abzubilden, Entfernen/Ausschließen der nils wie Sie gehen?

1097voto

the Tin Man Punkte 154584

Sie könnten verwenden compact :

[1, nil, 3, nil, nil].compact
=> [1, 3] 

Ich möchte die Leute daran erinnern, dass, wenn Sie ein Array erhalten, das nils als Ausgabe von einem map Block, und dieser Block versucht, bedingt Werte zurückzugeben, dann haben Sie Codegeruch und müssen Ihre Logik überdenken.

Zum Beispiel, wenn Sie etwas tun, das dies tut:

[1,2,3].map{ |i|
  if i % 2 == 0
    i
  end
}
# => [nil, 2, nil]

Dann lassen Sie es. Stattdessen sollten Sie vor dem map , reject die Dinge, die Sie nicht wollen oder select was Sie wirklich wollen:

[1,2,3].select{ |i| i % 2 == 0 }.map{ |i|
  i
}
# => [2]

Ich erwäge die Verwendung von compact ein Chaos aufzuräumen, um Dinge loszuwerden, mit denen wir nicht richtig umgegangen sind, meist weil wir nicht wussten, was auf uns zukommt. Wir sollten immer wissen, welche Art von Daten in unserem Programm herumgeschleudert wird; unerwartete/unbekannte Daten sind schlecht. Jedes Mal, wenn ich in einem Array, an dem ich arbeite, Nils sehe, untersuche ich, warum sie existieren, und schaue, ob ich den Code, der das Array erzeugt, verbessern kann, anstatt Ruby Zeit und Speicher zu verschwenden, um Nils zu erzeugen und dann das Array zu durchsuchen, um sie später zu entfernen.

'Just my $%0.2f.' % [2.to_f/100]

109voto

Ziggy Punkte 21056

Versuchen Sie es mit reduce o inject .

[1, 2, 3].reduce([]) { |memo, i|
  if i % 2 == 0
    memo << i
  end

  memo
}

Ich stimme der gängigen Antwort zu, dass wir nicht map y compact aber nicht aus den gleichen Gründen.

Ich fühle tief in mir, dass map dann compact ist gleichbedeutend mit select dann map . Bedenken Sie: map ist eine Eins-zu-Eins-Funktion. Wenn Sie von einer Menge von Werten abbilden, und Sie map dann können Sie wollen einen Wert in der Ausgabemenge für jeden Wert in der Eingabemenge. Wenn Sie Folgendes tun müssen select dann wollen Sie wahrscheinlich keine map am Set. Wenn Sie select danach (oder compact ), dann wollen Sie wahrscheinlich keine map am Set. In beiden Fällen iterieren Sie zweimal über die gesamte Menge, wenn ein reduce muss nur einmal gehen.

Im Englischen versuchen Sie außerdem, "eine Menge ganzer Zahlen in eine Menge gerader ganzer Zahlen zu reduzieren".

96voto

SRack Punkte 9986

Ruby 2.7+

Jetzt schon!

Mit Ruby 2.7 wird eingeführt filter_map für genau diesen Zweck. Es ist idiomatisch und leistungsfähig, und ich erwarte, dass es sehr bald zur Norm wird.

Zum Beispiel:

numbers = [1, 2, 5, 8, 10, 13]
enum.filter_map { |i| i * 2 if i.even? }
# => [4, 16, 20]

In Ihrem Fall, da der Block als falsch ausgewertet wird, einfach:

items.filter_map { |x| process_x url }

" Ruby 2.7 fügt Enumerable#filter_map hinzu " ist eine gute Lektüre zu diesem Thema, mit einigen Leistungsvergleichen zu früheren Ansätzen zu diesem Problem:

N = 100_000
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
  x.report("select + map")  { N.times { enum.select { |i| i.even? }.map{ |i| i + 1 } } }
  x.report("map + compact") { N.times { enum.map { |i| i + 1 if i.even? }.compact } }
  x.report("filter_map")    { N.times { enum.filter_map { |i| i + 1 if i.even? } } }
end

# Rehearsal -------------------------------------------------
# select + map    8.569651   0.051319   8.620970 (  8.632449)
# map + compact   7.392666   0.133964   7.526630 (  7.538013)
# filter_map      6.923772   0.022314   6.946086 (  6.956135)
# --------------------------------------- total: 23.093686sec
# 
#                     user     system      total        real
# select + map    8.550637   0.033190   8.583827 (  8.597627)
# map + compact   7.263667   0.131180   7.394847 (  7.405570)
# filter_map      6.761388   0.018223   6.779611 (  6.790559)

43voto

Evgeniya Manolova Punkte 2412

Eindeutig compact ist der beste Ansatz für die Lösung dieser Aufgabe. Wir können das gleiche Ergebnis jedoch auch mit einer einfachen Subtraktion erreichen:

[1, nil, 3, nil, nil] - [nil]
 => [1, 3]

35voto

sawa Punkte 160498

In Ihrem Beispiel:

items.map! { |x| process_x url } # [1, 2, 3, 4, 5] => [1, nil, 3, nil, nil]

sieht es nicht so aus, als hätten sich die Werte geändert, außer dass sie durch nil . Wenn das der Fall ist, dann:

items.select{|x| process_x url}

reicht aus.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X