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

3

有关于对哈希数组进行排序自然排序的可行答案,但是如何同时实现两者的最佳方式是什么?

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

首先,您的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'值分成数字和非数字部分,将数字部分转换为整数,然后使用数组<=>运算符(逐个组件比较)比较混合的字符串/整数数组;只要数字和非数字组件始终匹配即可。这种方法可以处理以下情况:
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结果。

很有帮助,谢谢。如果不是每个前缀都是“some-server”呢? - Ben Flynn
这在我的情况下完全起作用了,并且让我正确思考了。 - Ben Flynn
1
@Ben:如果你感兴趣的话,我已经将其推进到了下一步,我不想半途而废。 - mu is too short
我还没有尝试过这段代码,但它很清晰、很酷。收藏了这个。=) - Ben Flynn

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

0

我认为如果你要按照id字段排序,可以尝试这样做:

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

我认为这将进行字母排序,对吧?所以是1、10、2? - Ben Flynn
我认为更改仍然存在相同的问题。字符串需要以某种方式拆分并首先按字母顺序排序,然后按数字顺序排序。 - Ben Flynn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接