Ruby:对包含一些字符串数组的字符串数组进行按字母顺序排序

3

假设我有:

a = ["苹果", "梨", ["葡萄", "浆果"], "桃子"]

我想按以下方式排序:

a.sort_by do |f|
  f.class == Array ? f.to_s : f
end

我得到:

[["grapes", "berries"], "apple", "peach", "pear"]

我实际上希望将项目按字母顺序排序,其中数组项目按其第一个元素排序:

["apple", ["grapes", "berries"], "peach", "pear"]

或者更好的是,我想要:

["apple", "grapes, berries", "peach", "pear"]

如果这个例子还不够清晰,我想要将项目按字母顺序排序。
有什么建议吗?
到目前为止,我已经尝试了一些方法,但好像无法做到。谢谢。

我想按字母顺序排序,使用字符串数组的第一个项目与其他字符串进行比较。 - steve_gallagher
能否请那些撤回他们的反对和关闭投票的人?我已经澄清了问题。如果还有其他令人困惑的部分,请告诉我。 - steve_gallagher
1
为什么是葡萄浆而不是浆葡萄?内部数组按降序排列? - Abdo
1
你应该编辑“我想按字母顺序排序,将字符串数组的第一个项目用于与其他字符串进行比较”并加入到你的问题中。读者必须阅读评论才能理解你所需的排序方案。 - roippi
在提问之前,请先确定自己想从这里得到什么。当有人回答并完成后,请不要修改帖子。请记住,在未来也请遵守这个规则。 - Arup Rakshit
显示剩余3条评论
7个回答

3
我认为这是您想要的内容:
a.sort_by { |f| f.class == Array ? f.first : f }

1
添加 .flatten,你就可以得到他喜欢的输出了。无论如何,+1 :-) - Abdo
谢谢!是的,你对修改后的问题是正确的。无论如何,如果OP只想保持这种顺序,我不会将数组压平,因为这可能会令人困惑,而且也很昂贵。 - Rafa Paez
1
@Abdo,首选格式是"葡萄,浆果"而不是"葡萄","浆果",这是flatten所给出的。 - Cary Swoveland

3
我会做。
a = ["apple", "pear", ["grapes", "berries"], "peach"]
a.map { |e| Array(e).join(", ") }.sort
# => ["apple", "grapes, berries", "peach", "pear"]

我喜欢第一个,Arup。你可以用[e]代替Array(e)(虽然我没有偏好)。使用join(', ')会给出更美观的间距。你有注意到你的第二个解决方案的结果以稍微不正确的格式呈现吗?如果是这样,请加入我们的行列,因为至少还有两个人,包括我在内,犯了同样的错误。 - Cary Swoveland

2

Array#sort_by 显然是正确的方法,但这里提醒一下如何使用 Array#sort

  a.sort do |s1,s2| 
    t1 = (s1.is_a? Array) ? s1.first : s1
    t2 = (s2.is_a? Array) ? s2.first : s2
    t1 <=> t2
  end.map {|e| (e.is_a? Array) ? e.join(', ') : e }
    #=> ["apple", "grapes, berries", "peach", "pear"]  

@theTinMan指出,在这里sortsort_by慢得多,并给出了一个解释。我一直想看看Benchmark模块的用法,所以趁机比较了这两种方法在手头问题上的效果。我使用了@Rafa的sort_by解决方案和我的sort解决方案。
为了测试,我提前构建了一个包含100个随机样本的数组(每个样本都有10,000个要排序的随机元素),因此基准测试不包括构建样本所需的时间(这并不可忽略)。其中8,000个元素是由8个小写字母组成的随机字符串。另外2,000个元素是形如[str1,str2]的2元组,其中str1str2分别是由8个小写字母组成的随机字符串。我进行了其他参数的基准测试,但底线结果没有显著变化。
require 'benchmark'

# n: total number of items to sort
# m: number of two-tuples [str1, str2] among n items to sort
# n-m: number of strings among n items to sort
# k: length of each string in samples
# s: number of sorts to perform when benchmarking

def make_samples(n, m, k, s)
  s.times.with_object([]) { |_, a| a << test_array(n,m,k) }
end

def test_array(n,m,k)
  a = ('a'..'z').to_a 
  r = []
  (n-m).times { r << a.sample(k).join }
  m.times { r << [a.sample(k).join, a.sample(k).join] }
  r.shuffle!
end

