code_id_dict

[code=ruby]class Account < ActiveRecord::Base

class << self
def code_id_dict
codes = {}
connection.select_all(“select id, account_code from accounts”).each do |r|
next if r[“account_code”] == “”
if codes[r[“account_code”]]
codes[r[“account_code”]] = [ codes[r[“account_code”]] ] unless codes[r[“account_code”]].is_a? Array
codes[r[“account_code”]] << r[“id”].to_i
else
codes[r[“account_code”]] = r[“id”].to_i
end
end
codes
end
end

end[/code]
Czy ten kod można poprawić ?

w selecie dodac where ktory wyrzuci account_code rozne od null i pewnie tez rozne od pustego stringa. Co do dalszej czesci to tak na oko to chyba chodzi o cos takiego: ?

>> [{:code => 1, :id=> 3}, {:code => 1, :id=>4}, {:code => 2, :id => 5}].group_by{|x| x[:code]}.to_hash.each_pair{|k,v| v.map!{|e| e[:id]}} => {1=>[3, 4], 2=>[5]}

Jeśli zaś to wszyskto potrzebne jest Ci tylko po to by dalej tego użyć w innym zapytaniu Mysql to możesz zrobić group by account_code a w selecie uzyc group_concat dla id.

Dodam tylko, że ta metoda zwraca takiego hash’a (przykładowe dane):

{ "SPRAYCO"=>13594, "SCDIST"=>[12157, 12158], "_U0VL78B"=>13157, "STOCK"=>[12179, 12180, 12491] ... }

class Account < ActiveRecord::Base def self.code_id_dict returning Account.all(:select => "id, account_code").reject {|a| a.account_code.blank? }.group_by(&:account_code) do |dict| dict.values.map! {|set| set.map!(&:id)} end end end
a coś takiego? :slight_smile: reject można zamienić na parametr :conditions w Account.all :wink:

Wersja która zwraca narazie tylko same tablice.

Account.all.inject({}) { |hash,account| hash[account.code] ||= []; hash[account.code] << account.id; hash }

Wynik.

{"SCDIST"=>[4, 5, 9, 10], "STOCK"=>[1, 2, 3, 6, 7, 8], "SPRAYCO"=>[11], "_U0VL78B"=>[12]}

[quote=sevos]class Account < ActiveRecord::Base def self.code_id_dict returning Account.all(:select => "id, account_code").reject {|a| a.account_code.blank? }.group_by(&:account_code) do |dict| dict.values.map! {|set| set.map!(&:id)} end end end
a coś takiego? :slight_smile: reject można zamienić na parametr :conditions w Account.all ;)[/quote]
Bardzo ładnie tylko w przypadku 1 elementu zwraca 1-elementową tablicę zamiast tego elementu (spójrz na przykładowe dane powyżej). Idea jest taka, żeby poprawić tylko tą metodę a nie musieć przepisywać wszystkich miejsc które z niej korzystają.

Account.all.inject({}) do |hash, account| if hash[account.code] if hash[account.code].is_a?(Array) hash[account.code] << account.id else hash[account.code] = [hash[account.code], account.id] end else hash[account.code] = account.id end hash end
Wersja finalna :wink:

{"SCDIST"=>[4, 5, 9, 10], "STOCK"=>[1, 2, 3, 6, 7, 8], "SPRAYCO"=>11, "_U0VL78B"=>12}

class Account < ActiveRecord::Base def self.code_id_dict returning Account.all(:select => "id, account_code").reject {|a| a.account_code.blank? }.group_by(&:account_code) do |dict| dict.values.map! {|set| set.map!(&:id)} dict.values.map! {|set| set.length > 1 ? set : set.first} end end end

Jeszcze taka mała ciekawostka:

require 'benchmark' Benchmark.bm do |x| x.report("inject") do Account.all.inject({}) do |hash, account| if hash[account.code] if hash[account.code].is_a?(Array) hash[account.code] << account.id else hash[account.code] = [hash[account.code], account.id] end else hash[account.code] = account.id end hash end end x.report("map ") do returning Account.all(:select => "id, code").reject {|a| a.code.blank? }.group_by(&:code) do |dict| dict.values.map! {|set| set.map!(&:id)} dict.values.map! {|set| set.length > 1 ? set : set.first} end end end
Wynik dla ree 1.8.7 2010-01, 10012 Accounts

user system total real inject 9.170000 0.230000 9.400000 ( 9.418468) map 6.990000 0.150000 7.140000 ( 7.148616)

Aż nie mogłem uwierzyć w pierwszy wynik. Inject jest jednak szybsze, ale nieznacznie. Duże znaczenie ma też ilość danych.

require 'benchmark' Benchmark.bm do |x| x.report("full inject ") do Account.all.inject({}) do |hash, account| if hash[account.code] if hash[account.code].is_a?(Array) hash[account.code] << account.id else hash[account.code] = [hash[account.code], account.id] end else hash[account.code] = account.id end hash end end x.report("inject ") do Account.all(:select => "id, code").inject({}) do |hash, account| if hash[account.code] if hash[account.code].is_a?(Array) hash[account.code] << account.id else hash[account.code] = [hash[account.code], account.id] end else hash[account.code] = account.id end hash end end x.report("full map ") do returning Account.all.reject {|a| a.code.blank? }.group_by(&:code) do |dict| dict.values.map! {|set| set.map!(&:id)} dict.values.map! {|set| set.length > 1 ? set : set.first} end end x.report("map ") do returning Account.all(:select => "id, code").reject {|a| a.code.blank? }.group_by(&:code) do |dict| dict.values.map! {|set| set.map!(&:id)} dict.values.map! {|set| set.length > 1 ? set : set.first} end end end

