【问题标题】:Array to Hash : words count数组到哈希:字数
【发布时间】:2021-03-10 16:37:47
【问题描述】:

我有一个单词数组,我想得到一个哈希,其中键是单词,值是单词计数。

有没有比我的更美的方式:

result = Hash.new(0)
words.each { |word| result[word] += 1 }
return result

【问题讨论】:

  • 你在上伯克利 SaaS 课程吗?
  • 是的,我有一个解决方案,但正在寻找更好的版本。
  • 如果result[word] 不存在,它会抛出异常,因为没有+ 为nil。
  • result 初始化为 0,因此如果 key 不存在,它将为 0,而不是 nil

标签: ruby


【解决方案1】:

您使用的命令式方法可能是 Ruby 中最快的实现。通过一点重构,你可以写一个单行:

wf = Hash.new(0).tap { |h| words.each { |word| h[word] += 1 } }

另一种使用Enumerable#each_with_object的命令式方法:

wf = words.each_with_object(Hash.new(0)) { |word, acc| acc[word] += 1 }

使用现有抽象的功能/不可变方法:

wf = words.group_by(&:itself).map { |w, ws| [w, ws.length] }.to_h

注意,这在时间上仍然是O(n),但它会遍历集合3次,并在此过程中创建了两个中间对象。

最后:频率计数器/直方图是一种常见的抽象,您可以在诸如 Facets 之类的库中找到它:Enumerable#frequency

require 'facets'
wf = words.frequency

【讨论】:

  • 可能很简单,str.split(" ").reduce(Hash.new(0)) { |h,w| puts h[w] += 1; h }?
  • 一些盐分速度测试,运行小牛的 macbook 上的 ruby​​ 2.0.0p451:声明性:100.times { words.inject(Hash.new 0) { |h, w| h[w] += 1; h } }:平均 1.17 秒。命令式:100.times { hist = Hash.new 0; words.each { |w| hist[w] += 1 } }:平均 1.09 秒。 words 是一个包含 10k 个随机单词的数组,仅生成数组就需要 0.2 秒的平均时间。即,Imperative 的速度快了大约 9%。
  • 感谢您对 Facets 的最后说明。我现在已经重新实现了几次,并且 facets 为我省去了重新做它或启动我自己的标准库的麻烦。对于其他人,您应该查看 Facets,它就像 Ruby 标准库的扩展。
  • 很好的答案。我更喜欢group_by(&:itself)的可读性
  • 另外,each_with_objectreduce IMO 更适合这里。
【解决方案2】:

发布在一个相关问题上,但也在这里发布以提高知名度:

从 Ruby 2.7 开始,Enumerable#tally 方法可以解决这个问题。

来自后备箱documentation

统计集合。返回一个散列,其中键是元素,值是集合中与键对应的元素数。

["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}

【讨论】:

    【解决方案3】:

    inject:

    str = 'I have array of words and I want to get a hash, where keys are words'
    result = str.split.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
    
    => {"I"=>2, "have"=>1, "array"=>1, "of"=>1, "words"=>2, "and"=>1, "want"=>1, "to"=>1, "get"=>1, "a"=>1, "hash,"=>1, "where"=>1, "keys"=>1, "are"=>1}
    

    我不知道效率。

    【讨论】:

    • 根据 tokland 发布的 facets 方法的文档,inject 速度较慢。
    • 另外,如果你使用inject并且你需要像上面一样返回块末尾的对象(; h),你应该使用each_with_object来代替。
    【解决方案4】:
    irb(main):001:0> %w(foo bar foo bar).each_with_object(Hash.new(0)) { |w, m| m[w] += 1 }
    => {"foo"=>2, "bar"=>2}
    

    正如@mfilej 所说

    【讨论】:

      【解决方案5】:

      这个很优雅:

        words.group_by(&:itself).transform_values(&:count)
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-24
        • 1970-01-01
        • 2014-08-22
        • 2012-09-03
        • 2012-10-29
        • 2012-06-12
        • 2021-11-21
        相关资源
        最近更新 更多