2010-02-11 13 views
35

Mam dwie tablice muszę scalić, a przy użyciu operatora Unii (|) jest PAINFULLY powolny .. czy istnieją inne sposoby do osiągnięcia scalania tablicy?Array Merge (Union)

Ponadto tablice są wypełniane obiektami, a nie ciągami.

Przykładem obiektów w tablicy

#<Article 
id: 1, 
xml_document_id: 1, 
source: "<article><domain>events.waikato.ac</domain><excerpt...", 
created_at: "2010-02-11 01:32:46", 
updated_at: "2010-02-11 01:41:28" 
> 

Jeżeli źródło to fragment XML.

EDIT

Niestety! Przez "scalenie" rozumiem, że nie muszę wstawiać duplikatów.

A => [1, 2, 3, 4, 5] 
B => [3, 4, 5, 6, 7] 
A.magic_merge(B) #=> [1, 2, 3, 4, 5, 6, 7] 

Zrozumienie, że liczby całkowite są rzeczywiście Artykuł obiekty, a operator Unia wydaje się zawsze

+0

Co dokładnie masz na myśli przez scalenie? Jak poradzisz sobie z tożsamością obiektów i różnicami w zawartości? –

+0

Nie wiem, przypuszczam, że to, czego szukam, jest sposobem na "scalenie", a posiadanie operatora porównania, aby porównać obiekty, przychodzi czas scalania. – Rabbott

+0

Ryan: jest to interesujące pytanie, ale uważam, że uzyskasz więcej/lepszą odpowiedź na tej stronie, jeśli zagłosujesz na odpowiedzi, które są pomocne i przyjmiesz najlepszą odpowiedź na każde z pytań. Jest to podstawowa waluta Stack Overflow, a ludzie nieufnie pomagają komuś, gdy widzą, że jedyna akceptowana przez nich odpowiedź jest ich własna. –

Odpowiedz

61

Oto skrypt, w którym porównano dwie techniki łączenia: za pomocą operatora potoku (a1 | a2) i za pomocą konkatenacji-i-uniq ((a1 + a2).uniq). Dwa dodatkowe benchmarki dają czas indywidualny i niepowtarzalny.

require 'benchmark' 

a1 = []; a2 = [] 
[a1, a2].each do |a| 
    1000000.times { a << rand(999999) } 
end 

puts "Merge with pipe:" 
puts Benchmark.measure { a1 | a2 } 

puts "Merge with concat and uniq:" 
puts Benchmark.measure { (a1 + a2).uniq } 

puts "Concat only:" 
puts Benchmark.measure { a1 + a2 } 

puts "Uniq only:" 
b = a1 + a2 
puts Benchmark.measure { b.uniq } 

Na moim komputerze (Ubuntu Karmic, Ruby 1.8.7), mam wyjścia tak:

Merge with pipe: 
    1.000000 0.030000 1.030000 ( 1.020562) 
Merge with concat and uniq: 
    1.070000 0.000000 1.070000 ( 1.071448) 
Concat only: 
    0.010000 0.000000 0.010000 ( 0.005888) 
Uniq only: 
    0.980000 0.000000 0.980000 ( 0.981700) 

który pokazuje, że te dwie techniki są bardzo podobne pod względem szybkości i że jest uniq większy składnik operacji. Ma to sens intuicyjnie, będąc O (n) (w najlepszym wypadku), podczas gdy proste konkatenacja to O (1).

Tak więc, jeśli naprawdę chcesz przyspieszyć, musisz sprawdzić, w jaki sposób jest wdrażany operator <=> dla obiektów w twoich tablicach. Uważam, że większość czasu spędza się porównując obiekty, aby zapewnić nierówność pomiędzy dowolną parą w ostatecznej tablicy.

+0

Więc operator rur będzie korzystać z operatora <=>, jeśli po prostu przeładować to? Nie jestem pewien, czy będę w stanie go przyspieszyć, ponieważ widzisz, że obiekt, który porównywa jest raczej prosty, ale może przyspieszy go, gdy tylko porównasz kolumnę źródłową zamiast wszystkich 5 kolumn ... zobaczymy. Świetna odpowiedź! – Rabbott

+1

Naucz człowieka łowić ryby ... Świetna odpowiedź. –

1

Stosując metodę Array#concat prawdopodobnie będzie dużo szybciej, według moich wstępnych wskaźników z wykorzystaniem Ruby 1.8. 7:

