È sort
in Ruby stable? Cioè, per gli elementi che sono in un pareggio per sort
, l’ordine relativo tra loro è preservato dall’ordine originale? Ad esempio, dato:
a = [ {id: :a, int: 3}, {id: :b, int: 1}, {id: :c, int: 2}, {id: :d, int: 0}, {id: :e, int: 1}, {id: :f, int: 0}, {id: :g, int: 1}, {id: :h, int: 2}, ]
è garantito che otteniamo sempre per
a.sort_by{|h| h[:int]}
il seguente
[ {id: :d, int: 0}, {id: :f, int: 0}, {id: :b, int: 1}, {id: :e, int: 1}, {id: :g, int: 1}, {id: :c, int: 2}, {id: :h, int: 2}, {id: :a, int: 3}, ]
senza alcuna variazione per l’ordine relativo tra gli elementi con il valore :id
:d
:f
, e tra :b
:g
e,: :g
, e tra :c
:h
? Se questo è il caso, dove nella documentazione è descritto?
Questa domanda può o non può avere connessione con questa domanda .
Sia l’ ordinamento e l’ ordinamento della MRI sono instabili . Qualche tempo fa c’era una richiesta di renderli stabili, ma è stata respinta. Il motivo: Ruby utilizza un algoritmo di quicksort sul posto , che offre prestazioni migliori se non è richiesta la stabilità. Nota che puoi ancora implementare metodi stabili da quelli instabili:
module Enumerable def stable_sort sort_by.with_index { |x, idx| [x, idx] } end def stable_sort_by sort_by.with_index { |x, idx| [yield(x), idx] } end end
No, l’ordinamento predefinito di ruby non è stabile.
Se vuoi un ordinamento stabile, questo dovrebbe funzionare. Probabilmente vorresti creare un metodo per questo se lo userai spesso.
a.each_with_index.sort_by {|h, idx| [h[:int], idx] }.map(&:first)
Fondamentalmente tiene traccia dell’indice di array originale di ogni elemento e lo usa come tie-breaker quando h[:int]
è lo stesso.
Maggiori informazioni, per i curiosi:
Per quanto ne so, l’uso dell’indice di array originale come tie-breaker è l’unico modo per garantire la stabilità quando si utilizza un ordinamento instabile. Gli attributi attuali (o altri dati) degli articoli non ti diranno il loro ordine originale.
Il tuo esempio è un po ‘forzato perché le chiavi :id
sono ordinate in ordine crescente nell’array originale. Supponiamo che l’array originale sia stato ordinato decrescente per :id
; vorresti che :id
nel risultato sia decrescente quando tie-break, in questo modo:
[ {:id=>:f, :int=>0}, {:id=>:d, :int=>0}, {:id=>:g, :int=>1}, {:id=>:e, :int=>1}, {:id=>:b, :int=>1}, {:id=>:h, :int=>2}, {:id=>:c, :int=>2}, {:id=>:a, :int=>3} ]
Usare l’indice originale gestirà anche questo.
Aggiornare:
Il suggerimento di Matz (vedi questa pagina ) è simile e potrebbe essere leggermente più efficiente di quanto sopra:
n = 0 ary.sort_by {|x| n+= 1; [x, n]}
Per alcune implementazioni di Ruby, sort è stabile, ma non devi dipendere da esso. La stabilità di Ruby è definita dall’implementazione.
La documentazione dice che non dovresti dipendere dal fatto che l’ordinamento sia stabile:
Il risultato non è garantito per essere stabile. Quando il confronto di due elementi restituisce 0, l’ordine degli elementi è imprevedibile.
Nota che questo non dice se l’ordinamento è stabile o meno. Dice solo che non è garantito che sia stabile. Qualsiasi implementazione data di Ruby potrebbe avere un ordinamento stabile e comunque essere coerente con la documentazione. Potrebbe anche avere un ordinamento instabile o cambiare se l’ordinamento è stabile in qualsiasi momento.
Questo codice di test viene stampato true
se l’ordinamento di Ruby è stabile o false
se non lo è:
Foo = Struct.new(:value, :original_order) do def <=>(foo) value <=> foo.value end end size = 1000 unsorted = size.times.map do |original_order| value = rand(size / 10) Foo.new(value, original_order) end sorted = unsorted.sort stably_sorted = unsorted.sort_by do |foo| [foo.value, foo.original_order] end p [RUBY_PLATFORM, RUBY_VERSION, RUBY_PATCHLEVEL, sorted == stably_sorted]
Ecco i risultati di tutti i Ruby che ho installato sulla mia macchina Linux:
["java", "1.8.7", 357, false] ["java", "1.9.3", 551, false] ["x86_64-linux", "1.8.7", 374, false] ["x86_64-linux", "1.8.7", 374, false] ["x86_64-linux", "1.8.7", 376, false] ["x86_64-linux", "1.9.3", 392, false] ["x86_64-linux", "1.9.3", 484, false] ["x86_64-linux", "1.9.3", 551, false] ["x86_64-linux", "2.0.0", 643, false] ["x86_64-linux", "2.0.0", 648, false] ["x86_64-linux", "2.1.0", 0, false] ["x86_64-linux", "2.1.10", 492, false] ["x86_64-linux", "2.1.1", 76, false] ["x86_64-linux", "2.1.2", 95, false] ["x86_64-linux", "2.1.3", 242, false] ["x86_64-linux", "2.1.4", 265, false] ["x86_64-linux", "2.1.5", 273, false] ["x86_64-linux", "2.1.6", 336, false] ["x86_64-linux", "2.1.7", 400, false] ["x86_64-linux", "2.1.8", 440, false] ["x86_64-linux", "2.1.9", 490, false] ["x86_64-linux", "2.2.0", 0, true] ["x86_64-linux", "2.2.1", 85, true] ["x86_64-linux", "2.2.2", 95, true] ["x86_64-linux", "2.2.3", 173, true] ["x86_64-linux", "2.2.4", 230, true] ["x86_64-linux", "2.2.5", 319, true] ["x86_64-linux", "2.2.6", 396, true] ["x86_64-linux", "2.3.0", 0, true] ["x86_64-linux", "2.3.1", 112, true] ["x86_64-linux", "2.3.2", 217, true] ["x86_64-linux", "2.3.3", 222, true] ["x86_64-linux", "2.4.0", 0, true] ["x86_64-linux", "2.4.0", -1, true] ["x86_64-linux", "2.4.0", -1, true] ["x86_64-linux", "2.4.0", -1, true] ["x86_64-linux", "2.4.0", -1, true] ["x86_64-linux", "2.4.1", 111, true]
Possiamo vedere che JRuby è instabile, e la risonanza magnetica prima della 2.2, su Linux, è instabile. MRI> = 2.2.0 è stabile (di nuovo, su Linux).
La piattaforma conta, però. Anche se il risultato sopra mostra che sort è stabile in MRI 2.4.1 su Linux, la stessa versione è instabile su Windows:
["x64-mingw32", "2.4.1", 111, false]
Anche all’interno di una singola versione di un’implementazione di Ruby, l’algoritmo di ordinamento può cambiare. La risonanza magnetica può utilizzare almeno tre diversi tipi. La routine di ordinamento viene selezionata al momento della compilazione utilizzando una serie di #ifdefs in util.c. Sembra che la risonanza magnetica abbia la possibilità di utilizzare i tipi da almeno due diverse librerie. Ha anche una sua implementazione.
Dato che l’ordinamento può essere stabile ma non può essere garantito che sia stabile, non scrivere codice che dipenda dal fatto che il tipo di Ruby sia stabile. Tale codice potrebbe interrompersi se utilizzato su una versione, un’implementazione o una piattaforma diversa.
personalmente non ci conterei. Come fare una cosa del genere:
a.sort {|a, b| s1 = a[:int] <=> b[:int]; s1 != 0 ? s1 : a[:id] <=> b[:id] }