“自然”在Ruby中对哈希数组进行排序

时间:2011-08-30 02:11:06

标签: ruby natural-sort

sorting an array of hashesnatural sorting都有可行的答案,但同时执行这两项操作的最佳方式是什么?

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 个答案:

答案 0 :(得分:5)

首先,你的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'值拆分为数字和非数字部分,将数字部分转换为整数,然后使用数组<=> operator比较混合字符串/整数数组(比较组件 - 明智的);只要数字和非数字组件始终匹配,这将起作用。这种方法可以解决这个问题:

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"}
]

如果您需要处理这最后一种情况,那么您需要手动逐个比较数组,并根据您拥有的内容调整比较。您仍然可以获得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退化为难以理解的混乱。然后我们使用与之前相同的扫描将字符串分成几部分并手动实现数组<=>比较器。如果我们有两个相同类的东西,那么我们让该类的<=>处理它,否则我们强制将两个组件都串起来并比较它们。我们只关心第一个非0结果。

答案 1 :(得分:1)

@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"] }

答案 2 :(得分:0)

我认为如果你在id字段上排序,你可以试试这个:

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