如何在模式匹配中提取序列的剩余部分

时间:2011-11-26 07:00:58

标签: scala pattern-matching

我在原帖中解释我正在寻找的内容显然做得很差,所以让我们再试一次。我想要完成的是能够传递一系列项目,提取一个或多个项目,然后将序列的REMAINDER传递给另一个提取器。注意,按顺序,我的意思是序列(不一定是List)。我之前的例子使用list作为序列,我给出了一些使用cons(::)提取的例子,但我也可以将一个数组作为我的序列传递。

我以为我知道模式匹配和提取是如何工作的但我可能是错的,所以为了避免任何更基本的评论和链接到如何进行模式匹配网站,这是我的理解:

如果我想从提取器返回单个项目,我将定义一个unapply方法。此方法采用我选择的任何类型作为输入(类型可以是序列...)并返回单个可选项(返回类型本身可以是序列)。如果我想要比赛,则必须包含一些回报。如果我不想,则返回无。下面是一个示例,它将序列作为输入并返回包含在Some中的相同序列,但前提是它包含所有字符串。我很可能只是返回包含在Some中的序列而不做其他任何事情,但这似乎会给人们造成混乱。关键是如果它被包装在Some中那么它将匹配,如果它是None,它将不会。为了更清楚,除非输入也匹配我的unapply方法输入类型,否则匹配也不会发生。这是我的例子:

object Test {
  // In my original post I just returned the Seq itself just to verify I 
  // had matched but many people commented they didn't understand what I 
  // was trying to do so I've made it a bit more complicated (e.g. match 
  // only if the sequence is a sequence of Strings). Hopefully I don't 
  // screw this up and introduce a bug :)
  def unapply[A](xs: Seq[A]): Option[Seq[String]] = 
    if (xs forall { _.isInstanceOf[String] })
      Some(xs.asInstanceOf[Seq[String]])
    else
      None
}

以List为例,我现在可以执行以下操作:

// This works
def test1(xs: List[_]) = xs match {
  case (s: String) :: Test(rest) =>
    println("s = " + s + ", rest = " + rest)
  case _ =>
    println("no match")
}

test1(List("foo", "bar", "baz"))  // "s = foo, rest = List(bar, baz)"

我的test1函数将List作为输入,并使用cons通过构造函数模式提取头部和尾部(例如::( s,rest))。然后使用类型ascription(:String)来确保head(s)是一个String。尾部包含List(" bar"," baz")。这是一个List,这意味着它也是一个Seq(序列)。然后将其作为输入传递给我的Test提取器,该提取器验证两者" bar"和" baz"是字符串并返回包含在Some中的List。由于Some被返回,因此被认为是匹配(虽然在我的原始帖子中,我无意中将unapplySeq与unapply混合,但这并没有按预期工作,但是除此之外......)。这不是我想要的。这只是一个示例,表明Test确实提取了Seq作为预期的输入。

现在,在我上次无意中使用unapplySeq而不是在我的写作中使用unapply时,我造成了大量混淆。在试图理解发布的评论之后我感到非常困惑,我终于找到了错误。非常感谢Dan指出我正确的方向......

但是,为了避免更多的困惑,让我澄清一下我对unapplySeq的理解。像unapply一样,unapplySeq接受我选择作为输入的任何参数,但它不返回单个元素,而是返回一系列元素。然后,此序列中的每个项目都可用于其他模式匹配。同样,为了使匹配发生,输入类型必须匹配,并且我返回的序列必须包含在Some中,而不是None。从unapplySeq返回的项目序列中提取时,您可以使用_ *匹配尚未匹配的剩余项目。

好的,所以我的提取器将一个序列作为输入并返回一个序列(作为单个项目)作为回报。由于我只想返回一个项目作为匹配,我需要使用unapply not unapplySeq。即使在我的情况下,我还没有返回Seq,我也不想要unapplySeq,因为我不想对Seq中的项目进行更多的模式匹配。我只想将这些项目作为Seq返回,然后传递给我的案例匹配的主体。这听起来令人困惑,但对于那些理解unapply vs unapplySeq的人,我希望它不是。

所以这就是我想做的事情。我想拿一些返回序列的东西(例如List或Array),我想从这个序列中提取一些项目,然后提取项目的REMAINDER(例如_ *)作为序列。我们称之为余数序列。我想将剩余序列作为输入传递给我的提取器。如果符合我的标准,我的提取器将把剩余的项目作为单个Seq返回。只是要100%清楚。 List(或Array等)将调用其unapplySeq提取器来创建项目序列。我将提取这些项中的一个或多个,然后将剩下的作为序列传递给我的Test提取器,它将使用unapply(NOT unapplySeq)返回余数。如果您对此感到困惑,请不要发表评论......

以下是我的测试:

