译:词频练习的Ruby实现

4 篇文章 0 订阅

Word Count Exercise in Ruby

题目

单词计数
编写程序来计算给定短语中每个单词出现的次数。
例如输入”olly olly in come free”
计数结果应为:
olly: 2
in: 1
come: 1
free: 1

简单吧,让我们来动手实现:

class Phrase
  attr_accessor :words
  def initialize(words)
    @words = words
  end

  def word_count
    word_list = {}
    words.split(" ").each do |word|
    word_list = process_word_in_list(word, word_list)
    end 
    word_list
  end

  private
  def process_word_in_list(word, word_list)
    return word_list if word.empty?
    word_list[word] ||= 0
    word_list[word] += 1
    word_list
  end
end

最初的这一版实现很直接。创建一个hash,当单词出现时,对其相关hash键的值进行计数。够简单咯。
但是这里有三个问题:

  • word_count方法不易读
  • process_word_in_list方法的实现相当丑陋
  • 还对特殊字符和标点符号进行了计数
    (接下来这个版本看起来更丑,但这只是一切变好之前的过渡)
    class Phrase
      attr_accessor :words
      def initialize(words)
        @words = words
      end

      def word_count
        prep_words_for_counting()
        word_list = {}
        words.split(" ").each do |word|
          word_list = process_word_in_list(word, word_list)
        end
        word_list
      end

     private
     def process_word_in_list(word, word_list)
       word = parse_word(word)
       return word_list if word.empty?
       word_list[word] ||= 0
       word_list[word] += 1
       word_list
     end

     def prep_words_for_counting
       words.gsub!(",", " ")
     end

     def parse_word(word)
       word.gsub!(/[^a-zA-Z0-9]/, "")
       word.downcase!
       word
     end
    end

哦天,这个实现太恶心了。找大神帮我review之后作出了如下改进:
1. 创建有默认值的hash。Hash.new(0)创建了一个hash并且将0作为各个键的默认值。
这就可以砍掉word_list[word] ||= 0这一行了。
2. 我不需要在遍历中将每个单词使用downcase转为小写——直接对整个字符串转换更好。
3. 使用魔幻的ruby字符串方法:scan

Scan遍历字符串来匹配模式(可能是正则或者字符串)。对于每一个匹配,生成的结果可能返回到数组或者被传递到块中。
如下示例:

a = "cruel world"
a.scan(/\w+/)        #=> ["cruel", "world"]
a.scan(/.../)        #=> ["cru", "el ", "wor"]

基于新的ruby知识,接下来是我下一版的实现

class Phrase
  attr_accessor :words
  def initialize(words)
    @words = words
  end

  def word_count
    word_list = Hash.new(0)
    words.downcase.scan(/\w+/) do |word|
      word_list[word] += 1
    end
    word_list
  end
end

现在清晰多咯!使用scan可以省去大量之前进行检查和模式匹配的代码。
为了离完美更近一步,我们可以再作出以下优化:
- 分离word_count方法
这一点确实有些苛求,但是为了让word_count方法中的words.downcase.scan(/\w+/)可以更有表现力。让我们试着将细节提取到块中怎么样?
最终实现:

class Phrase
  attr_accessor :words
  def initialize(words)
    @words = words
  end

  def word_count
    list = Hash.new(0)
    each_word { |word| list[word] += 1 }
    list
  end

private
  def each_word
    words.downcase.scan(/\w+/) { |word| yield word }
  end
end

新的each_word方法更具有表现力:它清楚的告知了输出内容,同时将实现的细节小心封装为私有方法。
就这样,这个练习一开始困扰了我。但是在实现它的过程中,我见识到了scan的威力,另外,这也是一个很好的将复杂的方法移到Ruby块的例子。

更新

Twitter上有些大神建议我通过使用inject或者each_with_object来简化hash的创建。
他们给了我一个简单的原则:“如果只需要累加值(例如创建hash),使用each_with_object,如果需要改变对象,使用inject”。基于此原则,进一步简化实现如下:

class Phrase
  attr_accessor :words
  def initialize(words)
    @words = words
  end

  def word_count
    each_word.each_with_object(Hash.new(0)) { |word, hash| hash[word] += 1 }
  end

private
  def each_word
    words.downcase.scan(/\w+/)
  end
end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值