Сортировать хеш по ключу, вернуть хеш в Ruby

258

Будет ли это лучший способ отсортировать хэш и вернуть объект Hash (вместо Array):

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
# => {"a"=>1, "c"=>3, "b"=>2, "d"=>4}

Hash[h.sort]
# => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
Винсент
источник
9
Я не уверен, что есть много преимуществ в сортировке хэша, если вы не используете eachили не выполняете each_pairего итерацию. Даже тогда я, вероятно, все еще собираю ключи, сортирую их, а затем перебираю их, получая значения по мере необходимости. Это гарантирует, что код будет работать правильно на старых Ruby.
Жестянщик
Имеет смысл и в ruby ​​1.9. У меня была коллекция встреч, сгруппированных по датам (в виде ключей) из базы данных, и я вручную перебрал рубин. Например. {"2012-09-22": [...], "2012-09-30": [...], "2012-10-12": [...]}
Adit Saxena
Да, я считаю, что ваш хэш-процесс [h.sort] более эффективен, чем сортировка ключей, а затем повторный доступ к хешу через отсортированные ключи.
Дуглас
3
У вас было несколько лет, чтобы подумать о своем решении, готовы ли вы принять ответ? ;-)
Марк Томас

Ответы:

219

В Ruby 2.1 это просто:

h.sort.to_h
Марк Томас
источник
@zachaysan, но это работает: h.sort{|a,z|a<=>z}.to_h(протестировано 2.1.10, 2.3.3)
whitehat101
@ whitehat101 Вы правы. У меня была ошибка ( aэто массив, а не только ключ). Я удалил свой комментарий.
zachaysan
Только в случае , если кто - то ищет способ сортировки массива хэшей, это будет делать трюк (где Н массив): h.map(&:sort).map(&:to_h).
JM Janzen
82

Примечание: Ruby> = 1.9.2 имеет хэш, сохраняющий порядок: ключи порядка вставляются в порядке их перечисления. Нижеследующее относится к более старым версиям или к обратно совместимому коду.

Не существует понятия отсортированного хэша. Так что нет, то, что вы делаете, не правильно.

Если вы хотите, чтобы он был отсортирован для отображения, верните строку:

"{" + h.sort.map{|k,v| "#{k.inspect}=>#{v.inspect}"}.join(", ") + "}"

или, если вы хотите, чтобы ключи были в порядке:

h.keys.sort

или, если вы хотите получить доступ к элементам по порядку:

h.sort.map do |key,value|
  # keys will arrive in order to this block, with their associated value.
end

но в целом нет смысла говорить о отсортированном хэше. Из документов : «Порядок, в котором вы пересекаете хеш по ключу или значению, может показаться произвольным и обычно не будет в порядке вставки». Поэтому вставка ключей в определенном порядке в хэш не поможет.

Питер
источник
Это верно. Я бы предложил использовать гем RBtree для получения упорядоченного набора функций в ruby.
Аарон Скраггс
26
Начиная с версии 1.9.2 порядок вставки хеша будет сохранен. См. Redmine.ruby-lang.org/issues/show/994
Дэвид
4
Msgstr "Порядок вставки 1.9.2 будет сохранен."
Жестянщик
2
Повторяю мой первый комментарий (чувствуя себя смешно): Например, полагаясь на порядок хэшей, можно было бы молча и непредсказуемо сломаться для версий Ruby старше 1.9.2.
Джо Лисс
5
Как этот ответ получил около 20 + 1, не ответив ни на одну из двух частей вопроса ОП? «1) Будет ли это (пример OP) лучшим способом отсортировать хеш, 2) и вернуть объект Hash»? Я не завидую +1: просто после прочтения ответа у меня все еще остаются оригинальные вопросы. Также, если дело в том, что нет такой вещи, как отсортированный хэш, посмотрите на комментарии для выбранного ответа на этот вопрос stackoverflow.com/questions/489139/…
jj_
64

Я всегда использовал sort_by. Вам нужно обернуть #sort_byвывод, Hash[]чтобы он выводил хеш, иначе он выводит массив массивов. В качестве альтернативы, для этого вы можете запустить #to_hметод на массиве кортежей, чтобы преобразовать их в k=>vструктуру (хеш).

hsh ={"a" => 1000, "b" => 10, "c" => 200000}
Hash[hsh.sort_by{|k,v| v}] #or hsh.sort_by{|k,v| v}.to_h

