我有一个Scala代码,用于计算一组字符串之间的相似性,并提供所有唯一字符串。
val filtered = z.reverse.foldLeft((List.empty[String],z.reverse)) {
case ((acc, zt), zz) =>
if (zt.tail.exists(tt => similarity(tt, zz) < threshold)) acc
else zz :: acc, zt.tail
}._1
我会试着解释一下这里发生了什么:
这使用了反转输入数据的折叠,从空字符串(累积结果)和剩余输入数据(反向)开始(比较 - 我将其标记为zt为“z-tail”)。
然后折叠循环遍历数据,检查每个条目与剩余数据的尾部(因此它不会与自身或任何早期条目进行比较)
如果匹配,则只允许通过现有累加器(标记为acc),否则,将当前条目(zz)添加到累加器。这个更新的累加器与“剩余”字符串(zt.tail)的尾部配对,以确保要比较的缩减集。
最后,我们最终得到一对列表:所需的剩余字符串和一个空列表(没有字符串可供比较),因此我们将第一个作为结果。
问题就像在第一次迭代中一样,如果第1,第4和第8个字符串相似,我只得到第一个字符串。而不是它,我应该得到一组(第1,第4,第8),然后如果第2,第5,第14和第21弦相似,我应该得到一组(第2,第5,第14,第21)。
答案 0 :(得分:0)
如果我理解正确 - 你希望结果是List[List[String]]
类型,而不是你现在得到的List[String]
- 每个项目都是类似字符串的列表(对吗?)。 / p>
如果是这样 - 我无法看到对您的实现进行微不足道的更改,因为相似的值丢失(当您输入if(true)分支并返回时) acc
- 你跳过一个项目,你永远不会再看到#34;
我能想到的两种可能的解决方案:
根据您的想法,但使用(acc, zt, scanned)
形式的3-Tuple作为foldLeft
结果类型,其中添加的scanned
是已扫描的列表项目。这样我们就可以在找到一个不具有类似元素的元素时再引用它们:
val filtered = z.reverse.foldLeft((List.empty[List[String]],z.reverse,List.empty[String])) {
case ((acc, zt, scanned), zz) =>
val hasSimilarPreceeding = zt.tail.exists { tt => similarity(tt, zz) < threshold }
val similarFollowing = scanned.collect { case tt if similarity(tt, zz) < threshold => tt }
(if (hasSimilarPreceeding) acc else (zz :: similarFollowing) :: acc, zt.tail, zz :: scanned)
}._1
一个可能更慢但更简单的解决方案就是groupBy
类似的字符串组:
val alternative = z.groupBy(s => z.collect {
case other if similarity(s, other) < threshold => other
}.toSet ).values.toList
所有这些假设函数:
f(a: String, b: String): Boolean = similarity(a, b) < threshold
具有交换性和传递性,即:
f(a, b) && f(a. c)
表示f(b, c)
f(a, b)
当且仅当f(b, a)
测试我使用的两种实现:
// strings are similar if they start with the same character
def similarity(s1: String, s2: String) = if (s1.head == s2.head) 0 else 100
val threshold = 1
val z = List("aa", "ab", "c", "a", "e", "fa", "fb")
这两个选项产生相同的结果:
List(List(aa, ab, a), List(c), List(e), List(fa, fb))