为什么 ruby 的 IO readlines 方法在后面跟着过滤器时行为不同

Why does ruby's IO readlines method behave differently when followed by a filter

提问人:Brayw 提问时间:2/1/2022 更新时间:2/1/2022 访问量:95

问:

我正在构建一个受 Wordle 启发的小项目,并从我当地的词典中收集单词。最初我是这样做的:

word_list = File.readlines("/usr/share/dict/words", chomp: true)
word_list.filter { |word| word.length == 5 }.map(&:upcase)

第一行绝对需要很长时间。但是,在执行此操作时:

word_list = File.readlines("/usr/share/dict/words", chomp: true).filter { |word| word.length == 5 }.map(&:upcase)

它在几秒钟内完成。在为它们分配内存之前,我无法弄清楚该块是如何应用于正在读取的行的(我假设这是导致读取时间慢的原因),显然每个方法在调用下一个方法之前都没有完全应用,但这就是我认为方法链接的工作方式。filter

红宝石 io

评论

1赞 Siim Liiser 2/1/2022
您是在控制台中还是在 ruby 文件中运行这 2 行?我怀疑“年龄”部分正在打印出整个word_list而不是从文件中读取它。
0赞 Brayw 2/1/2022
@SiimLiiser 这是一个很好的观点,这可能是瓶颈。我在 IRB 中运行代码来测试获取值的方法,返回值以:(包括“...”)结尾,所以我认为这是 ruby 懒惰并且没有显示它不需要的所有内容,但也许这只是 IRB 整理输出?, "abaciscus", "abacist"...

答:

0赞 Cary Swoveland 2/1/2022 #1

让我们创建一个文件。

File.write('t', "dog horse\npig porcupine\nowl zebra\n") #=> 34

然后

a = File.readlines("t", chomp:true)
  #=> ["dog horse", "pig porcupine", "owl zebra"]

因此,您的块变量包含一个由两个单词组成的字符串。这显然不是你想要的。word

您可以使用 IO::read 将文件“吞入”到字符串中。

s = File.read("t")
  #=> "dog horse\npig porcupine\nowl zebra\n"

然后

a = s.scan(/\w+/)
  #=> ["dog", "horse", "pig", "porcupine", "owl", "zebra"].
b = a.select { |word| word.size == 5 }
  #=> ["horse", "zebra"]
c = b.map(&:upcase)
  #=> ["HORSE", "ZEBRA"]

我们当然可以将这些操作串联起来:

File.read("t").scan(/\w+/).select { |word| word.size == 5 }.map(&:upcase)
  #=> ["HORSE", "ZEBRA"]

scan(/\w+/)匹配每个单词字符串(字母、数字和下划线)。若要仅匹配字母,请将其更改为 。scan(/[a-zA-Z]+/)


您可以使用 IO#readlines,它将行读入数组,方法是提取每行的单词,过滤生成的数组以保留具有 5 个字符的数组,然后在大小写后将这些单词添加到先前定义的空数组中。

File.readlines('t')
    .each_with_object([]) { |line,arr| line.scan(/\w+/) }
    .select { |word| word.size == 5 }
    .map(&:upcase)
    .each { |word| arr << word } #=> ["HORSE", "ZEBRA"]

您可以将可选参数添加到 的参数中,但没有理由这样做。chomp: truereadline


更好的做法是使用 IO#foreach,它不带块,返回一个可以链接的枚举器,避免使用 创建的临时数组。readlines

File.foreach('t').with_object([]) do |line,arr|
  line.scan(/\w+/)
      .select { |word| word.size == 5 }
      .map(&:upcase)
      .each { |word| arr << word }
end
  #=> ["HORSE", "ZEBRA"]