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

发布于 2024-12-01 19:05:51 字数 563 浏览 6 评论 0原文

对于对哈希数组进行排序自然排序,但是最好的方法是什么同时做两个?

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 中实现比较函数吗?

There are workable answers for sorting an array of hashes and for natural sorting, but what is the best way to do both at once?

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

I would like to sort on "id" such that the final ordering is:

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

I feel like there must be a clever and efficient way to do this, though personally I don't need to break any speed records and will only be sorting a few hundred items. Can I implement a comparison function in sort_by?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

烟雨凡馨 2024-12-08 19:05:51

首先,您的 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 退化为难以理解的混乱。然后我们使用与之前相同的扫描将字符串分成几部分并手动实现数组 <=> 比较器。如果我们有同一类的两个事物,那么我们让该类的 <=> 处理它,否则我们将两个组件强制为 String 并按原样比较它们。我们只关心第一个非0结果。

First of all, your my_array is JavaScript/JSON so I'll assume that you really have this:

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

Then you just need to sort_by the numeric suffix of the 'id' values:

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

If the "some-server-" prefixes aren't always "some-server-" then you could try something like this:

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

That would split the 'id' values into numeric and non-numeric pieces, convert the numeric pieces to integers, and then compare the mixed string/integers arrays using the Array <=> operator (which compares component-wise); this will work as long as the numeric and non-numeric components always match up. This approach would handle this:

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

but not this:

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

If you need to handle this last case then you'd need to compare the arrays entry-by-entry by hand and adjust the comparison based on what you have. You could still get some of the advantages of the sort_by Schwartzian Transform with something like this (not very well tested code):

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']) }

The basic idea here is to push the comparison noise off to another class to keep the sort_by from degenerating into an incomprehensible mess. Then we use the same scanning as before to break the strings into pieces and implement the array <=> comparator by hand. If we have two things of the same class then we let that class's <=> deal with it otherwise we force both components to String and compare them as such. And we only care about the first non-0 result.

那请放手 2024-12-08 19:05:51

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

@mu gives a more than adequate answer for my case, but I also figured out the syntax for introducing arbitrary comparisons:

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"] }
南风几经秋 2024-12-08 19:05:51

我认为如果您要对 id 字段进行排序,您可以尝试以下操作:

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

I think that if you are sorting on the id field, you could try this:

my_array.sort { |a,b| a["id"].to_i <=> b["id"].to_i }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文