如何在ruby中以字母数字方式对数组数据进行排序?
假设我的数组是a = [test_0_1, test_0_2, test_0_3, test_0_4, test_0_5, test_0_6, test_0_7, test_0_8, test_0_9, test_1_0, test_1_1, test_1_2, test_1_3, test_1_4, test_1_5, test_1_6, test_1_7, test_1_8, test_1_9, test_1_10, test_1_11, test_1_12, test_1_13, test_1_14, ...........test_1_121...............]
我希望我的输出为:
.
.
.
test_1_121
.
.
.
test_1_14
test_1_13
test_1_12
test_1_11
test_1_10
test_1_9
test_1_8
test_1_7
test_1_6
test_1_5
test_1_4
test_1_3
test_1_2
test_1_1
test_0_10
test_0_9
test_0_8
test_0_7
test_0_6
test_0_5
test_0_4
test_0_3
test_0_2
test_0_1
答案 0 :(得分:7)
用于对在任意位置包含非填充序列号的字符串进行排序的通用算法。
padding = 4
list.sort{|a,b|
a,b = [a,b].map{|s| s.gsub(/\d+/){|m| "0"*(padding - m.size) + m } }
a<=>b
}
其中padding是您希望数字在比较期间具有的字段长度。如果字符串中找到的任何数字在比较之前将被填零,如果它包含少于“填充”的数字位数,则会产生预期的排序顺序。
要产生user682932要求的结果,只需在排序块之后添加.reverse
,这将自然排序(升序)翻转为降序。
通过字符串上的预循环,您当然可以动态地找到字符串列表中的最大位数,您可以使用它而不是硬编码一些任意填充长度,但这需要更多处理(更慢) )和更多的代码。 E.g。
padding = list.reduce(0){|max,s|
x = s.scan(/\d+/).map{|m|m.size}.max
(x||0) > max ? x : max
}
答案 1 :(得分:5)
例如,如果您只是按字符串排序,则不会在'test_2'和'test_10'之间得到正确的顺序。所以:
sort_by{|s| s.scan(/\d+/).map{|s| s.to_i}}.reverse
答案 2 :(得分:2)
您可以将块传递给sort函数以对其进行自定义排序。在你的情况下你会遇到问题,因为你的数字不是零填充,所以这个方法零填充数字部分,然后对它们进行排序,得到你想要的排序顺序。
a.sort { |a,b|
ap = a.split('_')
a = ap[0] + "%05d" % ap[1] + "%05d" % ap[2]
bp = b.split('_')
b = bp[0] + "%05d" % bp[1] + "%05d" % bp[2]
b <=> a
}
答案 3 :(得分:2)
排序例程的处理时间会有很大变化。基准测试的变化可以快速掌握最快的方式:
#!/usr/bin/env ruby
ary = %w[
test_0_1 test_0_2 test_0_3 test_0_4 test_0_5 test_0_6 test_0_7
test_0_8 test_0_9 test_1_0 test_1_1 test_1_2 test_1_3 test_1_4 test_1_5
test_1_6 test_1_7 test_1_8 test_1_9 test_1_10 test_1_11 test_1_12 test_1_13
test_1_14 test_1_121
]
require 'ap'
ap ary.sort_by { |v| a,b,c = v.split(/_+/); [a, b.to_i, c.to_i] }.reverse
及其输出:
>> [
>> [ 0] "test_1_121",
>> [ 1] "test_1_14",
>> [ 2] "test_1_13",
>> [ 3] "test_1_12",
>> [ 4] "test_1_11",
>> [ 5] "test_1_10",
>> [ 6] "test_1_9",
>> [ 7] "test_1_8",
>> [ 8] "test_1_7",
>> [ 9] "test_1_6",
>> [10] "test_1_5",
>> [11] "test_1_4",
>> [12] "test_1_3",
>> [13] "test_1_2",
>> [14] "test_1_1",
>> [15] "test_1_0",
>> [16] "test_0_9",
>> [17] "test_0_8",
>> [18] "test_0_7",
>> [19] "test_0_6",
>> [20] "test_0_5",
>> [21] "test_0_4",
>> [22] "test_0_3",
>> [23] "test_0_2",
>> [24] "test_0_1"
>> ]
测试速度显示的算法:
require 'benchmark'
n = 50_000
Benchmark.bm(8) do |x|
x.report('sort1') { n.times { ary.sort { |a,b| b <=> a } } }
x.report('sort2') { n.times { ary.sort { |a,b| a <=> b }.reverse } }
x.report('sort3') { n.times { ary.sort { |a,b|
ap = a.split('_')
a = ap[0] + "%05d" % ap[1] + "%05d" % ap[2]
bp = b.split('_')
b = bp[0] + "%05d" % bp[1] + "%05d" % bp[2]
b <=> a
} } }
x.report('sort_by1') { n.times { ary.sort_by { |s| s } } }
x.report('sort_by2') { n.times { ary.sort_by { |s| s }.reverse } }
x.report('sort_by3') { n.times { ary.sort_by { |s| s.scan(/\d+/).map{ |s| s.to_i } }.reverse } }
x.report('sort_by4') { n.times { ary.sort_by { |v| a = v.split(/_+/); [a[0], a[1].to_i, a[2].to_i] }.reverse } }
x.report('sort_by5') { n.times { ary.sort_by { |v| a,b,c = v.split(/_+/); [a, b.to_i, c.to_i] }.reverse } }
end
>> user system total real
>> sort1 0.900000 0.010000 0.910000 ( 0.919115)
>> sort2 0.880000 0.000000 0.880000 ( 0.893920)
>> sort3 43.840000 0.070000 43.910000 ( 45.970928)
>> sort_by1 0.870000 0.010000 0.880000 ( 1.077598)
>> sort_by2 0.820000 0.000000 0.820000 ( 0.858309)
>> sort_by3 7.060000 0.020000 7.080000 ( 7.623183)
>> sort_by4 6.800000 0.000000 6.800000 ( 6.827472)
>> sort_by5 6.730000 0.000000 6.730000 ( 6.762403)
>>
Sort1和sort2以及sort_by1和sort_by2有助于为sort
,sort_by
以及reverse
和{{1}}两者建立基线。
排序sort3和sort_by3是此页面上的另外两个答案。 Sort_by4和sort_by5是关于我如何做的两个旋转,sort_by5是我在几分钟的修补之后想出的最快的。
这显示了算法中的微小差异如何在最终输出中产生差异。如果有更多的迭代,或者更大的数组被排序,差异将更加极端。
答案 4 :(得分:2)
在此处发布更通用的方法来在Ruby中执行自然十进制排序。 以下是我的代码对https://github.com/CocoaPods/Xcodeproj/blob/ca7b41deb38f43c14d066f62a55edcd53876cd07/lib/xcodeproj/project/object/helpers/sort_helper.rb中的“像Xcode”进行排序的代码的启发,而其本身受到https://rosettacode.org/wiki/Natural_sorting#Ruby的启发。
即使很明显我们希望自然十进制排序中的“ 10”在“ 2”之后,也需要考虑其他方面以及多种可能的替代行为:
出于这些考虑:
scan
而不是split
,因为我们将要比较三种潜在的子字符串(数字,空格,其余)。 Comparable
类和def <=>(other)
,因为不可能简单地将每个子字符串map
变成其他具有两种不同行为的子字符串,具体取决于上下文(第一遍和相等遍)。这会导致实现过程有些冗长,但在边缘情况下效果很好:
# Wrapper for a string that performs a natural decimal sort (alphanumeric).
# @example
# arrayOfFilenames.sort_by { |s| NaturalSortString.new(s) }
class NaturalSortString
include Comparable
attr_reader :str_fallback, :ints_and_strings, :ints_and_strings_fallback, :str_pattern
def initialize(str)
# fallback pass: case is inverted
@str_fallback = str.swapcase
# first pass: digits are used as integers, spaces are compacted, case is ignored
@ints_and_strings = str.scan(/\d+|\s+|[^\d\s]+/).map do |s|
case s
when /\d/ then Integer(s, 10)
when /\s/ then ' '
else s.downcase
end
end
# second pass: digits are inverted, case is inverted
@ints_and_strings_fallback = @str_fallback.scan(/\d+|\D+/).map do |s|
case s
when /\d/ then Integer(s.reverse, 10)
else s
end
end
# comparing patterns
@str_pattern = @ints_and_strings.map { |el| el.is_a?(Integer) ? :i : :s }.join
end
def <=>(other)
if str_pattern.start_with?(other.str_pattern) || other.str_pattern.start_with?(str_pattern)
compare = ints_and_strings <=> other.ints_and_strings
if compare != 0
# we sort naturally (literal ints, spaces simplified, case ignored)
compare
else
# natural equality, we use the fallback sort (int reversed, case swapped)
ints_and_strings_fallback <=> other.ints_and_strings_fallback
end
else
# type mismatch, we sort alphabetically (case swapped)
str_fallback <=> other.str_fallback
end
end
end
示例1:
arrayOfFilenames.sort_by { |s| NaturalSortString.new(s) }
示例2:
arrayOfFilenames.sort! do |x, y|
NaturalSortString.new(x) <=> NaturalSortString.new(y)
end
您可以在https://github.com/CocoaPods/Xcodeproj/blob/ca7b41deb38f43c14d066f62a55edcd53876cd07/spec/project/object/helpers/sort_helper_spec.rb处找到我的测试用例,在这里我使用此参考进行订购: [ ' 一种', ' 一种', '0.1.1', '0.1.01', '0.1.2', '0.1.10', '1', '01', '1a', '2', '2个', '10', '一种', '一种', '一种 ', 'a2', 'a1', 'A1B001', 'A01B1', ]
当然,现在就可以自定义您自己的排序逻辑。
答案 5 :(得分:1)
与@ctcherry答案类似,但速度更快:
a.sort_by {|s| "%s%05i%05i" % s.split('_') }.reverse
编辑:我的测试:
require 'benchmark'
ary = []
100_000.times { ary << "test_#{rand(1000)}_#{rand(1000)}" }
ary.uniq!; puts "Size: #{ary.size}"
Benchmark.bm(5) do |x|
x.report("sort1") do
ary.sort_by {|e| "%s%05i%05i" % e.split('_') }.reverse
end
x.report("sort2") do
ary.sort { |a,b|
ap = a.split('_')
a = ap[0] + "%05d" % ap[1] + "%05d" % ap[2]
bp = b.split('_')
b = bp[0] + "%05d" % bp[1] + "%05d" % bp[2]
b <=> a
}
end
x.report("sort3") do
ary.sort_by { |v| a, b, c = v.split(/_+/); [a, b.to_i, c.to_i] }.reverse
end
end
输出:
Size: 95166
user system total real
sort1 3.401000 0.000000 3.401000 ( 3.394194)
sort2 94.880000 0.624000 95.504000 ( 95.722475)
sort3 3.494000 0.000000 3.494000 ( 3.501201)
答案 6 :(得分:0)
从它的外观来看,您想要使用sort function和/或reverse function.
ruby-1.9.2-p136 :009 > a = ["abc_1", "abc_11", "abc_2", "abc_3", "abc_22"]
=> ["abc_1", "abc_11", "abc_2", "abc_3", "abc_22"]
ruby-1.9.2-p136 :010 > a.sort
=> ["abc_1", "abc_11", "abc_2", "abc_22", "abc_3"]
ruby-1.9.2-p136 :011 > a.sort.reverse
=> ["abc_3", "abc_22", "abc_2", "abc_11", "abc_1"]
答案 7 :(得分:0)
好的,从您的输出中,您似乎只想让它反转,所以请使用reverse()
a.reverse