Scala在列表中找到重复项

时间:2018-03-01 21:54:24

标签: scala list duplicates

所以我想说我有一个这样的清单:

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

4 个答案:

答案 0 :(得分: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输出了一个列表,我们flatten它(list.flatMap {...})。

因此我们得到此列表:List(Frank, Peter, Frank)

然后,为了找出列表是否有重复,一个简单的方法是使用distinct转换列表,只保留每个元素的一个实例并将其与之前生成的列表进行比较:

names.distinct.length == result.length

答案 1 :(得分:1)

首先,从查看代码开始,我必须指出在同一个列表中包含不同类型的非常不良做法。 PersonFeatures特质对此没有任何帮助。我建议你制作Person 案例类,而不是List两种完全不同的类型(NameAge)。除此之外,这将改善数据结构并简化解决方案。 (如果你必须采用这种方式,那么支持异构列表 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

答案 2 :(得分:0)

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

您可以将此示例转移到您的案例中吗?

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 
}

对于我的例子,T是整数列表(内部列表),A只是T的属性,由T到A的函数选择。如果我们对该函数的结果进行分组,如果属性是唯一的,则大小应保持不变。

unique (lb, (l:List[Int]) => l(0))

这里的函数是索引到内部的int列表。既然我们知道人/年龄/姓名定义,我们也可以测试它:

unique (datlist, (p:Person) => p.list(0))
// res254: Boolean = false

unique (datlist, (p:Person) => p.list(1))
// res255: Boolean = true

我有点不高兴,解决方案没有透露,名称不是唯一的,而年龄是 - 不是结果是假的,而是我们用列表索引访问它,而不是属性名称。但是也许有一个很好的理由,就像那样写,这就是主题。但据我所知,具有以不同顺序初始化的属性的Person会使此方法失败。所以我们真正需要的是一个从Person到Name的方法,而不是从Person到Person.list中的第一个PersonAttribute。

这可以通过模式匹配来尝试,但我一般不相信Person设计。难道没有名字的人真的可能吗?有两个名字的人?或者如何从您的设计中防止这种情况?

然而,忽略对设计的批评,我们可以实现一个冗长的解决方案,它处理重新排序的功能(我称之为属性):

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

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

答案 3 :(得分:0)

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

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

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