// Doesn't compile. Is there a syntax for this?
def test2(xs: Seq[_]) = xs match {
  // Variations tried:
  //   Test(rest) @ _*  - doesn't compile (this one seems reasonable to me)
  //   Test(rest @ _*)  - doesn't compile (would compile if Test had 
  //                      unapplySeq, but in that case would bind List's
  //                      second element to Test as a Seq and then bind 
  //                      rest to that Seq (if all strings) - not what I'm
  //                      looking for...). I though that this might work
  //                      since Scala knows Test has no unapplySeq only 
  //                      unapply so @ _* can be tied to the List not Test
  //   rest @ Test(_*)  - doesn't compile (didn't expect to)
  case List(s: String, Test(rest) @ _*) => 
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

// This works, but messy
def test3(xs: List[_]) = xs match {
  case List(s: String, rest @ _*) if (
    rest match { case Test(rest) => true; case _ => false }
  ) => 
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

我根据Julian的评论创建了test3(感谢Julian ..)。有人评论说test3做了我想要的,所以他们很困惑我正在寻找的东西。是的,它完成了我想要完成的任务,但我对此并不满意。丹尼尔的例子也有效(感谢丹尼尔),但我也不满足于必须创建另一个提取器来分割事物然后进行嵌入式提取。这些解决方案似乎太多了,以便完成对我来说相当直接的事情。我想要的是使test2工作或知道它不能以这种方式完成。是否因为语法错误而给出了错误?我知道休息@ _ *会返回一个Seq,可以在这里验证:

def test4(xs: List[_]) = xs match {
  case List(s: String, rest @ _*) => 
    println(rest.getClass)   // scala.collection.immutable.$colon$colon
  case _ =>
    println("no match")
}

它返回cons(::),这是一个Seq的List。那么如何将_ * Seq传递给我的提取器并将其返回绑定到变量rest?

请注意,我还尝试将varargs传递给我的unapply构造函数(例如unapply(xs:A *)...)但是它们也不匹配。

所以,我希望现在很清楚,当我说我想在模式匹配中提取序列的其余部分时。我不确定我怎么说它。

基于丹尼尔一世的强烈反馈,希望他能为我找到答案:)

4 个答案:

答案 0 :(得分:6)

  

我想提取第一个项目并将剩余部分传递给另一个提取器。

行。你的test1完全是这样做的。 first_item :: Extractor(the_rest)。您看到的奇怪行为来自您的Test提取器。由于您已经得到了所述问题的答案,并且Test的预期行为会让您感到test1出现问题,所以您真正想要的就是提取器的一些帮助。

因此,请阅读docs.scala-lang.org和Extractor Objects中的Pattern Matching in Scala (pdf)。虽然该PDF有一个unapplySeq的例子,并建议你想要使用它的地方,但这里有一些额外的例子:

object Sorted {
  def unapply(xs: Seq[Int]) =
    if (xs == xs.sortWith(_ < _)) Some(xs) else None
}

object SortedSeq {
  def unapplySeq(xs: Seq[Int]) =
    if (xs == xs.sortWith(_ < _)) Some(xs) else None
}

交互:

scala> List(1,2,3,4) match { case Sorted(xs) => Some(xs); case _ => None }
res0: Option[Seq[Int]] = Some(List(1, 2, 3, 4))

scala> List(4,1,2,3) match { case Sorted(xs) => Some(xs); case _ => None }
res1: Option[Seq[Int]] = None

scala> List(4,1,2,3) match { case first :: Sorted(rest) => Some(first, rest); case _ => None }
res2: Option[(Int, Seq[Int])] = Some((4,List(1, 2, 3)))

scala> List(1,2,3,4) match { case SortedSeq(a,b,c,d) => (a,b,c,d) }
res3: (Int, Int, Int, Int) = (1,2,3,4)

scala> List(4,1,2,3) match { case _ :: SortedSeq(a, b, _*) => (a,b) }
res4: (Int, Int) = (1,2)

scala> List(1,2,3,4) match { case SortedSeq(a, rest @ _*) => (a, rest) }
res5: (Int, Seq[Int]) = (1,List(2, 3, 4))

或者也许 - 我对此只有微弱的怀疑,你没有说过多少 - 你不想要提取器帮助,但实际上你想要一种简洁的方式表达类似

scala> List(1,2,3,4) match { case 1 :: xs if (xs match { case Sorted(_) => true; case _ => false }) => xs }
res6: List[Int] = List(2, 3, 4)

Erlang有这样的功能(虽然没有这些疯狂的提取器):

example(L=[1|_]) -> examine(L).

,哪个模式匹配相同的参数两次 - L[1|_]。在Erlang中=的两边都是完整的模式,可以是任何东西,你可以添加更多=的第三个或更多模式。 Scala似乎只支持L=[1|_]形式,具有变量,然后是完整模式。

scala> List(4,1,2,3) match { case xs @ _ :: Sorted(_) => xs }
collection.immutable.::[Int] = List(4, 1, 2, 3)

答案 1 :(得分:4)

嗯,最简单的方法是:

case (s: String) :: Test(rest @ _*) =>

如果你需要这个来处理常规Seq,你可以定义一个从尾部分割头的提取器:

object Split {
  def unapply[T](xs: Seq[T]): Option[(T, Seq[T])] = if (xs.nonEmpty) Some(xs.head -> xs.tail) else None
}

然后像

一样使用它
case Split(s: String, Test(rest @ _*)) =>

另请注意,如果您已定义unapply而不是unapplySeq,则@ _*匹配的模式不需要Test

答案 2 :(得分:3)

::是一个提取器。有关它的工作原理(来自随机谷歌搜索),请参阅,例如here

def test1(xs: List[_]) = xs match {
  case s :: rest =>
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

scala> test1(List("a", "b", "c"))
s = a rest = List(b, c)

我认为这就是你想要的?

答案 3 :(得分:2)

弄乱这个问题,似乎问题与unapplySeq有关。

object Test {
  def unapply[A](xs: List[A]): Option[List[A]] = Some(xs)
}    

def test1(xs: List[_]) = xs match {
  case (s: String) :: Test(s2 :: rest) =>
    println("s = " + s + " rest = " + rest)
  case _ =>
    println("no match")
}

test1(List("foo", "bar", "baz"))

产生输出:

s = foo rest = List(baz)

我在搜索有关unapplyunapplySeq之间差异的文档时遇到了麻烦。