«натуральная» сортировка массива хэшей в Ruby - PullRequest
3 голосов
/ 30 августа 2011

Существуют реальные ответы для сортировки массива хэшей и для естественной сортировки , но как лучше всего сделать и то и другое одновременно?

my_array = [ {"id":"some-server-1","foo":"bar"},{"id":"some-server-2","foo":"bat"},{"id":"some-server-10","foo":"baz"} ]

Я бы хотел отсортировать по "id" так, чтобы окончательный порядок был:

some-server-1
some-server-2
some-server-10

Я чувствую, что должен быть умный и эффективный способ сделать это, хотя лично мне не нужно побивать рекорды скорости, и я буду сортировать только несколько сотен предметов. Могу ли я реализовать функцию сравнения в sort_by?

Ответы [ 3 ]

5 голосов
/ 30 августа 2011

Прежде всего, ваш my_array - это JavaScript / JSON, поэтому я предполагаю, что у вас действительно есть это:

my_array = [
    {"id" => "some-server-1",  "foo" => "bar"},
    {"id" => "some-server-2",  "foo" => "bat"},
    {"id" => "some-server-10", "foo" => "baz"}
]

Тогда вам просто нужно sort_byчисловой суффикс значений 'id':

my_array.sort_by { |e| e['id'].sub(/^some-server-/, '').to_i }

Если префиксы "some-server-" не всегда являются "some-server-", то вы можете попробовать что-то вроде этого:

my_array.sort_by { |e| e['id'].scan(/\D+|\d+/).map { |x| x =~ /\d/ ? x.to_i : x } }

Это разделит значения 'id' на числовые и нечисловые части, преобразует числовые части в целые числа, а затем сравнивает смешанные массивы строк / целых чисел, используя оператор Array <=> (который сравнивает по компонентам);это будет работать до тех пор, пока числовые и нечисловые компоненты всегда совпадают.Этот подход будет обрабатывать это:

my_array = [
    {"id" => "some-server-1", "foo" => "bar"},
    {"id" => "xxx-10",        "foo" => "baz"}
]

, но не это:

my_array = [
    {"id" => "11-pancakes-23", "foo" => "baz"},
    {"id" => "some-server-1",  "foo" => "bar"}
]

Если вам нужно обработать этот последний случай, вам нужно будет сравнивать массивы entry-by-entryот руки и настройте сравнение на основе того, что у вас есть.Вы все еще можете получить некоторые из преимуществ sort_by Schwartzian Transform с чем-то вроде этого (не очень хорошо проверенный код):

class NaturalCmp
    include Comparable
    attr_accessor :chunks

    def initialize(s)
        @chunks = s.scan(/\D+|\d+/).map { |x| x =~ /\d/ ? x.to_i : x }
    end

    def <=>(other)
        i = 0
        @chunks.inject(0) do |cmp, e|
            oe = other.chunks[i]
            i += 1
            if(cmp == 0)
                cmp = e.class == oe.class \
                    ? e      <=> oe \
                    : e.to_s <=> oe.to_s
            end
            cmp
        end
    end
end

my_array.sort_by { |e| NaturalCmp.new(e['id']) }

БазовыйИдея здесь состоит в том, чтобы перенести шум сравнения в другой класс, чтобы предотвратить превращение sort_by в непонятный беспорядок.Затем мы используем то же сканирование, что и раньше, чтобы разбить строки на части и реализовать компаратор массива <=> вручную.Если у нас есть две вещи одного и того же класса, тогда мы разрешим этому классу <=> разобраться с этим, в противном случае мы вынуждаем оба компонента в String и сравниваем их как таковые.И нам важен только первый результат, отличный от 0.

1 голос
/ 30 августа 2011

@ mu дает более чем адекватный ответ для моего случая, но я также выяснил синтаксис для введения произвольных сравнений:

def compare_ids(a,b)
  # Whatever code you want here
  # Return -1, 0, or 1
end

sorted_array = my_array.sort { |a,b| compare_ids(a["id"],b["id"] }
0 голосов
/ 30 августа 2011

Я думаю, что если вы сортируете по полю id, вы можете попробовать это:

my_array.sort { |a,b| a["id"].to_i <=> b["id"].to_i }
...