处理Scala中的元组列表 - 第3部分

时间:2018-02-15 16:16:55

标签: scala list loops pattern-matching tuples

我是Scala的新手,并试图了解如何处理元组列表,因此我创建了一个虚构的人员列表:

val fichier : List[(String, Int)] = List(("Emma Jacobs",21), ("Mabelle Bradley",53), ("Mable Burton",47), ("Ronnie Walton",41), ("Bill Morton",36), ("Georgia Bates",30), ("Jesse Caldwell",46), ("Jeffery Wolfe",50), ("Roy Norris",18), ("Ella Gonzalez",48))

我试图孤立那些年龄都很大的人:

  def separate(list: List[(String, Int)]) : List[(String, Int)] =//(List[(String, Int)] , List[(String, Int)]) =
list match {
 case (name, age)::t if age%2==0 => {(name, age)::separate(t)}
 case (name, age)::t if age%2!=0 => Nil
 }

结果是一个空列表。

我做错了什么?

注意:我正在尝试编写自己的功能来完成工作,而不是使用scala预先存在的方法

4 个答案:

答案 0 :(得分:4)

  

我的问题是:为什么它找到单位而不是列表?

因为

val liste = (name, age)::separate(t)

是一项任务。赋值具有返回类型的单位,如果要返回该值,请执行

case (name, age)::t if age%2==0 => {(name, age)::separate(t)}

作为一种让老年人变老的方法,我建议你使用收集。所以,

val fichier : List[(String, Int)] = List(("Emma Jacobs",21), ("Mabelle Bradley",53), ("Mable Burton",47), ("Ronnie Walton",41), ("Bill Morton",36), ("Georgia Bates",30), ("Jesse Caldwell",46), ("Jeffery Wolfe",50), ("Roy Norris",18), ("Ella Gonzalez",48))

fichier.collect {
   case (name, age) if age % 2 == 0 => (name, age)
}

或者您可以使用过滤器来避免再次声明您的元组:

fichier.filter(_._2 % 2 == 0)
res1: List[(String, Int)] = List((Bill Morton,36), (Georgia Bates,30), (Jesse Caldwell,46), (Jeffery Wolfe,50), (Roy Norris,18), (Ella Gonzalez,48))
  

我做错了什么?

另一件事我在这里看错了,为什么你得到一个空列表是你匹配整个列表本身。所以当你这样做时

list match {
 case (name, age)::t if age%2==0 => {val liste = (name, age)::separate(t)}
 case (name, age)::t if age%2!=0 => Nil
 }

(name, age)只是列表的头部。这就是Emma Jacobs,21。所以匹配属于第二种情况,你会得到一个空列表,因为你没有调用separate(t)来继续递归。但是,如果您尝试在没有任何其他更改的情况下执行此操作,则会收到错误:

scala>  def separate(list: List[(String, Int)]) : List[(String, Int)] =//(List[(String, Int)] , List[(String, Int)]) =
     | list match {
     |  case (name, age)::t if age%2==0 => {(name, age)::separate(t)}
     |  case (name, age)::t if age%2!=0 => separate(t)
     |  }
separate: (list: List[(String, Int)])List[(String, Int)]

scala> separate(fichier)
scala.MatchError: List() (of class scala.collection.immutable.Nil$)
  at .separate(<console>:12)
  at .separate(<console>:13)
  at .separate(<console>:13)
  at .separate(<console>:13)
  at .separate(<console>:13)
  at .separate(<console>:13)
  at .separate(<console>:13)
  ... 43 elided

因为你还没有处理过这个不是列表头部的情况,你可以很容易地做到这一点:

scala>  def separate(list: List[(String, Int)]) : List[(String, Int)] =//(List[(String, Int)] , List[(String, Int)]) =
     | list match {
     |  case (name, age)::t if age%2==0 => {(name, age)::separate(t)}
     |  case (name, age)::t if age%2!=0 => separate(t)
     |  case Nil => Nil
     |  }
separate: (list: List[(String, Int)])List[(String, Int)]

scala> separate(fichier)
res2: List[(String, Int)] = List((Bill Morton,36), (Georgia Bates,30), (Jesse Caldwell,46), (Jeffery Wolfe,50), (Roy Norris,18), (Ella Gonzalez,48))

事情会像你期望的那样奏效。但是,您在这里进行了大量的递归,因此您可能希望使用tailrec注释该方法并进行适当更新以避免大型列表中的任何堆栈溢出。或者更好的是,完全放弃递归并简化代码以使用.filter

