Scala在列表中找到重复项
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala在列表中找到重复项相关的知识,希望对你有一定的参考价值。
所以假设我有一个这样的列表:
List(Person(List(Name("Frank"), Age(50))),Person(List(Name("Peter"), Age(40))),Person(List(Name("Frank"), Age(20))))
如果所有名称都是唯一的,我如何编写一个返回true的函数,如果有重复的名称,我该如何返回false。例如,这将返回true:
List(Person(List(Name("Frank"), Age(50))),Person(List(Name("Peter"), Age(40))),Person(List(Name("Bob"), Age(20))))
上面的示例List将返回false。
我试过这个:
sealed abstract class PersonFeatures
case class Person(list: List[PersonFeatures]) extends PersonFeatures
case class Age(num: Int) extends PersonFeatures
case class Name(value: String) extends PersonFeatures
val datlist = List(Person(List(Name("Frank"), Age(50))),Person(List(Name("Peter"), Age(40))),Person(List(Name("Frank"), Age(20))))
def findDoubles(checklist: List[Person]): List[Person] = {
checklist.foldLeft(List[Person]()) {
case (uniquePersons, Person(List(name, age))) if uniquePersons.contains(Person(List(name, _))) => {
throw new IllegalArgumentException("Double name found");
}
case (uniquePersons, person) => uniquePersons :+ person
}
}
val result = findDoubles(datlist)
println(result)
但它抛出了这个错误:
type mismatch;
found : List[Any]
required: Playground.this.PersonFeatures
您可以使用以下代码修改代码以使其编译:
def findDoubles(checklist: List[Person]): List[String] = {
checklist.foldLeft(List[String]()) {
case (uniquePersons, Person(List(Name(name), _))) if uniquePersons.contains(name) =>
throw new IllegalArgumentException("Double name found");
case (uniquePersons, Person(List(Name(name), _))) => name :: uniquePersons
}
}
但对您的要求来说似乎相当复杂。
这是一个替代方案:
case class Name(name: String)
case class Age(age: Int)
case class Person(smthg: List[Any])
val list = List(
Person(List(Name("Frank"), Age(50))),
Person(List(Name("Peter"), Age(40))),
Person(List(Name("Frank"), Age(20))))
val names = list.flatMap {
case Person(smthg) => smthg.collect { case Name(name) => name }
}
println(names)
> List(Frank, Peter, Frank)
println(names.distinct.length == result.length)
> false
首先,我们从所有元素中提取名称:
val names = list.flatMap {
case Person(smthg) => smthg.collect { case Name(name) => name }
}
smthg.collect
适用于List(Name("Frank"), Age(50))
。它过滤Name类型的元素(为了过滤Age元素)并从Age(年龄)中提取实际年龄。
由于smthg.collect
输出一个List,我们flatten
它(list.flatMap {...}
)。
因此我们得到这个列表:List(Frank, Peter, Frank)
然后,为了找出列表是否有重复,一个简单的方法是使用distinct
转换列表,names.distinct.length == result.length
只保留每个元素的一个实例并将其与之前生成的列表进行比较:
PersonFeatures
首先,从查看代码开始,我必须指出在同一个列表中使用不同类型是非常糟糕的做法。 Person
特质无助于你。我建议你把List
作为一个案例类而不是两个完全不同类型的Name
(Age
和Shapeless)。除此之外,这将改善数据结构并简化解决方案。 (如果你必须采用这种方式,像List[List[Any]]
这样支持异构列表的库是比使用import scala.annotation.tailrec
final case class Person(name: String, age: Int)
val datlist = List(Person("Frank", 50), Person("Peter", 40), Person("Frank", 20))
// Determine if people names are unique.
def haveUniqueNames(pl: List[Person]): Boolean = {
// Helper function.
@tailrec
def headUnique(rem: List[Person], seen: Set[String]): Boolean = {
// If we've reached the end of the list, return true; we didn't find a duplicate.
if(rem.isEmpty) true
// Otherwise, if the person at the head of the list has a name we've already seen,
// return false.
else if(seen.contains(rem.head.name)) false
// Otherwise, add the head person's name to the set of names we've seen,
// and perform another iteration starting with the next person.
else headUnique(rem.tail, seen + rem.head.name)
}
// Start off with the full list and an empty set.
headUnique(pl, Set.empty)
}
// Check if names are unique.
haveUniqueNames(datlist)
更好的方法。)
那么,我就是这样做的:
datlist.map(_.name).distinct.size == datlist.size
或者,如果效率不如简洁那么重要:
val la = List (List (1, 2), List (3, 2), List (1, 4))
// la: List[List[Int]] = List(List(1, 2), List(3, 2), List(1, 4))
val lb = List (List (1, 2), List (3, 2), List (4, 1))
// lb: List[List[Int]] = List(List(1, 2), List(3, 2), List(4, 1))
la.groupBy (_(0)).size == la.size
// res229: Boolean = false
lb.groupBy (_(0)).size == lb.size
// res230: Boolean = true
没人知道,人和年龄是什么。 :)
你能把这个例子转移到你的案子吗?
def unique [T, A] (l: List[T], f: T => A): Boolean = {
l.groupBy (element => f(element)).size == l.size
}
好。这可以抽象出来。有一些东西,根据这些东西的谓词,我们想找到唯一性。
unique (lb, (l:List[Int]) => l(0))
对于我的例子,T是整数列表(内部列表),A只是T的属性,由T到A的函数选择。如果我们对该函数的结果进行分组,则大小应为如果属性是唯一的,则不变。
unique (datlist, (p:Person) => p.list(0))
// res254: Boolean = false
unique (datlist, (p:Person) => p.list(1))
// res255: Boolean = true
这里的函数是索引到内部的int列表。既然我们知道人/年龄/名称定义,我们也可以测试它:
unique (datlist, (p:Person) => p.list.filter (pf=> isFeatureByName (pf, "Name")))
//res259: Boolean = false1def isFeatureByName (pf: PersonFeatures, featurename: String) = (pf, featurename) match {
case (Age (_), "Age") => true
case (Name (_), "Name") => true
case (Person (_), "Person") => true
case _ => false
}
unique (datlist, (p:Person) => p.list.filter (pf=> isFeatureByName (pf, "Age")))
// res258: Boolean = true
unique (datlist, (p:Person) => p.list.filter (pf=> isFeatureByName (pf, "Name")))
// res259: Boolean = false
我有点不高兴,解决方案没有透露,名称不是唯一的,而年龄是 - 不是结果是假的,而是我们用列表索引访问它,而不是属性名称。但是也许有一个很好的理由,就像那样写,这就是主题。但据我所知,具有以不同顺序初始化的属性的Person会使此方法失败。所以我们真正需要的是一个从Person到Name的方法,而不是从Person到Person.list中的第一个PersonAttribute。
这可以通过模式匹配来尝试,但我一般都不相信Person设计。难道没有名字的人真的可能吗?有两个名字的人?或者如何防止您的设计出现这种情况?
但是,忽略对设计的批评,我们可以实现一个详细的解决方案,它处理重新排序的功能(我称之为属性):
final case class Person(name: String, age: Int)
val datlist = List(Person("Frank", 50), Person("Peter", 40), Person("Frank", 20))
// Determine if people names are unique.
def haveUniqueNames(personList: List[Person]): Boolean = {
val personMap = personList.map(person => person.name -> person).toMap
(personMap.size == personList.size)
}
但是如何使用缺少的功能,没有姓名或年龄的人,或者两个名字,两个年龄?我想很明显,这个设计需要重新思考。
您可以使用此类地图将删除重复键的功能将您的列表转换为人名的地图。
我同意Mike Allen的观点,你不应该使用不同类型的列表,而应该使用案例类。然后,您可以编写如下函数:
qazxswpoi
以上是关于Scala在列表中找到重复项的主要内容,如果未能解决你的问题,请参考以下文章