【问题标题】:Efficiently find common values in a map of lists - scala在列表映射中有效地找到共同值 - scala
【发布时间】:2021-03-10 04:28:20
【问题描述】:

我已经问过类似的问题here。但是,我误判了我的具体案例的规模。在我给出的示例中,地图中只有 4 个键。我实际上正在处理超过 10,000 个键,它们被映射到不同大小的列表。所以给出的解决方案是正确的,但我现在正在寻找一种更有效的方法。

说我有:

val myMap: Map[Int, List[Int]] = Map(
  1 -> List(1, 10, 12, 76, 105), 2 -> List(2, 5, 10), 3 -> List(10, 12, 76, 5), 4 -> List(2, 4, 5, 10), 
  ... -> List(...)
)

想象一下(...) 有超过 10,000 个键。如果它们各自列表的交集大小为>= 3,我想返回一个列表列表,其中包含一对键及其共享值。

例如:

res0: List[(Int, Int, List[Int])] = List(
      (1, 3, List(10, 12, 76)),
      (2, 4, List(2, 5, 10)),
      (...),
      (...),
      )

我已经为此困扰了几天,因此非常感谢任何帮助。提前谢谢!

【问题讨论】:

  • 你认为 O(n log n) 足够高效吗?
  • @BitTickler 据我了解,O(log n) 在处理一组大量数据时最有效。但是,我不一定要寻找最有效的答案,只是比我原来的帖子更有效的答案。所以,我认为 O(n log n) 就足够高效了。
  • 任何输入列表都可以包含重复项吗?
  • @BitTickler 不。每个输入列表中的每个值都是唯一的。
  • 密钥对combinations 要求是增加计算负载的原因。如果可以选择在Apache Spark(利用分布式计算能力)上处理它,那将是相当直接的。

标签: scala functional-programming higher-order-functions


【解决方案1】:

如果空间不是问题,那么问题可以在 O(N) 中解决,其中 N 是列表中元素的数量。

算法:

  • 从输入映射中创建反向查找映射。此处反向查找将列表元素映射到键 (Id)。
  • 对于每个输入映射键
    • 创建临时地图
    • 遍历列表并在反向查找中查找值 (Id)。计算获取的 id 发生的次数。
    • 出现次数等于或超过 3 次的所有密钥都是所需的对。

代码

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

object Application extends App {

  val inputMap = Map(
    1 -> List(1, 2, 3, 4),
    2 -> List(2, 3, 4, 5),
    3 -> List(3, 5, 6, 7),
    4 -> List(1, 2, 3, 6, 7))
  
/*
  Expected pairs

  |  pair | common elements |
  ---------------------------
  (1, 2) -> 2, 3, 4
  (1, 4) -> 2, 3, 4
  (2, 1) -> 2, 3, 4
  (3, 4) -> 3, 5, 6
  (4, 1) -> 1, 2, 3
  (4, 3) -> 3, 5, 6
   */

  val reverseMap = mutable.Map[Int, ArrayBuffer[Int]]()

  inputMap.foreach {
    case (id, list) => list.foreach(
      o => if (reverseMap.contains(o)) reverseMap(o).append(id) else reverseMap.put(o, ArrayBuffer(id)))
  }

  val result = inputMap.map {
    case (id, list) =>
      val m = mutable.Map[Int, Int]()
      list.foreach(o =>
        reverseMap(o).foreach(k => if (m.contains(k)) m.update(k, m(k)+1) else m.put(k, 1)))
      val res = m.toList.filter(o => o._2 >= 3 && o._1 != id).map(o => (id, o._1))
      res
  }.flatten

  println(result)
}

【讨论】:

  • 这确实完成了工作。不过,我正试图以一种实用的方式来解决这个问题。总的来说,我还是函数式编程和 scala 的新手,但是使用可变映射不会被认为是函数式的吗?
  • 是的,突变使它成为非功能性代码。我很快就会用不可变代码更新答案。
  • 如果列表中整数的范围仅限于一些已知的小范围(例如 16 位值),您可以通过不使用映射而是使用简单、足够大小的数组来进一步优化。
  • 为了将这个想法转化为纯功能且高性能的代码,您需要依赖persistent data structures。如果 scala 有野心支持纯函数式编程,它应该在其标准库中提供它们。
猜你喜欢
  • 1970-01-01
  • 2010-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-29
  • 1970-01-01
  • 2016-05-21
  • 2015-04-30
相关资源
最近更新 更多