2009-03-05 21 views
17

W języku Ruby, jaki jest najbardziej ekspresywny sposób mapowania tablicy w taki sposób, że niektóre elementy są modyfikowane , a pozostałe pozostały nietknięte:?Mapowanie tablicy modyfikującej tylko elementy pasujące do określonego warunku

To jest prosta droga, aby to zrobić:

old_a = ["a", "b", "c"]       # ["a", "b", "c"] 
new_a = old_a.map { |x| (x=="b" ? x+"!" : x) } # ["a", "b!", "c"] 

Pominięcie "leave-alone" przypadek oczywiście jeśli nie wystarczy:

new_a = old_a.map { |x| x+"!" if x=="b" }  # [nil, "b!", nil] 

Chciałbym coś takiego jak to:

new_a = old_a.map_modifying_only_elements_where (Proc.new {|x| x == "b"}) 
     do |y| 
      y + "!" 
     end 
# ["a", "b!", "c"] 

Czy jest jakiś dobry sposób, aby zrobić to w Ruby (lub może Rails ma jakieś metody convenience którego jeszcze nie znalazłem)?


Dziękuję wszystkim za odpowiedź. Podczas gdy wspólnie przekonaliście mnie, że najlepiej jest korzystać z operatora trójskładnikowego pod numerem map, niektórzy z was napisali bardzo ciekawe odpowiedzi!

+0

Myślę, że rzecz #map jest w porządku tak jak jest. ;-) –

+0

Tak, zgadzam się. Możesz usunąć pareny, jeśli sprawi, że będziesz się bardziej podobać! –

+0

Zamknięte? FTL. Spójrz na mój post = P –

Odpowiedz

6

Zgadzam się, że wyciąg z mapy jest dobry, jak jest. Jest jasne i proste, i byłoby łatwe do utrzymania dla każdego.

Jeśli chcesz coś bardziej skomplikowanego, co powiesz na to?

module Enumerable 
    def enum_filter(&filter) 
    FilteredEnumerator.new(self, &filter) 
    end 
    alias :on :enum_filter 
    class FilteredEnumerator 
    include Enumerable 
    def initialize(enum, &filter) 
     @enum, @filter = enum, filter 
     if enum.respond_to?(:map!) 
     def self.map! 
      @enum.map! { |elt| @filter[elt] ? yield(elt) : elt } 
     end 
     end 
    end 
    def each 
     @enum.each { |elt| yield(elt) if @filter[elt] } 
    end 
    def each_with_index 
     @enum.each_with_index { |elt,index| yield(elt, index) if @filter[elt] } 
    end 
    def map 
     @enum.map { |elt| @filter[elt] ? yield(elt) : elt } 
    end 
    alias :and :enum_filter 
    def or 
     FilteredEnumerator.new(@enum) { |elt| @filter[elt] || yield(elt) } 
    end 
    end 
end 

%w{ a b c }.on { |x| x == 'b' }.map { |x| x + "!" } #=> [ 'a', 'b!', 'c' ] 

require 'set' 
Set.new(%w{ He likes dogs}).on { |x| x.length % 2 == 0 }.map! { |x| x.reverse } #=> #<Set: {"likes", "eH", "sgod"}> 

('a'..'z').on { |x| x[0] % 6 == 0 }.or { |x| 'aeiouy'[x] }.to_a.join #=> "aefiloruxy" 
+16

You Przekształciłem jedną linię w trzydzieści coś. Lubię Twój styl. – Pesto

+0

Ja * powiedziałem *, że najlepiej trzymać się warunkowego w/na mapie :) – rampion

3

Twoje rozwiązanie map jest najlepsze. Nie jestem pewien, dlaczego uważasz, że map_modifying_only_elements_ gdzie jest jakoś lepiej. Używanie map jest czystsze, bardziej zwięzłe i nie wymaga wielu bloków.

1

Jeśli nie potrzebujesz starej tablicy, wolę mapę! w tym przypadku, ponieważ możesz użyć! Metoda reprezentowania zmienia tablicę w miejscu.

self.answers.map!{ |x| (x=="b" ? x+"!" : x) } 

wolę to ponad:

new_map = self.old_map{ |x| (x=="b" ? x+"!" : x) } 
7
old_a.map! { |a| a == "b" ? a + "!" : a } 

daje

=> ["a", "b!", "c"] 

map! modyfikuje odbiornik w miejscu, więc old_a jest teraz, że zwrócona tablica.

1

To długa kilka linii, ale tutaj jest alternatywą dla piekła go:

oa = %w| a b c | 
na = oa.partition { |a| a == 'b' } 
na.first.collect! { |a| a+'!' } 
na.flatten! #Add .sort! here if you wish 
p na 
# >> ["b!", "a", "c"] 

Collect z trójskładnikowego wydaje się najlepszym moim zdaniem.

23

Ponieważ tablice są wskaźnikami, to działa również:

a = ["hello", "to", "you", "dude"] 
a.select {|i| i.length <= 3 }.each {|i| i << "!" } 

puts a.inspect 
# => ["hello", "to!", "you!", "dude"] 

W pętli, upewnij się stosować metodę, która zmienia przedmiot zamiast tworzenia nowego obiektu. Na przykład. upcase! w porównaniu do upcase.

Dokładna procedura zależy od tego, co dokładnie chcesz osiągnąć. Trudno przyswoić konkretną odpowiedź za pomocą przykładów foo-bar.

1

Znalazłem, że najlepszą drogą do osiągnięcia tego celu jest wykorzystywanie tap

arr = [1,2,3,4,5,6] 
[].tap do |a| 
    arr.each { |x| a << x if x%2==0 } 
end 
2

One liner:

["a", "b", "c"].inject([]) { |cumulative, i| i == "b" ? (cumulative << "#{i}!") : cumulative } 

W powyższym kodzie zacząć [] "skumulowany". Jak wyliczysz przez Enumerator (w naszym przypadku tablica, ["a", "b", "c"]), zarówno kumulatywny, jak i "bieżący" element zostanie przekazany do naszego bloku (| skumulowany, i |) oraz wynik wykonania naszego bloku jest kumulowany. To, co robię powyżej, jest kumulowane bez zmian, gdy element nie jest "b" i dołącz "b!" do skumulowanej tablicy i zwróć ją, gdy jest b.

Istnieje odpowiedź powyżej, która używa select, co jest najłatwiejszym sposobem na zrobienie tego (i zapamiętanie).

Można łączyć select z map, aby osiągnąć to, czego szukasz:

arr = ["a", "b", "c"].select { |i| i == "b" }.map { |i| "#{i}!" } 
=> ["b!"] 

Wewnątrz bloku select należy określić warunki element do „wybranych”. To zwróci tablicę. Możesz wywołać "map" w wynikowej tablicy, aby dołączyć do niej wykrzyknik.