我是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预先存在的方法
答案 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实现也不会太难,但是现在......我们从一个特定的元组开始,逐步更加通用。该列表是一个元组列表,该函数接受一个Int并返回一个布尔值。获取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))
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)”。