【问题标题】:Work on list of tuples in Scala - part 3在 Scala 中处理元组列表 - 第 3 部分
【发布时间】:2018-07-26 11:21:28
【问题描述】:

我是 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 预先存在的方法

【问题讨论】:

  • 删除val liste =
  • @slouc 但为什么呢?我认为 scala 能够识别未定义变量的类型
  • 因为您的 Operation 正在分配一个 val。如果您只是删除分配,则将返回 ` (name, age)::separate(t)` 的结果。 val x = 123 的结果是 Unit。

标签: scala list loops pattern-matching tuples


【解决方案1】:

我的问题是:为什么它会找到 Unit 而不是 List?

因为

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)

【讨论】:

  • 我误解了一点:对我来说,(name, age) 代表列表的第一个元素,t 代表剩下的元素。然后是输出(name, age)::separate(t) 分析列表的第一个元素,然后是列表中剩余的第一个元素,如果是偶数,则进入列表。递归语句重复操作并在t 中仍然没有任何内容时停止我的想法有什么问题?
  • 对不起,当我摆弄这个答案时,实际上并没有在我的控制台中运行那个单独的代码。我认为 mrmcgreg 可能有答案,尽管我还没有仔细观察。我将在控制台中运行一些代码并适当地更新我的答案。
  • @Yuva 我已经更新了我的答案以解决您的评论
【解决方案2】:

在实践中,我也建议使用过滤器,因为它更紧凑,而且不神秘。

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

由于 RHS 上不使用名称(右手边,大多数情况下:等号右侧,这里:大小写表达式的右侧)),我们可以指出:

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 实现也不会太难,但是现在......我们从一个特定的元组开始,逐步使其更通用。该列表是一个元组列表,该函数接受一个 Int 并返回一个布尔值。一个接受 Int 并返回 Boolean 的函数如下所示: f: (Int => 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 并不高兴。

我们必须对整个方法进行Type注解:

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 => Boolean,并在元组的 Int 部分(这里是第 2 部分)上执行函数:

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

【讨论】:

  • 这个周末我会这样做,下周给你更新
  • 我认为“rhs”是指“右手边”,对吗?为非英语使用者更新您的答案以使其更加明显可能会很好。
【解决方案3】:

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

除非您需要 liste 用于其他用途,否则请完全删除该分配。

case (name, age)::t if age%2==0 =&gt; (name, age)::separate(t)

否则像这样返回:

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

【讨论】:

  • 好的。我很惊讶我必须写它应该返回的内容,我认为casedoes 它自己
  • 如果您不将结果分配给一个值,它会这样做。这与构造方法的方式相同。最后一条语句应该是与方法的类型注释匹配的返回值。
  • 是的,case (name, age)::t if age%2==0 =&gt; (name, age)::separate(t) 就足够了,剩下的只是噪音和垂直空间消耗。
【解决方案4】:

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

试试这个:

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)”。

【讨论】:

    猜你喜欢
    • 2018-07-29
    • 2018-07-25
    • 2018-07-24
    • 1970-01-01
    • 2015-07-06
    • 2014-01-23
    • 2020-04-05
    • 1970-01-01
    • 2014-12-30
    相关资源
    最近更新 更多