编程风格的练习:电子表格

上周,我们借助数据库解决了前25个词频问题。 这周,我们将回到单独使用代码来解决它。 但是,解决方案的模型将设计为电子表格。

这是《编程风格练习》重点系列的 15 帖子。其他帖子包括:

  1. 以编程风格介绍练习
  2. 以编程风格进行练习,将内容堆叠起来
  3. 编程风格的练习,Kwisatz Haderach风格
  4. 编程风格的练习,递归
  5. 具有高阶功能的编程风格的练习
  6. 以编程风格进行练习
  7. 以编程风格进行练习,回到面向对象的编程
  8. 编程风格的练习:地图也是对象
  9. 编程风格的练习:事件驱动的编程
  10. 编程风格的练习和事件总线
  11. 反思编程风格的练习
  12. 面向方面的编程风格的练习
  13. 编程风格的练习:FP&I / O
  14. 关系数据库风格的练习
  15. 编程风格的练习:电子表格 (此职位)
  16. 并发编程风格的练习
  17. 编程风格的练习:在线程之间共享数据
  18. 使用Hazelcast以编程风格进行练习
  19. MapReduce样式的练习
  20. 编程风格的练习总结

电子表格模型的原理

电子表格包含许多单元格,每个单元格都具有一个值一个公式 。 就像在常规电子表格中一样,公式是一个可以引用另一个单元格值并计算当前单元格值的函数。

以下是候选单元格的列表及其说明:

细胞 类型

allWords

List<String>

read(filename).flatMap{
    it.toLowerCase()
      .split("\\W|_".toRegex())
  }.filter{
    it.isNotBlank()&&it.length>=2
  }

stopWords

List<String>

read("stop_words.txt")
  .flatMap{it.split(",")}

nonStopWords

List<String>

allWords.value.filter{
  !stopWords.value.contains(it)
}

uniqueWords

List<String>

nonStopWords.value.distinct()

counts

List<Int>

uniqueWords.value.map{unique->
  allWords.value.count{it==unique}
}

sortedData

List<Pair<String, Int>>

uniqueWords.value.zip(counts.value)
  .sortedByDescending{
    it.formulaasInt
  }

下一步是将以上所有单元格放入一个集合中,并根据公式顺序设置该值。

data classCell<T,V>(varvalue:T,varformula:V) (1)  (3)
typealiasSpreadsheet=List<Cell<List<Any>,()->List<Any>>> (2)  (3)

valspreadsheet:Spreadsheet=
  listOf(allWords,stopWords,nonStopWords,uniqueWords,counts,sortedData)

update(spreadsheet)

privatefunupdate(cells:Spreadsheet){
  cells.forEach{
    it.value=it.formula() (4)
  }
}
  1. 在Kotlin中, Pair是不可变的,因此我们需要一个自定义类来对单元进行建模
  2. Kotlin的类型别名允许为具有泛型的集合命名,而无需创建完整的类型
  3. 单元格在Python版本中被称为“列”。 我更新了语义以更好地反映电子表格的建模:“列”变为“单元格”,“单元格集合”变为“电子表格”
  4. 通过调用单元格的功能来更新单元格的值

迈向不变

为了再次证明无论选择哪种编程范例都可以使用不变性(应该?),让我们迁移到递归和不变数据结构。

主要步骤是彻底更改更新功能:

privatefunupdate(cells:Spreadsheet){
  cells.forEach{
    it.value=it.formula() (1)
  }
}

privatefunupdate(columns:Spreadsheet):Spreadsheet{ (2)
  tailrecfunrecurseUpdate(todo:Spreadsheet,acc:Spreadsheet):Spreadsheet{ (3)
    returnif(todo.isEmpty())acc
    else{
      valcolumn=todo.first()
      valf=column.second
      if(fis()->List<Any>) (4)
          recurseUpdate(todo.takeLast(todo.size-1),acc+(f()tof)) (5)
      else{
          valg=fas(List<Any>)->List<Any>
          recurseUpdate(todo.takeLast(todo.size-1),
                        acc+(g(acc.last().first)tof)) (6)
      }
    }
  }
  returnrecurseUpdate(columns,arrayListOf())
}

typealiasSpreadsheet=List<Pair<List<Any>,Function<List<Any>>>> (7)
  1. 可变版本:单元格的值已更改
  2. 不可变版本:该函数返回电子表格
  3. 使用尾递归以获得更好的性能
  4. 检查该函数是否有任何参数。
  5. 如果函数不接受任何参数( 例如,读取停用词),则只需调用公式并返回具有已更新单元格值的新数据结构
  6. 如果函数接受参数,则从最后一个单元格的值获取所需的参数。 这显然是一个巨大的约束,但在我们的情况下有效。
  7. 类型别名也需要更新

此时,可以更改代码以使用此新版本:

valspreadsheet:Spreadsheet=listOf(allWords,nonStopWords,counts,sortedData)

return(update(spreadsheet)
  .last()
  .first
  .take(25)asList<Pair<String,Int>>)
  .toMap()

结论

将问题建模为电子表格是一个有趣的转折。

请注意,与常规电子表格软件相反,需要显式更新值:每当从属值发生更改时,都没有实现观察者模式来更新值的实现。

我相信不可变的版本尽管管理起来有点复杂,但可以对每个功能进行更好的单元测试。

这篇文章的完整源代码可以在Github上找到。

翻译自: https://blog.frankel.ch/exercises-programming-style/15/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值