按数字对字符串数组进行排序

Sorting array of string by numbers

我想按如下方式对数组进行排序:

["10a","10b","9a","9b","8a","8b"]

当我打电话时,

a = a.sort {|a,b| a <=> b}

排序如下:

["10a","10b","8a","8b","9a","9b"]

10是字符串,不作为数字处理。当我首先按整数排序然后按字符串排序时,它会做同样的事情。有谁知道我如何将 10 作为 10 处理而不将其变成整数?这会弄乱字母 ab

▶ a = ["10a","10b","9a","9b","8a","8b"]
▶ a.sort { |a,b| a.to_i == b.to_i ? a <=> b : a.to_i <=> b.to_i }
#=> [
#  [0] "8a",
#  [1] "8b",
#  [2] "9a",
#  [3] "9b",
#  [4] "10a",
#  [5] "10b"
#]

希望对您有所帮助。

["10a","10b","9a","9b","8a","8b"]
.sort_by{|s| s.split(/(\D+)/).map.with_index{|s, i| i.odd? ? s : s.to_i}}
#=> ["8a", "8b", "9a", "9b", "10a", "10b"]

["10a3","10a4", "9", "9aa","9b","8a","8b"]
.sort_by{|s| s.split(/(\D+)/).map.with_index{|s, i| i.odd? ? s : s.to_i}}
#=> ["8a", "8b", "9", "9aa", "9b", "10a3", "10a4"]

我会这样做:

ary = ["10a","10b","9a","9b","8a","8b"]

sorted_ary = ary.sort_by{ |e|
  /(?<digit>\d+)(?<alpha>\D+)/ =~ e 
  [digit.to_i, alpha]
}

ary        # => ["10a", "10b", "9a", "9b", "8a", "8b"]
sorted_ary # => ["8a", "8b", "9a", "9b", "10a", "10b"]
对于此类问题,

sorted_by 将比 sort 更快。因为被排序的值不是直接比较,我们需要深入研究它以获得用于排序规则的值,所以正常的排序必须对每个元素执行多次。相反,使用 sort_by 缓存计算值,然后基于它进行排序。

/(?<digit>\d+)(?<alpha>\D+)/ =~ e 不是您通常会看到的正则表达式。 named-captures ?<digit>?<alpha> 定义了以那种形式使用时可以立即访问的局部变量的名称。

[digit.to_i, alpha] returns 由前导数字组成的数组转换为整数,后跟字符。然后该数组用于 sort_by.

的比较

使用 Fruitysortsort_by 进行基准测试:我向正在排序的数组添加了一些长度,以更努力地推动例程以获得更好的时间分辨率。

require 'fruity'

ARY = (%w[10a 10b 9a 9b 8a 8b] * 1000).shuffle

compare do
  cary_to_i_sort_by { ARY.sort_by { |s| s.to_i(36) } }
  cary_to_i_sort    { ARY.map { |s| s.to_i(36) }.sort.map {|i| i.to_s(36)} }
end

compare do
  jorge_sort_by { ARY.sort_by {|el| [el.to_i, el] } }
  jorg_sort     { ARY.map {|el| [el.to_i, el] }.sort.map(&:last) }
end
# >> Running each test 2 times. Test will take about 1 second.
# >> cary_to_i_sort_by is faster than cary_to_i_sort by 19.999999999999996% ± 1.0%
# >> Running each test once. Test will take about 1 second.
# >> jorge_sort_by is faster than jorg_sort by 10.000000000000009% ± 1.0%

Ruby 的 sort_by 使用 Schwartzian Transform,在处理我们必须计算要排序的值的对象时,它可以在排序速度上产生重大差异。


Could you run your benchmark for 100_000 instead of 1_000 in the definition of ARY?

require 'fruity'

ARY = (%w[10a 10b 9a 9b 8a 8b] * 100_000).shuffle

compare do
  cary_to_i_sort_by { ARY.sort_by { |s| s.to_i(36) } }
  cary_to_i_sort    { ARY.map { |s| s.to_i(36) }.sort.map {|i| i.to_s(36)} }
end

compare do
  jorge_sort_by { ARY.sort_by {|el| [el.to_i, el] } }
  jorg_sort     { ARY.map {|el| [el.to_i, el] }.sort.map(&:last) }
end
# >> Running each test once. Test will take about 10 seconds.
# >> cary_to_i_sort_by is faster than cary_to_i_sort by 2x ± 1.0
# >> Running each test once. Test will take about 26 seconds.
# >> jorg_sort is similar to jorge_sort_by

维基百科文章中有一篇很好的 efficiency analysis and example 解释了为什么 sort_by 更适合进行昂贵的比较。

Ruby的sort_by documentation也涵盖了这一点。

我认为数组的大小不会有太大差异。如果有的话,随着数组大小的增长,如果中间值的计算成本很高,sort_by 仍然会更快,因为它有缓存。请记住,sort_by 是所有编译代码,而使用基于脚本的 Ruby 转换执行速度较慢,因为数组被转换,移交给 sort,然后原始对象是从子数组中提取。更大的数组意味着它只需要完成更多次。

When I first sort by integer and then by string, it will just do the same.

那将是我的第一直觉,而且它似乎工作得很好:

%w[10a 10b 9a 9b 8a 8b].sort_by {|el| [el.to_i, el] }
# => ['8a', '8b', '9a', '9b', '10a', '10b']

不使用String#to_i的两种方式(但依赖于每个字符串由一个或多个数字后跟一个小写字母组成的假设)。

ary = ["10a","10b","9a","9b","8a","8b","100z", "96b"]

#1

mx = ary.map(&:size).max
ary.sort_by { |s| s.rjust(mx) }
  #=> ["8a", "8b", "9a", "9b", "10a", "10b", "96b", "100z"] 