require 'benchmark' 

def reset_arrays! 
    @array1 = [] 
    @array2 = [] 

    [@array1, @array2].each do |array| 
    10000.times { array << ActiveSupport::SecureRandom.hex } 
    end 
end 

reset_arrays! && puts(Benchmark.measure { @array1 | @array2 }) 
# => 0.030000 0.000000 0.030000 ( 0.026677) 

reset_arrays! && puts(Benchmark.measure { @array1.concat(@array2) }) 
# => 0.000000 0.000000 0.000000 ( 0.000122) 
+1

Co robi concat z duplikatami? – Rabbott

+0

Zachowuje je, ale możesz zrobić "Array # uniq", aby pozbyć się dupków. –

+0

Ok, będę musiał przetestować concat vs +, aby zobaczyć, który jest szybszy – Rabbott

1

Spróbuj i zobacz, czy to szybciej

a = [1,2,3,3,2] 
b = [1,2,3,4,3,2,5,7] 
(a+b).uniq 
7

Czy chcesz, aby pozycje były w określonej kolejności w tablicach? Jeśli nie, możesz sprawdzić, czy użycie numeru Set s przyspiesza działanie.

Aktualizacja

Dodawanie do innego kodu odpowiadającego za:

require "set" 
require "benchmark" 

a1 = []; a2 = [] 
[a1, a2].each do |a| 
    1000000.times { a << rand(999999) } 
end 

s1, s2 = Set.new, Set.new 

[s1, s2].each do |s| 
    1000000.times { s << rand(999999) } 
end 

puts "Merge with pipe:" 
puts Benchmark.measure { a1 | a2 } 

puts "Merge with concat and uniq:" 
puts Benchmark.measure { (a1 + a2).uniq } 

puts "Concat only:" 
puts Benchmark.measure { a1 + a2 } 

puts "Uniq only:" 
b = a1 + a2 
puts Benchmark.measure { b.uniq } 

puts "Using sets" 
puts Benchmark.measure {s1 + s2} 

puts "Starting with arrays, but using sets" 
puts Benchmark.measure {s3, s4 = [a1, a2].map{|a| Set.new(a)} ; (s3 + s4)} 

daje (Ruby 1.8.7 (2008-08-11 patchlevel 72) [Universal-darwin10.0])

Merge with pipe: 
    1.320000 0.040000 1.360000 ( 1.349563) 
Merge with concat and uniq: 
    1.480000 0.030000 1.510000 ( 1.512295) 
Concat only: 
    0.010000 0.000000 0.010000 ( 0.019812) 
Uniq only: 
    1.460000 0.020000 1.480000 ( 1.486857) 
Using sets 
    0.310000 0.010000 0.320000 ( 0.321982) 
Starting with arrays, but using sets 
    2.340000 0.050000 2.390000 ( 2.384066) 

Sugeruje, że zestawy mogą być lub nie mogą być szybsze, w zależności od okoliczności (wiele scaleń lub nie wiele połączeń).

+0

Kolejność nie ma znaczenia, ponieważ muszę przetasować tablicę po jej scaleniu - ale scalanie przy użyciu zestawów jest wolniejsze niż połączenie za pomocą narzędzia concat, które jest najszybszą metodą, jaką udało nam się opracować do tej pory. I muszę użyć tablic, ponieważ scalam zestaw danych ActiveRecord z db, który jest tablicą. (Myślę) – Rabbott

+0

Jeśli obie tablice były pierwotnie jednostkami bazy danych, a prędkość jest twoją obawą, możesz rozważyć scalenie ich w bazie danych przed konstruowanie obiektów. Naprawdę nie mogę podać przykładu (ani ocenić wykonalności tego, co właśnie napisałem :-)), nie znając szczegółów schematu, ale to może być warte poznania. – chesterbr

+0

Inna sprawa: to, co dostajesz z bazy danych, nie jest tak naprawdę tablicą, ale "ActiveRecord :: Result" (http://api.rubyonrails.org/classes/ActiveRecord/Result.html). Jak widać w tych dokumentach, ta klasa zawiera "Enumerable", dlatego przez większość czasu "niszczy" jak Array (a także daje wygodną - i dość często niejawnie nazywaną - 'to_a', która ją konwertuje do rzeczywistego 'Tablica'). – chesterbr