# Here's what the samples look like:    
make_samples(6,2,4,4)
  #=> [["bloj", "izlh", "tebz", ["lfzx", "rxko"], ["ljnv", "tpze"], "ryel"],
  #    ["jyoh", "ixmt", "opnv", "qdtk", ["jsve", "itjw"], ["pnog", "fkdr"]],
  #    ["sxme", ["emqo", "cawq"], "kbsl", "xgwk", "kanj", ["cylb", "kgpx"]],
  #    [["rdah", "ohgq"], "bnup", ["ytlr", "czmo"], "yxqa", "yrmh", "mzin"]]

n = 10000 # total number of items to sort
m = 2000  # number of two-tuples [str1, str2] (n-m strings)
k = 8     # length of each string
s = 100   # number of sorts to perform

samples = make_samples(n,m,k,s)

Benchmark.bm('sort_by'.size) do |bm|
  bm.report 'sort_by' do
    samples.each do |s|
      s.sort_by { |f| f.class == Array ? f.first : f }
    end
  end

  bm.report 'sort' do
    samples.each do |s| 
      s.sort do |s1,s2| 
        t1 = (s1.is_a? Array) ? s1.first : s1
        t2 = (s2.is_a? Array) ? s2.first : s2
        t1 <=> t2
      end
    end
  end
end

              user     system      total        real
sort_by   1.360000   0.000000   1.360000 (  1.364781)
sort      4.050000   0.010000   4.060000 (  4.057673)

尽管从未怀疑,@theTinMan是正确的!我用不同的参数进行了几次其他运行,一直发现sort_by相对于sort具有一致的性能优势。

请注意,sort_by的“系统”时间为零。在其他运行中,它有时会为sort的零值。这些值始终为零或0.010000,让我想知道那里到底发生了什么。(我在Mac上运行了这些)。

对于不熟悉Benchmark的读者,Benchmark#bm需要一个参数,该参数等于所需表头行(user system...)的左填充量。 bm.report将一行标签作为参数。


谢谢,@Jessie。我添加了“flatten”以将结果放入“首选”格式,但这是基于对问题的错误理解。后来我明白了(请参见我的评论Matt的答案),但忘记修复我的答案。现在应该没问题了。 - Cary Swoveland
虽然 sort 可以使用,但它的本质使得它比 sort_by 慢得多。有关更多信息,请阅读"Schwartzian transform" - the Tin Man

1

你已经非常接近了。只需要将.to_s替换为.first即可。

irb(main):005:0> b = ["grapes", "berries"]
=> ["grapes", "berries"]
irb(main):006:0> b.to_s
=> "[\"grapes\", \"berries\"]"
irb(main):007:0> b.first
=> "grapes"

这里有一个可行的例子:
a.sort_by do |f|
  f.class == Array ? f.first : f
end

产生:

["apple", ["grapes", "berries"], "peach", "pear"]

1
a.map { |b| b.is_a?(Array) ? b.join(', ') : b }.sort

# => ["apple", "grapes, berries", "peach", "pear"]

不错,马特。如果列表中还有“葡萄柚”,那么如果是“葡萄,浆果”,逗号会确保它们被正确排序(“葡萄,浆果”在“葡萄柚”之前)。 - Cary Swoveland

1

使用 join 替换 to_s

a.sort_by do |f|
  f.class == Array ? f.join : f
end

# => ["apple", ["grapes", "berries"], "peach", "pear"]

更简洁地说:

a.sort_by {|x| [*x].join }

# => ["apple", ["grapes", "berries"], "peach", "pear"]

问题在于to_s将您的数组转换为以"["开头的字符串:
"[\"grapes\", \"berries\"]"

这个字符串按字母顺序排在其余字符串之前。

join 实际上创建了您希望排序的字符串:

"grapesberries"

根据您的逻辑,该内容已正确按字母顺序排序。
如果您不想让数组保持为数组,则需要进行略微不同的操作,但仍需使用 join
a.map {|x| [*x].join(", ") }.sort

# => ["apple", "grapes, berries", "peach", "pear"]

0

对扁平化数组进行排序

如果您只想将嵌套数组的所有元素扁平化并按字母顺序排序,您只需要使用flattensort即可。例如:

["apple", "pear", ["grapes", "berries"], "peach"].flatten.sort
#=> ["apple", "berries", "grapes", "peach", "pear"]

1
这不是原帖作者想要的。 - roippi
我知道想要这种实现方式似乎很合理,但这是针对表格行的类别类型,有些情况下可能会有多个类型。因此,我必须将它们保持在一起。谢谢。 - steve_gallagher

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