按数字排序字符串数组

时间:2015-01-15 09:29:42

标签: ruby arrays sorting

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

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

当我打电话时,

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

它将如下排序:

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

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

6 个答案:

答案 0 :(得分:7)

  

当我第一次按整数然后按字符串排序时,它会做同样的事情。

这本来是我的第一直觉,似乎完美无缺:

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

答案 1 :(得分:2)

我做这样的事情:

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]返回一个数组,由前导数字转换为整数,后跟字符。然后,该数组用于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,在处理我们必须计算要排序的值的对象时,它可以在排序速度上产生重大差异。


  

你可以在ARY的定义中运行100_000而不是1_000的基准吗?

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

Wikepedia上的文章有一个很好的efficiency analysisexample,解释了为什么sort_by首选进行代价高昂的比较。

Ruby&#39;} sort_by documentation也很好地涵盖了这一点。

我不认为阵列的大小会有很大的不同。如果有的话,随着数组大小的增加,如果中间值的计算成本很高,sort_by由于其缓存仍然会更快。请记住,sort_by是所有已编译的代码,而使用基于Ruby脚本的转换会因为数组转换后执行速度较慢,切换到sort然后从子文件中提取原始对象-arrays。一个更大的数组意味着它必须要做更多次。

答案 2 :(得分:1)

两种不使用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)}

会更快。

答案 3 :(得分:0)

▶ 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"
#]

希望它有所帮助。

答案 4 :(得分:0)

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

答案 5 :(得分:0)

先生们,启动引擎!

我决定对提供的各种解决方案进行基准测试。我感到好奇的一件事是将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 - 到 - sort_by转换。毫不奇怪,转换为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

确认方法返回相同的值

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)

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

有人能解释基准测试差异的原因吗?