Аналогичный вопрос есть в разделе « Как отсортировать хэш Ruby по числовому значению? ».

boulder_ruby
источник
8
использование sort_byхэша вернет массив. Вы должны будете отобразить это как хэш снова. Hash[hsh.sort_by{|k,v| v}]
Stevenspiel
1
да, класс перечислителя интерпретирует хэши как массивы, как мне кажется
boulder_ruby
3
Правильно, сортировка по значениям: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
Кэри Свовеланд
1
Обратите внимание, что вызов to_hподдерживается только в Ruby 2.1.0+
Phrogz
1
в комментарии есть опечатка, исправление:sort_by{|k,v| v}.to_h)
джиттер
13

Нет, это не так (Ruby 1.9.x)

require 'benchmark'

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
many = 100_000

Benchmark.bm do |b|
  GC.start

  b.report("hash sort") do
    many.times do
      Hash[h.sort]
    end
  end

  GC.start

  b.report("keys sort") do
    many.times do
      nh = {}
      h.keys.sort.each do |k|
        nh[k] = h[k]
      end
    end
  end
end

       user     system      total        real
hash sort  0.400000   0.000000   0.400000 (  0.405588)
keys sort  0.250000   0.010000   0.260000 (  0.260303)

Для больших хешей разница вырастет до 10х и более

fl00r
источник
12

Вы дали лучший ответ себе в ОП: Hash[h.sort]Если вы стремитесь к большему количеству возможностей, вот модификация исходного хеша для его сортировки:

h.keys.sort.each { |k| h[k] = h.delete k }
Борис Стиницкий
источник
1
Для любопытных, это работает немного быстрее, чем «сортировка ключей» в stackoverflow.com/a/17331221/737303 на Ruby 1.9.3.
азот
9

Сортировать хеш по ключу , вернуть хеш в Ruby

С деструктуризацией и хэш-сортировкой

hash.sort { |(ak, _), (bk, _)| ak <=> bk }.to_h

Перечислимых # sort_by

hash.sort_by { |k, v| k }.to_h

Hash # sort с поведением по умолчанию

h = { "b" => 2, "c" => 1, "a" => 3  }
h.sort         # e.g. ["a", 20] <=> ["b", 30]
hash.sort.to_h #=> { "a" => 3, "b" => 2, "c" => 1 }

Примечание: <Ruby 2.1

array = [["key", "value"]] 
hash  = Hash[array]
hash #=> {"key"=>"value"}

Примечание:> Ruby 2.1

[["key", "value"]].to_h #=> {"key"=>"value"}
Moriarty
источник
1
если не используете, vвы должны префикс подчеркиванияhash.sort_by { |k, _v| k }.to_h
silva96
5

ActiveSupport :: OrderedHash - это еще один вариант, если вы не хотите использовать ruby ​​1.9.2 или откатить собственные обходные пути.

отшельник
источник
Ссылка не работает
Снейк Сандерс
3
Я исправил ссылку, указывающую на Google, на случай, если какой-нибудь историк захочет исследовать, как это могло быть сделано в прошлом. Но любой, кто придет к этому сейчас, должен использовать более свежую версию Ruby.
Отшельник
0
@ordered = {}
@unordered.keys.sort.each do |key|
  @ordered[key] = @unordered[key]
end
beornborn
источник
4
Вот как бы вы это сделали, если бы у ruby ​​не было таких методов, как Hash # sort_by
boulder_ruby
0

У меня была такая же проблема (мне пришлось отсортировать оборудование по имени), и я решил так:

<% @equipments.sort.each do |name, quantity| %>
...
<% end %>

@equipments - это хеш, который я строю на своей модели и возвращаю на свой контроллер. Если вы вызываете .sort, он сортирует хеш по значению ключа.

Габриэль Мескита
источник
-3

Мне понравилось решение в предыдущем посте.

Я сделал мини-класс, назвал это class AlphabeticalHash. Она также имеет метод , называемый ap, который принимает один аргумент, Hash, в качестве входных данных: ap variable. Сродни пп ( pp variable)

Но он будет (пытаться и) печатать в алфавитном списке (его ключи). Не знаю, если кто-то еще хочет использовать это, он доступен как драгоценный камень, вы можете установить его так:gem install alphabetical_hash

Для меня это достаточно просто. Если другим понадобится больше функциональности, дайте мне знать, я включу их в жемчужину.

РЕДАКТИРОВАТЬ: Кредит идет на Питера , который дал мне идею. :)

shevy
источник