#2

ary.sort_by { |s| s.to_i(36) }
  #=> ["8a", "8b", "9a", "9b", "10a", "10b", "96b", "100z"] 

嗯,我想知道是否:

ary.map { |s| s.rjust(mx) }.sort.map(&:lstrip)

ary.map { |s| s.to_i(36) }.sort.map {|i| i.to_s(36)}

会更快。

先生们,发动你们的引擎!

我决定对已提供的各种解决方案进行基准测试。我很好奇的一件事是将 sort_by 解转换为 sort 解的效果。比如我比较了我的方法

def cary_to_i(a)
  a.sort_by { |s| s.to_i(36) }
end 

def cary_to_i_sort(a)
  a.map { |s| s.to_i(36) }.sort.map {|i| i.to_s(36)}
end

这总是涉及将原始数组映射到 sort_by 块中的转换值,对该数组进行排序,然后将结果映射回原始数组中的元素(当可以完成时)。

我用一些使用 sort_by 的方法尝试了这种 sort_bysort 的转换。毫不奇怪,转换到 sort 的速度通常更快,尽管改进的数量差异很大。

方法比较

module Methods
  def mudasobwa(a)
    a.sort { |a,b| a.to_i == b.to_i ? a <=> b : a.to_i <=> b.to_i }
  end

  def jorg(a)
    a.sort_by {|el| [el.to_i, el] }
  end

  def jorg_sort(a)
    a.map {|el| [el.to_i, el] }.sort.map(&:last)
  end

  def the(a)
    a.sort_by {|e| /(?<digit>\d+)(?<alpha>\D+)/ =~ e
      [digit.to_i, alpha] }
  end

  def the_sort(a)
    a.map {|e| /(?<digit>\d+)(?<alpha>\D+)/ =~ e
     [digit.to_i, alpha]}.sort.map {|d,a| d.to_s+a }
  end

  def engineer(a) a.sort_by { |s|
    s.scan(/(\d+)(\D+)/).flatten.tap{ |a| a[0] = a[0].to_i } }
  end

  def sawa(a) a.sort_by { |s|
    s.split(/(\D+)/).map.with_index { |s, i| i.odd? ? s : s.to_i } }
  end  

  def cary_rjust(a)
    mx = a.map(&:size).max
    a.sort_by {|s| s.rjust(mx)}
  end

  def cary_rjust_sort(a)
    mx = a.map(&:size).max
    a.map { |s| s.rjust(mx) }.sort.map(&:lstrip)
  end

  def cary_to_i(a)
    a.sort_by { |s| s.to_i(36) }
  end

  def cary_to_i_sort(a)
    a.map { |s| s.to_i(36) }.sort.map {|i| i.to_s(36)}
  end
end

include Methods
methods = Methods.instance_methods(false)
  #=> [:mudasobwa, :jorg, :jorg_sort, :the, :the_sort,
  #    :cary_rjust, :cary_rjust_sort, :cary_to_i, :cary_to_i_sort]

测试数据和助手

def test_data(n)
  a = 10_000.times.to_a.map(&:to_s)
  b = [*'a'..'z']
  n.times.map { a.sample + b.sample }
end

def compute(m,a)
  send(m,a)
end

确认方法return相同值

a = test_data(1000)
puts "All methods correct: #{methods.map { |m| compute(m,a) }.uniq.size == 1}"

基准代码

require 'benchmark'

indent = methods.map { |m| m.to_s.size }.max

n = 500_000
a = test_data(n)
puts "\nSort random array of size #{n}"
Benchmark.bm(indent) do |bm|
  methods.each do |m|
    bm.report m.to_s do
      compute(m,a)
    end
  end    
end

测试

Sort random array of size 500000
                      user     system      total        real
mudasobwa         4.760000   0.000000   4.760000 (  4.765170)
jorg              2.870000   0.020000   2.890000 (  2.892359)
jorg_sort         2.980000   0.020000   3.000000 (  3.010344)
the               9.040000   0.100000   9.140000 (  9.160944)
the_sort          4.570000   0.090000   4.660000 (  4.668146)
engineer         10.110000   0.070000  10.180000 ( 10.198117)
sawa             27.310000   0.160000  27.470000 ( 27.504958)
cary_rjust        1.080000   0.010000   1.090000 (  1.087788)
cary_rjust_sort   0.740000   0.000000   0.740000 (  0.746132)
cary_to_i         0.570000   0.000000   0.570000 (  0.576570)
cary_to_i_sort    0.460000   0.020000   0.480000 (  0.477372)

附录

@theTinMan 证明了 sort_bysort 方法之间的比较对测试数据的选择很敏感。使用他使用的数据:

def test_data(n)
  (%w[10a 10b 9a 9b 8a 8b] * (n/6)).shuffle
end

我得到了这些结果:

Sort random array of size 500000
                      user     system      total        real
mudasobwa         0.620000   0.000000   0.620000 (  0.622566)
jorg              0.620000   0.010000   0.630000 (  0.636018)
jorg_sort         0.640000   0.010000   0.650000 (  0.638493)
the               8.790000   0.090000   8.880000 (  8.886725)
the_sort          2.670000   0.070000   2.740000 (  2.743085)
engineer          3.150000   0.040000   3.190000 (  3.184534)
sawa              3.460000   0.040000   3.500000 (  3.506875)
cary_rjust        0.360000   0.010000   0.370000 (  0.367094)
cary_rjust_sort   0.480000   0.010000   0.490000 (  0.499956)
cary_to_i         0.190000   0.010000   0.200000 (  0.187136)
cary_to_i_sort    0.200000   0.000000   0.200000 (  0.203509)

请注意,绝对时间也会受到影响。

谁能解释基准差异的原因?