此外,如果单独表示您要整体尝试做什么,您可以查看partition方法以获得偶数和奇数编号的人:

val (evens, odds) = fichier.partition(_._2 % 2 == 0)

答案 1 :(得分:2)

在实践中,我也建议过滤,因为它更紧凑,没有神秘感。

fichier.filter {case (name, age) => (age %2 == 0)}

因为在RHS上没有使用名称(右侧,大部分时间:等号右边,这里:右边是case表达式)),我们可以指出:

fichier.filter {case (_, age) => (age %2 == 0)}

更紧凑的是使用位置值,但它更少自我记录。与具有位置的每个其他元素(如List,Vector,String ...)相比,计数从1开始,而不是0.

fichier.filter (_._2 %2 == 0)

但是,您提到的练习是自己实施,这是一个合理的学习过程。那么为什么不一劳永逸地实施过滤器呢?

让我们开始具体,例如,仅使用第二个元素int过滤touples。

停止阅读,先试试自己。

在这种情况下,什么是过滤器?

 fichier.filter {case (name, age) => (age %2 == 0)}

它在异类元组列表上运行,但我们现在不喜欢亚当和夏娃的开始,所以让我们把List作为一个额外的参数。编写一个基本的List实现也不会太难,但是现在......我们从一个特定的元组开始,逐步更加通用。该列表是一个元组列表,该函数接受一个I​​nt并返回一个布尔值。获取Int并返回布尔值的函数如下所示:f:(Int =&gt; Boolean))

scala> def tfilter (tl: List [(String, Int)], f: (Int => Boolean)) : List[(String, Int)] = tl match {
     |     case Nil => Nil
     |     case t :: tt => if (f (t._2)) t :: tfilter (tt, f) else tfilter (tt, f) 
     | }
tfilter: (tl: List[(String, Int)], f: Int => Boolean)List[(String, Int)]

