如何根据条件从 Scala 中的地图中提取列表?

How do I extract a list from a map in Scala according to conditions?

我有一个由字符串和元组列表组成的映射:

Map[String, List[(String, Int]]

地图中的一些线条示例如下:

"test1", List(("apple", 0), ("pear", 1), ("banana", 0))

"test2", List(("green", 1), ("yellow", 1), ("red", 0))

"test3", List(("monday", 0), ("tuesday", 1), ("wednesday", 0))

仅当元组列表包含超过 1 个等于 0 的值时,提取每行中第一个字符串列表的最佳方法是什么?

换句话说,我想要一个包含“test1”和“test2”的列表,因为这两行是唯一在其元组列表中包含超过 1 个“0”的行。

您的起点将是 collectfilter;当你有一个集合并且你想要一个应用了一些条件的新集合时,这两种方法都很有用。

如前所述,您的条件听起来像是 count 方法的 use-case,标准库中的几乎所有集合类型都定义了该方法。

这应该有效...

theMapFromYourExample
  .collect { case (key, list) if list.count(_._2 == 0) > 1 => key }
  .toList

...但有一些注意事项:

  • .collecttoList 都将创建新的整个集合。将它们放在一起意味着您正在浪费一些内存和 CPU 时间,因为您构建了一个中间集合,一旦您的表达式完成 运行,它就会直接发送到垃圾收集。为避免为整个中间集合分配内存,您可以使用 View 模式,可通过大多数集合的 .view 方法访问。
  • .count 是列表大小的 O(N),因为它需要检查列表中的每个项目以查看它是否符合您的条件。由于您只关心计数“至少”是某个特定值,因此如果超过 1,您可以提前停止计数。lengthCompare 方法对这种事情很有用。

下面是大致相同的代码,利用“视图”来提高效率:

def hasAtLeastTwoZeroes(list: List[(String, Int)]) = {
  list
   .view // lets us call `.filter` without allocating a new List
   .filter(_._2 == 0) // apply conditional: value is 0
   .lengthCompare(1) > 0 // like saying `.length > 1`, but is O(1), not O(N)
}

theMapFromYourExample
  .view // lets us call `collect` without allocating an intermediate collection
  .collect { case (key, list) if hasAtLeastTwoZeroes(list) => key }
  .toList // builds a List from the collected view

Update 正如 Luis 在评论中指出的那样,Scala 2.13 添加了 sizeIs 方法,它具有比 lengthCompare[= 更流畅的界面25=]