【问题标题】:Scala pattern matching on sequences other than Lists对列表以外的序列进行 Scala 模式匹配
【发布时间】:2011-10-12 01:27:42
【问题描述】:

我有以下代码递归地对列表中的每个元素进行操作

def doMatch(list: List[Int]): Unit = list match {
  case last :: Nil  => println("Final element.")
  case head :: tail => println("Recursing..."); doMatch(tail)
}

现在,忽略此功能可通过 filter()foreach() 获得,它工作得很好。但是,如果我尝试将其更改为接受任何 Seq[Int],则会遇到问题:

  • Seq 没有​​ ::,但它确实有 +:,据我了解,这基本上是一回事。但是,如果我尝试在 head +: tail 上进行匹配,编译器会抱怨“错误:未找到:值 +:”
  • Nil 是 List 特有的,我不确定用什么替换它。如果我能解决上一个问题,我会尝试 Seq()

我认为代码应该是这样的,但它不起作用:

def doMatch(seq: Seq[Int]): Unit = seq match {
  case last +: Seq() => println("Final element.")
  case head +: tail  => println("Recursing..."); doMatch(tail)
}

编辑:这么多好的答案!我接受了敏捷钢的回答,因为他是第一个指出 :: 在我的示例中不是运算符,而是一个案例类,因此是不同的。

【问题讨论】:

  • 两个旁注:final 不允许作为标识符,编译器抱怨说,这些案例并不详尽。您可以改用:def doMatch (list: List[Int]): Unit = list match { | case last :: Nil => println ("Final element.") case head :: tail => println ("Recursing..."); doMatch (tail) case Nil => println ("only seen for empty lists") }
  • 是的,final 是一个“简化场景”的错误。我将其更改为 last 以便它可以编译,但我省略了您的 case Nil 以便评论对问题有意义。谢谢。
  • 我可以删除评论 - 没问题。这将节省新读者一些时间,找到一个更正确的问题,而不是评论,而不是他们喜欢纠正的东西,直到他们找到评论和评论的答案...... SE应该像一个维基,人们为解决方案做出贡献 - 生产文档并不那么重要,以及谁编写了什么。
  • 为什么不在this SO question 中使用类似于trycatch 的方法?本质上,使用Seq 的大小并使用headtail 来访问所需的元素。

标签: scala collections pattern-matching


【解决方案1】:

截至 2012 年 3 月的 ide,这适用于 2.10+:

  def doMatch(seq: Seq[Int]): Unit = seq match {
    case last +: Seq() => println("Final element.")
    case head +: tail  => println("Recursing..."); doMatch(tail)
  }                                               //> doMatch: (seq: Seq[Int])Unit

  doMatch(List(1, 2))                             //> Recursing...
                                                  //| Final element.

更一般地,在SeqExtractors 中为Seq 添加了两个不同的head/tail 和init/last 分解对象镜像附加/前置:

List(1, 2) match { case init :+ last => last } //> res0: Int = 2                                              
List(1, 2) match { case head +: tail => tail } //> res1: List[Int] = List(2)                                               
Vector(1, 2) match { case init :+ last => last } //> res2: Int = 2                                              
Vector(1, 2) match { case head +: tail => tail } //> res3: scala.collection.immutable.Vector[Int] = Vector(2)

