【问题标题】:Ruby String end_with? Array红宝石字符串 end_with?大批
【发布时间】:2018-10-24 20:34:22
【问题描述】:

我正在寻找一种方法来限定文件名是否以数组内的后缀结尾。

filetypes = %w(.flac .wv)
Dir.open(Dir.pwd).each do |filename|
  q = "." + filename.split('.').last.to_s
  pp filename if filetypes.include?(q)
end

我认为必须有某种内置迭代器来遍历限定符数组。我在这里遗漏了什么吗?

filetypes = %w(.flac .wv)
Dir.open(Dir.pwd).each do |filename|
  pp filename if filename.end_with?(filetypes)
end

这不起作用。文档是这样说的:

end_with?([suffixes]+) → true or false

所以它表明可以遍历一个数组,但它对我不起作用。如何将数组传递给end_with?

【问题讨论】:

  • 也许应该是filetypes = %w(.flac .wv),而不是a = %w(.flac .wv)
  • 是的,对不起,我错过了。会修复的。
  • 一般来说,我认为你会发现询问如何实现某个目标而不是将考虑限制在特定方法(这里使用end_with?)更有帮助。后者有时称为"XY problems"
  • @CarySwoveland OK 一个可枚举的,但在字符串对象上没有方法。似乎 Enumerable 是最快的方法。
  • 我是按照问题的标题来的。

标签: arrays ruby string


【解决方案1】:

您只是缺少 splat 运算符 (*):

filetypes = %w(.flac .wv)

Dir.open(Dir.pwd).each do |filename|
  pp(filename) if filename.end_with?(*filetypes)
end

【讨论】:

  • 好的,我们有了。正是我想要的。谢谢。
  • 没问题!乐于助人:)
【解决方案2】:

您可以使用避免使用end_with? 的简单单行来做到这一点:

Dir[Dir.pwd + '/*.{flac,wv}'].each { |filename| pp filename }

关于end_with? 接受哪些值的问题,答案是它接受以逗号分隔的值列表,例如:

'foo'.end_with?('a', 'b', 'c')
=> false
'foo'.end_with?('a', 'b', 'c', 'o')
=> true

end_with?([suffixes]+) → true or false 的文档示例可以读作“end_with? 接受一个或多个以逗号分隔的后缀,接收对象会将其视为数组,如果字符串以任何后缀结尾,则返回true,否则返回false。”

您还可以使用splat operator 将数组转换为end_with? 的可接受值:

'foo'.end_with?(*['a', 'b', 'c'])
=> false
'foo'.end_with?(*['a', 'b', 'c', 'o'])
=> true

更新以澄清给出的示例

从下面的 cmets 来看,Rich_F 对这个关于 splat 运算符的答案感到非常困惑,所以我将澄清我的答案,以帮助确保没有其他人对我提供的示例感到困惑。

Ruby 中的 splat 运算符 * 会将数组的内容转换为以逗号分隔的参数列表。在将数组作为参数传递给方法时经常使用它,例如:

array = ['a', 'b', 'c']
'foo'.end_with?(*array)

这会将数组“分解”成逗号分隔的参数列表,并且在功能上等同于:

'foo'.end_with?(*['a', 'b', 'c'])

同样,它在功能上等价于:

'foo'.end_with?('a', 'b', 'c')

同样,它在功能上等价于:

array = %w(a b c)
'foo'.end_with?(*array)

同样,它在功能上等价于:

'foo'.end_with?(*%w(a b c))

同样,它在功能上等价于:

filetypes = %w(.flac .wv)
filename.end_with?(*filetypes)

鉴于有关 splat 运算符如何影响数组的知识以及将数组作为参数传递给方法的示例,可以推断问题中的给定代码示例可以更改如下:

filetypes = %w(.flac .wv)
Dir.open(Dir.pwd).each do |filename|
  pp filename if filename.end_with?(*filetypes)
end

唯一的变化是添加*。通过在参数filetypes 之前添加splat 运算符*filetypes 数组将扩展为end_with? 调用的逗号分隔参数。

这不是问题的有效解决方案,因为它需要为目录列表的每次迭代迭代一次 filetypes 数组,并且迭代目录中的所有对象而不考虑匹配,而不是使用 Dir[Dir.pwd + '/*.{flac,wv}'].each { |filename| pp filename }仅返回相关文件,并且不必为每次迭代遍历第二个数组。因此,在给出的示例中使用end_with? 效率非常低,不应在任何实际代码中使用。

例如,给定一个包含 10,000 个以 .flac 结尾的文件的目录:

require 'benchmark'