该方法在某种程度上是空的。它接受List,取第一个元素t,用f(t._2)测试它(我们知道元组的第二部分是Int,并且函数对给定的Int返回true或false。如果为true,则返回元素和列表的其余部分,tt,通过相同的方式过滤。因此我们必须传递函数。

调用:

scala> tfilter (fichier, (_ % 2 == 0))
res107: List[(String, Int)] = List((Bill Morton,36), (Georgia Bates,30), (Jesse Caldwell,46), (Jeffery Wolfe,50), (Roy Norris,18), (Ella Gonzalez,48))

内括号定义我们的功能。它是一个占位符,由函数填充,因为它由参数调用,并返回该值上的模2是否等于0.这是两个符号变体。

tfilter (fichier, (i:Int => i % 2 != 0))
tfilter (fichier, ((i:Int) => i % 2 != 0))

但是,如果我们的下一个元组是反过来的,并且在第二个位置有int,那该怎么办?位置1在我们的方法中是硬编码的,这是一件坏事。

再次尝试自己。更改参数序列很复杂。我们最好立即参与我们期望的类型的参数化,结果,我们返回的是什么,因此,谓词,返回布尔值的函数需要什么。

我们只是将一个元组(String,Int)命名为X:

scala> def tfilter (tl: List [X], f: (X => Boolean)) : List[X] = tl match {
     |     case Nil => Nil
     |     case t :: tt => if (f (t)) t :: tfilter (tt, f) else tfilter (tt, f) 
     | }
<console>:8: error: not found: type X
       def tfilter (tl: List [X], f: (X => Boolean)) : List[X] = tl match {
                                                            ^

3标题中的变化,以及测试f(t)上的变化。但REPL并不高兴。

我们必须键入注释整个方法:

scala> def tfilter [X] (tl: List [X], f: (X => Boolean)) : List[X] = tl match {
     |     case Nil => Nil
     |     case t :: tt => if (f (t)) t :: tfilter (tt, f) else tfilter (tt, f) 
     | }
tfilter: [X](tl: List[X], f: X => Boolean)List[X]

现在REPL很高兴。无论X是什么,我们都承诺传递一个X列表和一个方法,它可以将一个X转换为一个布尔值,我们将返回一个X的列表。

我们现在可以迈出小步。编写一个方法,它采用元组和函数Int =&gt;布尔值,并在元组的第0部分中执行函数:

def tuple2bool (si: (String, Int), f: (Int => Boolean)) : Boolean = f (si._2)

非常简单,除了未使用的符号。对单个元组的微小测试:

tuple2bool (fichier(0), (i:Int) => (i % 2 == 0))
Boolean = false

tuple2bool (fichier(5), (i:Int) => (i % 2 == 0))
Boolean = true

我们如何在整个列表中调用它? 好吧,首先我们介绍两个参数列表的概念。

def (a: Int, b:Int, c:Int) = ... 

几乎相同
def (a: Int) (b:Int, c:Int) = ... 

该功能可以在a,b,c内部操作。但是,对于implicits,只能隐含整个参数列表,因此如果我们只想隐藏一些元素,我们将它们分组到一个自己的参数列表中。对于编译器和编码器,更容易弄清楚,遗漏了什么。 对我来说,在不同的参数列表中分离List和函数调用也更容易:

def tfilter [X] (tl: List [X]) (f: (X => Boolean)) : List[X] = tl match {
    case Nil => Nil
    case t :: tt => if (f (t)) t :: tfilter (tt)(f) else tfilter (tt)(f) 
}

使用两个参数列表进行函数调用有点可怕,但要么你很少这样做,要么你很快就习惯了。现在,我们打电话给我们的

tfilter (fichier) (si => tuple2bool (si, _ % 2 == 0 ))
List[(String, Int)] = List((Bill Morton,36), (Georgia Bates,30), (Jesse Caldwell,46), (Jeffery Wolfe,50), (Roy Norris,18), (Ella Gonzalez,48))

第一个参数是我们的fichier,第二个参数是一个函数,它接受一个si,一个标识符来绑定我们的tuple2bool param 1,然后是要在Int上执行的函数,它将被提取。 _是未绑定的Int的占位符。

但我们不想一遍又一遍地写这样的中间函数。我们可以马上写:

 tfilter (fichier) (si => (si._2 % 2 == 0 ))
 List[(String, Int)] = List((Bill Morton,36), (Georgia Bates,30), (Jesse Caldwell,46), (Jeffery Wolfe,50), (Roy Norris,18), (Ella Gonzalez,48))

完全忘记了tuple2bool-fun。

我们现在非常灵活。如果我们决定写一个Personne类,并更改订单年龄/名称:

val personnes: List [Personne] = List (Personne(21, "Emma Jacobs"), Personne(54, "Mabelle Bradley")) 
personnes: List[Personne] = List(Personne(21,Emma Jacobs), Personne(54,Mabelle Bradley))

tfilter (personnes) (p => (p.age % 2 == 0 ))
List[Personne] = List(Personne(54,Mabelle Bradley))

我缩小了列表以获得较小的结果并改变了Mabelle的年龄,从而对过滤产生了真正的影响。

我们现在不仅可以使用int函数进行过滤:

tfilter (personnes) (p => (p.name.contains ('a')))
List[Personne] = List(Personne(21,Emma Jacobs), Personne(54,Mabelle Bradley))
与fichier一样,它的工作方式相同:

tfilter (fichier) (p => (p._1.contains ('a')))

我们可以嵌套我们的函数,通过第二个过滤器传递元组:

tfilter (tfilter (fichier) (p => (p._1.contains ('a'))))(p => p._2 < 30)
List[(String, Int)] = List((Emma Jacobs,21))

The original, sequential style in the scala libs, is surely more elegant, better to parse for us coders, mainly: 

fichier.filter (p => (p._1.contains ('a'))).filter (p => p._2 < 30)

至少,我们可以采用之前介绍过的'case'语法:

tfilter (fichier)({case (name, _) => (name.length > 13)})

答案 2 :(得分:0)

您需要使用match返回结果,但您要分配值。

除非您需要其他内容liste,否则请完全删除该作业。

case (name, age)::t if age%2==0 => (name, age)::separate(t)

否则返回它:

case (name, age)::t if age%2==0 => {
     val liste = (name, age)::separate(t)
     liste
     }

答案 3 :(得分:0)

你的功能不完整,因为当年龄为奇数时,它会终止,但它应该在列表中进一步遍历。

试试这个:

def separate(list: List[(String, Int)]) : List[(String, Int)] = list match {
    case (name, age)::t if age%2==0 => (name, age)::separate(t)
    case (name, age)::t if age%2!=0 => separate(t)
    case Nil => Nil
}

无需在花括号中添加“(name,age):: separate(t)”。