【问题标题】:How to group items from an unsorted sequence with custom logic?如何使用自定义逻辑对未排序序列中的项目进行分组?
【发布时间】:2018-11-01 02:23:51
【问题描述】:

遇到了麻烦...我什至不知道从哪里开始。

我有一个未排序的对象列表:

myList = (A, Z, T, J, D, L, W...)

这些对象有不同的类型,但都共享相同的父类型。

一些对象通过自定义业务逻辑相互“匹配”:

A.matches(B) = True

A.matches(C) = False

(编辑:匹配是可交换的。X.matches(Y) = Y.matches(X)

我正在 Scala 中寻找一种方法来对匹配的对象进行分组,所以我最终得到了类似的结果:

myMatches = [ [A,B,C], [D,Z,X], [H], ...]

这里有一个问题——匹配不是传递的

A.matches(B) = True

B.matches(C) = True

A.matches(C) = False <---- A and C can only be associated through their matches to B

我仍然希望 [A,B,C] 被分组,即使 AC 不直接匹配。

有没有一种简单的方法可以将所有相互匹配的东西组合在一起?是否有此类问题的名称,以便我可以 Google 更多有关它的信息?

【问题讨论】:

  • 这是一个奇怪的要求。我认为您需要构造一个传递函数来替换matches(例如matches2),该函数封装了使这些对象属于同一组的原因,因此如果A matches2 BB matches2 C,则A matches2 C 紧随其后。此外,为了保持一致,一组中的任何东西都必须与其他组匹配失败。即使将元素与现有组的成员与您当前的 matches 进行比较也会失败(如果在 B 出现之前比较它们,AC 可能最终会出现在不同的组中)。

标签: scala grouping


【解决方案1】:

在假设下,

  • 匹配是可交换的,即如果A匹配B,那么B匹配A
  • 如果 A 匹配 B,B 匹配 C,C 匹配 D,则它们都应该在同一个组中。

您只需要在匹配图中搜索(DFSBFS),该匹配图从尚未在组中的每个元素开始。您在一个搜索表单中找到的元素恰好是一组。

下面是一些示例代码:

import scala.collection.mutable

case class Element(name: Char) {
  def matches(other: Element): Boolean = {
    val a = name - 'A'
    val b = other.name - 'A'
    a * 2 == b || b * 2 == a
  }

  override def toString: String = s"$name (${name - 'A'})"
}

def matchingGroups(elements: Seq[Element]): Seq[Seq[Element]] = {
  val notInGroup: mutable.Set[Element] = elements.to[mutable.Set]

  val groups: mutable.ArrayBuilder[Seq[Element]] = mutable.ArrayBuilder.make()
  val currentGroup: mutable.ArrayBuilder[Element] = mutable.ArrayBuilder.make()

  def fillCurrentGroup(element: Element): Unit = {
    notInGroup -= element
    currentGroup += element
    for (candidate <- notInGroup) {
      if (element matches candidate) {
        fillCurrentGroup(candidate)
      }
    }
  }

  while (notInGroup.nonEmpty) {
    currentGroup.clear()
    fillCurrentGroup(notInGroup.head)
    groups += currentGroup.result()
  }

  groups.result()
}

matchingGroups('A' to 'Z' map Element) foreach println

这会找到以下组:

WrappedArray(M (12), G (6), D (3), Y (24))
WrappedArray(R (17))
WrappedArray(K (10), F (5), U (20))
WrappedArray(X (23))
WrappedArray(V (21))
WrappedArray(B (1), C (2), E (4), I (8), Q (16))
WrappedArray(H (7), O (14))
WrappedArray(L (11), W (22))
WrappedArray(N (13))
WrappedArray(J (9), S (18))
WrappedArray(A (0))
WrappedArray(Z (25))
WrappedArray(P (15))
WrappedArray(T (19))

如果matches 关系是不可交换的,这个问题就有点复杂了。在这种情况下,在搜索过程中,您可能会遇到几个不同的组,这是您在之前的搜索中发现的,您必须将这些组合并为一个。使用disjoint-set data structure 表示组可能有助于更快地合并。

【讨论】:

  • 像魅力一样工作。谢谢!
【解决方案2】:

这是一个基于 Scala Sets 的函数式解决方案。它假定未排序的对象列表不包含重复项,并且它们都继承自某种类型的 MatchT,其中包含适当的 matches 方法。

此解决方案首先将所有对象分组为包含直接匹配对象的集合。然后它依次检查每个集合并将其与具有任何共同元素(非空交集)的任何其他集合组合。

def groupByMatch[T <: MatchT](elems: Set[T]): Set[Set[T]] = {
  @tailrec
  def loop(sets: Set[Set[T]], res: Set[Set[T]]): Set[Set[T]] =
    sets.headOption match {
      case None =>
        res
      case Some(h) =>
        val (matches, noMatches) = res.partition(_.intersect(h).nonEmpty)
        val newMatches = h ++ matches.flatten

        loop(sets.tail, noMatches + newMatches)
    }

  val matchSets = objs.map(x => objs.filter(_.matches(x)) + x)

  loop(matchSets, Set.empty[Set[T]])
}

这里有许多低效率的地方,所以如果性能是一个问题,那么基于可变 Maps 的非功能版本可能会更快。

【讨论】:

    猜你喜欢
    • 2019-01-10
    • 2011-09-29
    • 1970-01-01
    • 1970-01-01
    • 2015-06-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-24
    相关资源
    最近更新 更多