代码之家  ›  专栏  ›  技术社区  ›  Yuva

Scala中元组列表的工作-第3部分

  •  1
  • Yuva  · 技术社区  · 7 年前

    我是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 回复  |  直到 7 年前
        1
  •  4
  •   EdgeCaseBerg    7 年前

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

    因为

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

    是一项任务。赋值的返回类型为unit,如果要返回该值,请继续并执行以下操作

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

    作为一种让老年人保持平衡的方法,我建议你使用collect。所以

    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) 只是你名单上的头号人物。这就是艾玛·雅各布斯,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)

        2
  •  2
  •   user unknown    7 年前

    实际上,我也建议使用过滤器,因为它更紧凑,不神秘。

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

    因为RHS上没有使用名称( 右侧 ,大多数情况下:等号右侧,此处:大小写表达式右侧),我们可以表示:

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

    更紧凑的方法是使用位置值,但它的自文档化程度较低。与其他具有位置的元素(如列表、向量、字符串…)相比。。。,这里的计数从1开始,而不是0。

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

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

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

    停止阅读,先试试自己。

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

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

    它在一个异构元组列表上运行,但我们现在不喜欢从Adam和Eve开始,所以让我们将列表作为一个附加参数。编写基本列表实现也不会太难,但现在。我们从一个特定的元组开始,逐步使其更一般。该列表是元组列表,函数接受Int并返回布尔值。接受Int并返回布尔值的函数如下所示: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.\u 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)上有1处变化。但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=>布尔值,并对元组的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上运行。然而,对于隐式,只有整个参数列表是隐式的,所以如果我们只想隐式一些元素,我们就将它们分组到自己的参数列表中。对于编译器和编码器来说,更容易找出遗漏了什么。 在这里,对我来说,在不同的参数列表中分离列表和函数调用也更容易:

    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,第二个是a函数,它接受一个si,一个标识符来绑定tuple2bool参数1,然后是要在Int上执行的函数,该函数将被提取出来。\u是未绑定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的乐趣。

    我们现在非常灵活。如果我们决定,写一个班级人员,并更改订单年龄/姓名:

    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)})
    
        3
  •  0
  •   airudah    7 年前

    您需要返回一个结果 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
         }
    
        4
  •  0
  •   Akshay Batra    7 年前

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

    尝试以下操作:

    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
    }
    

    无需在花括号中添加“(姓名、年龄)::separate(t)”。