2014-06-24 8 views
7

Czy Enumerable#group_by zachowuje oryginalne zamówienie w ramach każdej wartości? Kiedy dostaję to:Czy grupa enumerable's group_by zachowuje zamówienie Enumerable?

[1, 2, 3, 4, 5].group_by{|i| i % 2} 
# => {1=>[1, 3, 5], 0=>[2, 4]} 

jest to zagwarantowane, że, na przykład, tablica [1, 3, 5] zawiera elementy w tej kolejności, a nie na przykład [3, 1, 5]?

Czy istnieje jakiś opis dotyczący tego punktu?

Nie mówię o kolejności między kluczami 1 i 0. To inna sprawa.

+0

'Enumerable' używa' each' przemierzać kolekcja. Zmiana kolejności wymagałaby dodatkowego wysiłku. – Stefan

+0

Ale wcześniej dowiedziałem się, że reminescent "Enumerable # sort" nie jest stabilny. Więc nie mogłem być tego pewien. – sawa

Odpowiedz

11

Tak, Enumerable#group_by zachowuje kolejność wprowadzania.

Oto wdrożenie tej metody w MRI, z https://github.com/ruby/ruby/blob/trunk/enum.c:

static VALUE 
enum_group_by(VALUE obj) 
{ 
    VALUE hash; 

    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size); 

    hash = rb_hash_new(); 
    rb_block_call(obj, id_each, 0, 0, group_by_i, hash); 
    OBJ_INFECT(hash, obj); 

    return hash; 
} 

static VALUE 
group_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash)) 
{ 
    VALUE group; 
    VALUE values; 

    ENUM_WANT_SVALUE(); 

    group = rb_yield(i); 
    values = rb_hash_aref(hash, group); 
    if (!RB_TYPE_P(values, T_ARRAY)) { 
     values = rb_ary_new3(1, i); 
     rb_hash_aset(hash, group, values); 
    } 
    else { 
     rb_ary_push(values, i); 
    } 
    return Qnil; 
} 

enum_group_by rozmowy group_by_i na każdej tablicy (obj) pierwiastków w kolejności. group_by_i tworzy tablicę jednoelementową (rb_ary_new3(1, i)) przy pierwszym napotkaniu grupy i przesuwa dalej do tablicy (rb_ary_push(values, i)). Tak więc zamówienie wejściowe zostanie zachowane.

Ponadto wymaga tego RubySpec. Od https://github.com/rubyspec/rubyspec/blob/master/core/enumerable/group_by_spec.rb:

it "returns a hash with values grouped according to the block" do 
    e = EnumerableSpecs::Numerous.new("foo", "bar", "baz") 
    h = e.group_by { |word| word[0..0].to_sym } 
    h.should == { :f => ["foo"], :b => ["bar", "baz"]} 
end 
+0

To zależy od implementacji "każdego", który jest mapowany przez 'id_each' w wywołaniu' rb_block_call' – Matt

4

Dokładniej Enumerable rozmowy each więc to zależy od tego jak each jest realizowany oraz czy each plony elementy w oryginalnej kolejności:

class ReverseArray < Array 
    def each(&block) 
    reverse_each(&block) 
    end 
end 

array = ReverseArray.new([1,2,3,4]) 
#=> [1, 2, 3, 4] 

array.group_by { |i| i % 2 } 
#=> {0=>[4, 2], 1=>[3, 1]} 
+0

Dzięki za wyjaśnienie, że wywołuje "każdy". "reverse_each" nie miałoby sensu, gdyby kolejność "każdego" była dowolna. Czy istnienie 'reverse_each' nie oznacza, że' each' zachowuje kolejność (chyba że zostanie nadpisana przez użytkownika)? – sawa

+1

To tylko przykład. "Array # each" oczywiście daje elementy [w tej samej kolejności] (http://www.ruby-doc.org/core-2.1.2/Array.html#class-Array-label-Iterating+over+Arays): * "W przypadku każdego Arraya wszystkie elementy w instancji Array są kolejno dostarczane do dostarczonego bloku." * Ale inne klasy mogłyby zaimplementować to inaczej, na przykład 'Hash' w Ruby 1.8. Dlatego 'group_by' nie może zagwarantować żadnego zamówienia, to zależy całkowicie od" każdego ". – Stefan

Powiązane problemy