0

我编写了一个简单的程序来解析我的银行交易 CSV 文件。我的表达式将结果推送到将保存到数据库的数组/哈希数据结构。

有两个部分:

  1. 一种打开文件、读取每一行并推送它的运行方法。
  2. 从哈希中提取数据的视图。

我在下面包含了我的主要解析方法。它检查每一行的关键字,如果匹配失败,它应该推送到未分类的哈希。但是,条件会根据我是否使用elsif或来推送 ALL 或 NO 事务else

Matchdata 对象默认返回字符串,所以else应该工作不应该吗?这是构建数据结构的方法。我评论了我遇到问题的部分:

def generateHashDataStructure(fileToParse, wordListToCheckAgainst)
  transactionInfo = Hash.new
  transactionInfo[:transactions] = Hash.new
  transactionInfo[:unclassifiedTransaction] = Hash.new
  transaction = transactionInfo[:transactions]
  unclassifiedTransaction = transactionInfo[:unclassifiedTransaction]

  wordListToCheckAgainst.each do |word|
    transaction[word] = Array.new
    unclassifiedTransaction[:unclassifiedTransaction] = Array.new
    File.open(fileToParse).readlines.each do |line|
       if transaction = /(?<transaction>)#{word}/.match(line)   
        date = /(?<Month>\d{1,2})\D(?<Day>\d{2})\D(?<Year>\d{4})/.match(line).to_s
        transaction = /(?<transaction>)#{word}/.match(line).to_s
        amount =/-+(?<dollars>\d+)\.(?<cents>\d+)/.match(line).to_s
        transactions[word].push({:date => date, 
                                :name => transaction, :amount =>    amount.to_f.round(2)})

        # this is problem: else/elsif don't push only if match fails
        else
         date = /(?<Month>\d{1,2})\D(?<Day>\d{2})\D(?<Year>\d{4})/.match(line).to_s
         transaction = /(?<Middle>)".*"/.match(line).to_s
         amount =/-*(?<dollars>\d+)\.(?<cents>\d+)/.match(line).to_s
         unclassifiedTransaction[:unclassifiedTransaction].push({:date => date, 
                                   :name => transaction, :amount => amount.to_f.round(2)})
         next
        end
     end
     return transactionInfo
   end

任何想法都会很棒。我对此进行了研究,我觉得接触社区已经失败了。我意识到正则表达式可能不是最好的方法,所以我愿意接受所有反馈。

4

1 回答 1

2

我使您的代码更加地道,这有助于揭示一些非常有问题的事情。

  1. Ruby 方法和变量是用snake_case 编写的,而不是CamelCase。虽然这似乎是个人意见的问题,但它也成为可维护性/可读性的一个案例。这_有助于我们的大脑在视觉上将变量名称中的词段彼此分开,而不是看到带有混合大小写“驼峰”的运行在一起的字符串。Try_reading_a_bunch_of_text_that_is_identical exceptForThatAndSeeWhichIsMoreExhausting.
  2. 您正在分配给条件测试中的变量:

    if transaction = /(?<transaction>)#{word}/.match(line)
    

    不要那样做。即使它是故意的,当其他人不理解你为什么要这样做时,它也会带来维护错误的可能性。相反,分两步编写它,这样它的意图就很明显了:

    transaction = /(?<transaction>)#{word}/.match(line)  
    if transaction
    

    或者,你的“分配然后比较”真的应该写成:

    if transaction == /(?<transaction>)#{word}/.match(line)   
    

    或者:

    if /(?<transaction>)#{word}/.match(line)   
    

    哪个更干净/安全/明显。

  3. 而不是使用Hash.new, and Array.new,而是分别使用直接赋值{}[]。它们不那么嘈杂,而且更常见。此外,而不是增量定义您的哈希:

    transactionInfo = Hash.new
    transactionInfo[:transactions] = Hash.new
    transactionInfo[:unclassifiedTransaction] = Hash.new
    

    利用:

    transaction_info = {
      :transactions => {},
      :unclassified_transaction => {}
    }
    

    您的结构立即显露出来,使意图更加清晰。

  4. File.open(fileToParse).readlines.each do |line|是一种复杂的做法:

    File.foreach(fileToParse) do |line|
    

    只是foreach不会浪费内存一次将整个文件全部吸入内存。“啜饮”你的文件并没有明显的速度改进,如果文件增长到“巨大”的比例,它只会有缺点。

  5. 而不是使用:

    transactions[word].push({:date => date, 
                            :name => transaction, :amount =>    amount.to_f.round(2)})
    

    更简单地编写代码。push掩盖了您正在做的事情,就像您格式化行的方式一样:

    transactions[word] << {
      :date   => date,
      :name   => transaction,
      :amount => amount.to_f.round(2)
    }
    

    注意对齐到列。有些人会避开这种特殊的习惯,但是当你处理大量任务时,看到每一行的变化会产生很大的不同。

这是更惯用的 Ruby 代码:

def generate_hash_data_structure(file_to_parse, word_list_to_check_against)

  transaction_info = {
    :transactions => {},
    :unclassified_transaction => {}
  }

  transaction = transaction_info[:transactions]
  unclassified_transaction = transaction_info[:unclassified_transaction]

  word_list_to_check_against.each do |word|

    transaction[word] = []
    unclassified_transaction[:unclassified_transaction] = []

    File.foreach(file_to_parse) do |line|

      if transaction = /(?<transaction>)#{word}/.match(line)   

        date        = /(?<Month>\d{1,2})\D(?<Day>\d{2})\D(?<Year>\d{4})/.match(line).to_s
        transaction = /(?<transaction>)#{word}/.match(line).to_s
        amount      = /-+(?<dollars>\d+)\.(?<cents>\d+)/.match(line).to_s

        transactions[word] << {
          :date   => date,
          :name   => transaction,
          :amount => amount.to_f.round(2)
        }

        # this is problem: else/elsif don't push only if match fails

      else

        date        = /(?<Month>\d{1,2})\D(?<Day>\d{2})\D(?<Year>\d{4})/.match(line).to_s
        transaction = /(?<Middle>)".*"/.match(line).to_s
        amount      = /-*(?<dollars>\d+)\.(?<cents>\d+)/.match(line).to_s

        unclassified_transaction[:unclassified_transaction] << {
            :date   => date,
            :name   => transaction,
            :amount => amount.to_f.round(2)
          }

        # next
      end

    end

    transaction_info

  end
end
于 2013-10-04T16:57:19.523 回答