Рубиновая сортировка по логическим и числовым - PullRequest
6 голосов
/ 03 ноября 2010

Я использую Ruby 1.8.7. У меня есть следующий массив хэшей. Сначала мне нужно отсортировать по логическому значению, но эти результаты также должны быть упорядочены в исходном порядке. Мне в основном нужно переместить все истинные хэши в верхнюю часть массива, но сохранить первоначальный порядок.

Любая помощь будет оценена!

array = [{:id => 1, :accepts => false}, 
         {:id => 2, :accepts => false}, 
         {:id => 3, :accepts => true}, 
         {:id => 4, :accepts => false}, 
         {:id => 5, :accepts => true}]

sorted = array.sort do |x, y|
  if x[:accepts] == y[:accepts]
    0
  elsif x[:accepts] == true
    -1
  elsif x[:accepts] == false
    1
  end
end

Этот сорт, который у меня получается:

5 - правда
3 - правда
2 - ложь
4 - ложь
1 - ложь

Мне это нужно, чтобы дать:

3 - правда
5 - правда
1 - ложь
2 - ложь
4 - ложь

Ответы [ 7 ]

14 голосов
/ 03 ноября 2010

Используйте sort_by для этих вещей, а не sort!

array.sort_by {|h| [h[:accepts] ? 0 : 1,h[:id]]}
8 голосов
/ 03 ноября 2010

Это делает работу:

array.sort{|a,b| (a[:accepts] == b[:accepts]) ? ((a[:id] < b[:id]) ? -1 : 1) : (a[:accepts] ? -1 : 1)}
1 голос
/ 04 ноября 2010

Что ж, из вашего вопроса я выводу, что вы действительно хотели сгруппировать результаты по значению :accepts и объединить оба набора результатов обратно в один массив.Мое решение этого было бы:

array.select {|where| where[:accepts] } | array.reject {|where| where[:accepts] }
# => [{:accepts=>true, :id=>3},
#     {:accepts=>true, :id=>5},
#     {:accepts=>false, :id=>1},
#     {:accepts=>false, :id=>2},
#     {:accepts=>false, :id=>4}]

Это будет поддерживать первоначальный порядок без каких-либо сортировок на клавише :id.Это означает, что вам не понадобится вспомогательный ключ для сохранения порядка, и вы можете сохранить порядок для результата независимо от передаваемых данных.

Это также может быть полезно (и, возможно, именно то, что вам нужно для дальнейших оценок):

array.group_by {|where| where[:accepts] }
# => {false=>[{:accepts=>false, :id=>1},
#             {:accepts=>false, :id=>2},
#             {:accepts=>false, :id=>4}],
#      true=>[{:accepts=>true, :id=>3},
#             {:accepts=>true, :id=>5}]}

Опять же, искусственные сортировки не требуются ... group_by является новым в 1.8.7.

PS: Если вам не нужен первый фрагмент кодаудалите дубликаты из вашего массива, замените оператор bar на оператор плюс."|"объединяет два набора в соответствии с теорией наборов (объединение), в то время как "+" объединяет два набора (на самом деле это не набор, а простой массив).

1 голос
/ 03 ноября 2010
array = [{:id => 1, :accepts => false}, 
         {:id => 2, :accepts => false}, 
         {:id => 3, :accepts => true}, 
         {:id => 4, :accepts => false}, 
         {:id => 5, :accepts => true}]

sorted = array.sort do |x, y|
  if x[:accepts] ^ y[:accepts]
      x[:accepts] ? -1 : 1
  else
      x[:id] <=> y[:id]
  end
end

puts sorted

Или != вместо ^, если хотите.

0 голосов
/ 04 ноября 2010

Это потому что сортировка в Ruby 1.8.7 нестабильна

Все, что вам нужно сделать, это чтобы ваш блок сортировки не возвращался 0:

sorted = array.sort do |x, y|
  if x[:accepts] == y[:accepts]
    x[:id] <=> y[:id]  # not 0
  elsif x[:accepts]
    -1
  else
    1
  end
end

(нет необходимости явно сравнивать логическое значение с true и false)

0 голосов
/ 03 ноября 2010
a.sort_by { |x| (x[:accepts] ? 0 : 99999) + x[:id] }

Обновление : Ну, очевидно, для этого требуется x[:id].respond_to? "+" и, кроме того, существуют ограничения на его диапазон относительно констант.

Это, однако, самый короткий и, вероятно,самый быстрый ответ, если, конечно, и самый сомнительный.

Действительно важный урок заключается в том, что он показывает, что нужно смотреть за пределы Array (или что-то еще) и проверять Enumerable, если оно находится в (your object).class.ancestors.Эти вопросы и их читатели часто после ответа на " что я должен узнать о Руби дальше, я подозреваю, что есть другие способы ".

Независимо от того, является ли это хороший способ сортировки(по общему признанию это сомнительно), этот ответ предполагает #sort_by, и просто поиск документов для #sort_by (его нет в массиве) преподаст небольшой, но важный урок для начинающего.

0 голосов
/ 03 ноября 2010

Вы можете добавить дополнительную проверку для клавиши :id, если :accepts равен, как показано ниже:

array = [{:id => 1, :accepts => false}, 
    {:id => 2, :accepts => false}, 
    {:id => 3, :accepts => true}, 
    {:id => 4, :accepts => false}, 
    {:id => 5, :accepts => true}]

sorted = array.sort do |x, y|
  if x[:accepts] == y[:accepts]
    if x[:id] == y[:id]
      0
    elsif x[:id] > y[:id]
      1
    elsif x[:id] < y[:id]
      -1
    end
  elsif x[:accepts] == true
    -1
  elsif x[:accepts] == false
    1
  end
end
...