2010-10-04 13 views
17

Kanoniczna Array przykład różnica w Ruby jest:Ruby tablica odejmowanie bez usuwania elementów więcej niż raz

[ 1, 1, 2, 2, 3, 3, 4, 5 ] - [ 1, 2, 4 ] #=> [ 3, 3, 5 ] 

Jaki jest najlepszy sposób, aby uzyskać następujące zachowanie w zamian?

[ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) #=> [ 1, 2, 3, 3, 5 ] 

Oznacza to, że tylko pierwsza instancja każdego pasującego elementu w drugiej macierzy jest usuwana z pierwszej tablicy.

Odpowiedz

11

wartości Odejmowanie tyle razy, ile oni pojawiają się w drugiej tablicy, lub dowolny Enumerable:

class Array 
    # Subtract each passed value once: 
    # %w(1 2 3 1).subtract_once %w(1 1 2) # => ["3"] 
    # [ 1, 1, 2, 2, 3, 3, 4, 5 ].subtract_once([ 1, 2, 4 ]) => [1, 2, 3, 3, 5] 
    # Time complexity of O(n + m) 
    def subtract_once(values) 
    counts = values.inject(Hash.new(0)) { |h, v| h[v] += 1; h } 
    reject { |e| counts[e] -= 1 unless counts[e].zero? } 
    end 

Odejmij każdy wyjątkowy wartość raz:

require 'set' 
class Array 
    # Subtract each unique value once: 
    # %w(1 2 2).subtract_once_uniq %w(1 2 2) # => [2] 
    # Time complexity of O((n + m) * log m) 
    def subtract_once_uniq(values) 
    # note that set is implemented 
    values_set = Set.new values.to_a 
    reject { |e| values_set.delete(e) if values_set.include?(e) } 
    end 
end 
+1

Mam zamiar zaakceptować to, ale byłoby miło, gdyby argument mógł zawierać zduplikowane wartości, które zostaną zastosowane po kolei (zostaną zgniecione przez konwersję do Set). Nie wiesz, jak zachować duplikaty, zachowując jednocześnie wydajność. (Chciałem również zaakceptować tablicę, a nie wartości jako oddzielne argumenty, ale to łatwa zmiana). –

+0

Zaktualizowałem odpowiedź wersją, która stosuje duplikaty tyle razy, ile jest obecnych w drugiej tablicy. – glebm

+1

@ genialny człowiek genialny rozwiązanie! To bardzo mi pomogło. Napisałeś to tylko po to, by odpowiedzieć na to pytanie? Wielkie dzięki. –

8

To wszystko, co mogę myśleć o tak daleko:

[1, 2, 4].each { |x| ary.delete_at ary.index(x) } 
+0

to może się nieco powolny, jeśli 'm' (wielkość [1,2,4]) jest duża – glebm

+1

to rozwiązanie działa tylko wtedy, gdy każdy element tablicy [1,2,4] jest obecny w 'ary'. W przeciwnym razie indeks elementu jest zerowy. Wewnątrz może być coś takiego: 'i = ary.index (x); ary.delete_at (i) if i ' –

9
class Array 
    def subtract_once(b) 
    h = b.inject({}) {|memo, v| 
     memo[v] ||= 0; memo[v] += 1; memo 
    } 
    reject { |e| h.include?(e) && (h[e] -= 1) >= 0 } 
    end 
end 

Wierzę, że robi to, co chcę. Wiele dzięki @glebm

+0

Nie widziałem tego - jest dobrze. – glebm

+1

Sugestie: Wewnątrz iniekcji: 'memo [v] || = 0; memo [v] + = 1; note " Wewnątrz odrzuceń: ' h.include? (e) &&! (h [e] - = 1) .zero? ' – glebm

1

Podobne do odpowiedzi @Jeremy Ruten, ale księgowy dla faktu, że niektóre elementy mogą nie być obecne:

# remove each element of y from x exactly once 
def array_difference(x, y) 
    ret = x.dup 
    y.each do |element| 
    if index = ret.index(element) 
     ret.delete_at(index) 
    end 
    end 
    ret 
end 

Ta odpowiedź również nie zmodyfikuje oryginalną tablicę jak to działa, więc :

x = [1,2,3] 
y = [3,4,5] 
z = array_difference(x, y) # => [1,2] 
x == [1,2,3]    # => [1,2,3] 
Powiązane problemy