dir = Dir[Dir.pwd + '/*.{flac,vw}']; nil
10.times { puts Benchmark.measure { 10000.times { dir.each { |filename| nil } } } }
  4.271809   0.001728   4.273537 (  4.274684)
  4.279765   0.002286   4.282051 (  4.283524)
  4.334877   0.004689   4.339566 (  4.343982)
  4.269334   0.001593   4.270927 (  4.272033)
  4.256148   0.001545   4.257693 (  4.258734)
  4.261371   0.001733   4.263104 (  4.264229)
  4.254568   0.001085   4.255653 (  4.256379)
  4.259886   0.001245   4.261131 (  4.261711)
  4.258024   0.001964   4.259988 (  4.261133)
  4.236385   0.001142   4.237527 (  4.238184)

与问题中的原始示例相比,速度是原来的两倍多:

require 'benchmark'

dir = Dir[Dir.pwd + '/*']; nil
filetypes = %w(flac vw); nil
10.times { puts Benchmark.measure { 10000.times { dir.each { |filename| nil if filename.end_with?(*filetypes) } } } }
   9.509041   0.003634   9.512675 (  9.514197)
   9.484041   0.003079   9.487120 (  9.488686)
   9.508674   0.002872   9.511546 (  9.512768)
   9.508382   0.002809   9.511191 (  9.512343)
   9.762489   0.011043   9.773532 (  9.783415)
   9.607308   0.005655   9.612963 (  9.616716)
   9.962166   0.009848   9.972014 (  9.978026)
   9.621152   0.005883   9.627035 (  9.631075)
  10.811991   0.010787  10.822778 ( 10.831729)
  10.461568   0.013571  10.475139 ( 10.487688)

当匹配文件的数量远低于不匹配文件的数量时,差异变得更加明显。在这里,该目录有 1,000 个.flac 文件和 9,000 个.txt 文件:

require 'benchmark'

dir = Dir[Dir.pwd + '/*.{flac,vw}']; nil
10.times { puts Benchmark.measure { 10000.times { dir.each { |filename| nil } } } }
  0.384366   0.000210   0.384576 (  0.384669)
  0.384336   0.000186   0.384522 (  0.384717)
  0.386674   0.000178   0.386852 (  0.386947)
  0.383575   0.000075   0.383650 (  0.383671)
  0.382555   0.000090   0.382645 (  0.382692)
  0.384618   0.000048   0.384666 (  0.384677)
  0.384687   0.000199   0.384886 (  0.384976)
  0.386517   0.000193   0.386710 (  0.386842)
  0.386167   0.000132   0.386299 (  0.386388)
  0.390683   0.000093   0.390776 (  0.390817)

与使用 splat 运算符的原始示例相比,速度慢了 36 倍

require 'benchmark'

dir = Dir[Dir.pwd + '/*']; nil
filetypes = %w(flac vw); nil
10.times { puts Benchmark.measure { 10000.times { dir.each { |filename| nil if filename.end_with?(*filetypes) } } } }
  11.182205   0.014303  11.196508 ( 11.210042)
  11.154286   0.011573  11.165859 ( 11.178611)
  11.081012   0.009853  11.090865 ( 11.098028)
  11.084294   0.009361  11.093655 ( 11.101870)
  10.990442   0.007118  10.997560 ( 11.002036)
  11.044119   0.009284  11.053403 ( 11.058608)
  11.072604   0.009114  11.081718 ( 11.087941)
  11.127151   0.009354  11.136505 ( 11.143270)
  11.172101   0.012262  11.184363 ( 11.195138)
  11.126791   0.010767  11.137558 ( 11.145617)

【讨论】:

  • 这就是我的想法,然后希望看到一个输入数组以某种方式表示为逗号分隔就足够了。但我希望这个线程显示希望通过数组而不是Array.include?('stringthing') 来获得资格。
  • 我不知道您在此评论中想说什么,我已经阅读了很多遍。如果您说您仍想传递一个数组,请在您的数组上使用 splat 运算符*,如我的答案的最新编辑中所示。
  • 对不起,是的,你说的没错。 myarray.*',') 有效。
  • 没有。如果您阅读了这篇文章,那么通过一组限定符进行往复迭代没有任何意义。是的,我已经写了一个解决方法,但问题是关于迭代数组的方法。请阅读原帖。
  • 好的,你已经编辑了它,你想让我说你已经给了我答案。你没有。你给了我一个解决方法,而不是我认为我错过的方法。请停止编辑帖子,然后要求承认您未提供的内容。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-29
  • 1970-01-01
  • 2013-07-26
  • 2014-02-11
  • 2011-01-21
  • 1970-01-01
  • 2010-09-25
相关资源
最近更新 更多