Scala在列表中找到重复项

问题描述 投票:1回答:4

所以假设我有一个这样的列表:

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
scala list duplicates
4个回答
3
投票

您可以使用以下代码修改代码以使其编译:

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

1
投票

首先,从查看代码开始,我必须指出在同一个列表中使用不同类型是非常糟糕的做法。 Person特质无助于你。我建议你把List作为一个案例类而不是两个完全不同类型的NameAgeShapeless)。除此之外,这将改善数据结构并简化解决方案。 (如果你必须采用这种方式,像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

0
投票

没人知道,人和年龄是什么。 :)

你能把这个例子转移到你的案子吗?

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)
}

但是如何使用缺少的功能,没有姓名或年龄的人,或者两个名字,两个年龄?我想很明显,这个设计需要重新思考。


0
投票

您可以使用此类地图将删除重复键的功能将您的列表转换为人名的地图。

我同意Mike Allen的观点,你不应该使用不同类型的列表,而应该使用案例类。然后,您可以编写如下函数:

qazxswpoi
© www.soinside.com 2019 - 2024. All rights reserved.