【讨论】:

    【解决方案2】:

    有点作弊,但就是这样:

    def doMatch(seq: Seq[Int]): Unit = seq match {
      case Seq(x) => println("Final element " + x)
      case Seq(x, xs@_*) => println("Recursing..." + x); doMatch(xs)
    }
    

    不要问我为什么xs* 不起作用...

    【讨论】:

    • 我想知道这样的事情是否可行,但我无法让语法正常工作。你认为这是对我的问题作弊,还是在“一般情况下你不应该这样做”的意义上作弊?
    • @Zecrates: _* 表示“忽略模式的以下元素”。使用@ 语法,我给“被忽略”的部分起了一个名字,感觉就像在作弊。我认为这段代码实际上没有任何错误(当然headtail 在给定的Seq 上可能效率低下)。
    • 你也可以这样做(如果你知道你的 Seq 不为空:val Seq(x, xs@_*) = seq
    • @Jaap 如果你这样做,你会陷入无限递归。
    • yakshaver 的回答更新了:stackoverflow.com/a/19147469/707926
    【解决方案3】:

    Scala 中有两个::(读作 cons)。一个是在class List 中定义的运算符,一个是classList 的子类),表示一个以头尾为特征的非空列表。

    head :: tail 是构造函数模式,在语法上由::(head, tail) 修改而来。

    :: 是一个案例类,这意味着为它定义了一个提取器对象。

    【讨论】:

    • 我明白了,这很棘手!那么,我是否正确地说你不能对任何语句进行模式匹配,它必须是一个独立的对象(或者我们可以称之为的任何对象)?我刚刚测试过,情况似乎如此,因为我可以在 2 上进行模式匹配,但不能在 (1 + 1) 上进行模式匹配。
    • 一般来说,您不是在匹配语句,而是匹配模式。提取器是 Scala 中的语法结构,有助于定义模式。这些通常是构造函数模式。在 2 上进行模式匹配时,模式是一个常数,这是一种完全不同的模式。 1 + 1 是一个任意表达式,它不符合任何类型的模式,尽管它可能看起来像一个。
    • 值得注意的是,cons :: 运算符不会在 Vector 上工作,尽管它可以在 Seq 和 and List 上工作。因此使用 +: 更安全
    【解决方案4】:

    您实际上可以为+: 定义一个对象来完全按照您的要求进行操作:

    object +: { 
      def unapply[T](s: Seq[T]) = 
        if(s.nonEmpty)
          Some(s.head, s.tail) 
        else
          None
    }
    
    scala> val h +: t = Seq(1,2,3)
    h: Int = 1
    t: Seq[Int] = List(2, 3)
    

    然后您的代码完全按预期工作。

    这是因为h +: t 在用于模式匹配时等价于+:(h,t)

    【讨论】:

    • 注意:s.size >= 1 在 Seq 上可能会很慢,可以用 s.nonEmpty 替换(这似乎也更具描述性)。
    【解决方案5】:

    我认为标准库中没有对任意序列的模式匹配支持。不过,您可以在没有模式匹配的情况下做到这一点:

      def doMatch(seq: Seq[Int]) {
        if (seq.size == 1) println("final element " + seq(0)) else {
          println("recursing")
          doMatch(seq.tail)
        }
      }
      doMatch(1 to 10)
    

    您可以定义自己的提取器对象。见http://www.scala-lang.org/node/112

    object SEQ {
      def unapply[A](s:Seq[A]):Option[(A, Seq[A])] = {
        if (s.size == 0) None else {
          Some((s.head, s.tail))
        }
      }
    }
    
    def doMatch(seq: Seq[Int]) {
      seq match {
        case SEQ(head, Seq()) => println("final")
        case SEQ(head, tail) => {
          println("recursing")
          doMatch(tail)
        }
      }
    }
    

    【讨论】:

    • 定义EmptySeq() 能给你带来什么吗?只使用Seq() 似乎工作正常。
    【解决方案6】:

    从 Seq 到 List 的简单转换就可以完成这项工作:

    def doMatch (list: List[Int]): Unit = list match {           
        case last :: Nil => println ("Final element.")             
        case head :: tail => println ("Recursing..."); doMatch (tail)
        case Nil => println ("only seen for empty lists") 
      }
    
    def doMatchSeq (seq: Seq[Int]) : Unit = doMatch (seq.toList)
    
    doMatch (List(3, 4, 5))
    doMatchSeq (3 to 5)
    

    【讨论】:

      猜你喜欢
      • 2016-03-10
      • 1970-01-01
      • 2018-10-01
      • 1970-01-01
      • 2015-01-17
      • 2012-10-14
      • 2017-10-14
      • 2014-09-26
      • 2015-05-30
      相关资源
      最近更新 更多