对于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

我觉得必须有一个聪明和有效的方法来做到这一点,虽然就我个人而言,我不需要打破任何速度记录,只会排序几百个项目。我能按排序方式实现比较函数吗?

最佳答案

首先,您的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值的数字后缀:
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'值拆分为数值和非数值部分,将数值部分转换为整数,然后使用数组'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"}
]

如果需要处理最后一种情况,则需要手动逐个比较数组条目,并根据所拥有的内容调整比较。您仍然可以通过这样的方式(测试不太好的代码)获得<=>SchwartzianTransform的一些优点:
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退化为不可理解的混乱。然后,我们使用与之前相同的扫描方式将字符串分割成块,并手动实现数组比较器。如果我们有同一个类的两个东西,那么我们让该类的sort_by处理它,否则我们将强制两个组件字符串化并进行比较。我们只关心第一个非0结果。

10-07 19:04
查看更多