user system total real full inject 9.220000 0.230000 9.450000 ( 9.612165) inject 6.820000 0.140000 6.960000 ( 6.997783) full map 9.610000 0.190000 9.800000 ( 9.859938) map 7.190000 0.160000 7.350000 ( 7.406834)

Straszne wolne masz te wyniki… Zakladam ze robisz to na jakiejs przyzwoitej maszynie. Ja na zwykłym stacjonarnym komputerze, przy 22K kont i kodzie dostosowanym do nazwy kolumn wyonuję wersję “map” w 1.2 sekundy na ruby 1.8.6 a na REE 1.8.7 w 0.6 sekundy. Zdumiewające, że na 2 razy więcej danych mam 10 razy szybsze wyniki :-). Jak znam życie to pewnie kwestia tego, że ja mam tylko kilka rodzajów kodów a ty dużo więcej…

No byc moze, jeszcze jade na sqlite wiec tutaj tez nie mozna sie za wiele spodziewac po silniku.

[code]>> Account.count(:group => :code)
=> #<OrderedHash {“twattlle”=>2501, “oculist”=>2513, “murderer”=>2479, “magnifico”=>2461, “informer”=>2515, “homonymous”=>2540, “rustler”=>2474, “pyromaniac”=>2472, “machiavel”=>2513, “warder”=>2533, “sniggle”=>2557, “sirene”=>2475, “STOCK”=>6, “SCDIST”=>4, “denunciatory”=>2572, “althorn”=>2480, “SPRAYCO”=>1, “theologist”=>2537, “cerate”=>2504, “swop”=>2400, “interminable”=>2491, “especially”=>2533, “cassino”=>2552, “unobtainable”=>2489, “promptbook”=>2544, “lordship”=>2518, “dissatisfaction”=>2448, “realism”=>2525, “forged”=>2441, “fascination”=>2530, “vacatur”=>2432, “rutilant”=>2521, “proprietress”=>2484, “condescending”=>2471, “_U0VL78B”=>1, “telegony”=>2488, “senses”=>2561, “reptatorial”=>2487, “disenchant”=>2440, “annulet”=>2475, “stiffnecked”=>2504, “sharer”=>2518, “duodecimal”=>2540, “decalogue”=>2482}>

Account.count
=> 100012[/code]

Tu masz 100K użytkowników a w poprzednim poście 10K duża różnica :slight_smile: Ja jechałem na mysqlu.

Jedno zero mi wcielo :wink: Tak czy inaczej odrazu powinenm przetestowac wszystko przez data = Account.all i dopiero na data robic benchmark, wtedy widac wyrazniej.

Męcze kod dalej :wink: Po wrzuceniu tych 100 tys rekordów do hash’a i zamienieniu całości na odwołaniiu do hash. (wszystkie atrybuty i Symbol#to_proc wylecialy) metoda z kilkoma map zrownala do metody z inject. Wyglada na to ze operowania na obiektach daje w kosc. Prawdopodobnie to wina wiekszej ilosci duck_typing i Symbol#to_proc w przypadku pracy na obiektach. Wszystko w zasadzie w celu przetestowania nowego MacRuby, Ruby 1.9 i Rubinius’a

[code]ree 1.8.7 2010-01
user system total real
inject 0.750000 0.000000 0.750000 ( 0.745395)
map 0.780000 0.000000 0.780000 ( 0.785857)

rbx 1.0rc2
user system total real
inject 3.127727 0.000000 3.127727 ( 3.127432)
map 3.956688 0.000000 3.956688 ( 3.956730)

ruby 1.9.2 preview
user system total real
inject 0.200000 0.020000 0.220000 ( 0.215817)
map 0.680000 0.000000 0.680000 ( 0.684270)[/code]

require 'rubygems' require 'benchmark' require 'yaml' require 'active_support' Benchmark.bm do |x| data = YAML.load_file("data.yaml") x.report("inject") do $data1 = data.inject({}) do |hash, account| if hash[account["code"]] if hash[account["code"]].is_a?(Array) hash[account["code"]] << account["id"] else hash[account["code"]] = [hash[account["code"]], account["id"]] end else hash[account["code"]] = account["id"] end hash end end x.report("map ") do $data2 = returning data.reject {|a| a["code"].blank? }.group_by { |a| a["code"] } do |dict| dict.values.map! {|set| set.map! { |a| a["id"] }} dict.values.map! {|set| set.length > 1 ? set : set.first} end end end
Jeśli ktoś chciałby potestować sobie we własnym zakresie to tu jest data.yaml

http://b3.s3.p.quickshareit.com/files/datagz98550.gz

P.S. MacRuby niestety wymiekl, albo raczje ja wymieklem po 10 minutach mielenia obu rdzeni na samym ladowaniu YAML

A ja bym postawił na złożoność obliczeniową :slight_smile: W pierwszym algorytmie masz jedno przejscie w którym robisz wszystko na bieżąco, a w drugim kilka razy iterujesz po różnych danych i zamieniasz je z jednych na drugie.