比较两个集合的所有元素并生成第三个集合

Compare all elements of two collections and produce a third one

我正在寻找一种比较两个 Scala 集合的元素并根据条件生成第三个集合的好方法。我可以牺牲代码外观来换取性能。

假设如下:

case class Item(name: String, category: String, code: String, price: Int, freeItem: Option[Item])

val parentItems = Seq(
  Item("name_1", "category_A", "code_A", 100, None),
  Item("name_2", "category_B", "code_B", 100, None),
  Item("name_3", "category_C", "code_C", 100, None)
)

val childItems = Seq(
  Item("name_4", "category_A", "code_A", 100, None),
  Item("name_5", "category_C", "code_C", 100, None)
)

def isChild(i1: Item, i2: Item): Boolean = {
  i1.name != i2.name &&
    i1.category == i2.category &&
    i1.code == i2.code
}

val parentsWithChildren: Seq[Item] = (
  for {
    i1 <- parentItems;
    i2 <- childItems
  } yield {
    if (isChild(i1, i2)) Some(i1.copy(freeItem = Some(i2.copy())))
    else None
  }).filter(_.isInstanceOf[Some[_]]).map(_.get)

上面的代码片段将产生以下结果,这是预期的:

Seq(
  Item("name_1", "category_A", "code_A", 100, 
    Some(Item("name_4", "category_A", "company_A", 100, None))),
  Item("name_3", "category_C", "code_C", 100, 
    Some(Item("name_5", "category_C", "company_C", 100, None)))
)

我知道上面的代码有漏洞但是没关系。我要找的是:

  1. 有什么方法可以避免 if (isChild(i1, i2)) Some(i1.copy(freeItem = Some(i2.copy()))) else None 和结果 .filter(_.isInstanceOf[Some[_]]).map(_.get)?偏函数是一个选项吗?

  2. 我在这里使用嵌套循环,我会在 Java 中做类似的事情。还有其他方法可以解决吗?一开始我以为我 zip 但显然是行不通的,至少它自己行不通。

提前致谢!

关于 1,返回选项似乎是正确的做法,但您可以用展平替换过滤器:

(for {
    i1 <- parentItems;
    i2 <- childItems
  } yield {
    if (isChild(i1, i2)) Some(i1.copy(freeItem = Some(i2.copy())))
    else None
  }).flatten

至于2,如果你真的需要一个嵌套循环并且你担心性能,你的代码对我来说看起来不错。也许有人有更好的主意。

我很想尝试这样的事情:

val parentsWithChildren: Seq[Item] =
  for {
    parent <- parentItems
    child <- childItems if isChild(parent, child)
  }
    yield parent.copy(freeItem = Some(child))

希望对您有所帮助!

一种略微非正统的方法,但我认为这种方法性能更高,将连接 categorycode 以形成具有 groupBy 的映射的键:

val childItemMap: Map[String, Iterable[Item]] = childItems
   .groupBy(item => s"${item.category}-${item.code}")

然后您只需遍历 parentItems,连接 它们的 类别和代码,并将映射值复制到父项中:

val parentsWithChildren: Seq[Item] = parentItems
   .map { p => 
      p.copy(freeItem = childItemMap.get(s"${p.category}-${p.code}").map(_.head))
   }

您的数据模型表明只有一个可能的匹配项,因此我只获取第一个(并且可能是唯一的)匹配值。我使用的是 head 而不是 headOption,因为我用 groupBy 生成了 Map,因此永远不会有空值。结果是 Seq[Item],其中子项匹配复制到父项中。