ChatGPT解决这个技术问题 Extra ChatGPT

Scala pattern matching on sequences other than Lists

I have the following code which recursively operates on each element within a List

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

Now, ignoring that this functionality is available through filter() and foreach(), this works just fine. However, if I try to change it to accept any Seq[Int], I run into problems:

Seq doesn't have ::, but it does have +:, which as I understand is basically the same thing. If I try to match on head +: tail however, the compiler complains 'error: not found: value +:'

Nil is specific to List, and I'm not sure what to replace it with. I'm going to try Seq() if I ever get past the previous problem

Here is how I think the code should look, except it doesn't work:

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

Edit: So many good answers! I'm accepting agilesteel's answer as his was the first that noted that :: isn't an operator in my example, but a case class and hence the difference.

Two sidenotes: final is not allowed as an identifier there, and the compiler complains, that the cases aren't exhaustive. You could use: 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") } instead.
Yeah, the final was a 'simplifying the scenario' error. I changed it to last so that it will compile, but I left out your case Nil so that the comment will make sense with the question. Thanks.
I can delete the comment - no problem. It would save new readers some time, to find a more correct question without comment, instead of something, that they like to correct, until they find a comment and an answer to the comment ... SE should be like a wiki, where people contribute to the solution - the documentation of the production isn't that important, and who wrote what.
Why not use something similar to the trycatch method in this SO question? Essentially, use the size of the Seq and use head and tail to access the desired elements.

y
yakshaver

As of the ides of March 2012, this works in 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.

More generally, two different head/tail and init/last decomposition objects mirroring append/prepend were added for Seq in SeqExtractors:

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)

L
Landei

Kind of cheating, but here it goes:

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

Don't ask me why xs* doesn't work...


I was wondering whether something like this would work, but I couldn't quite get the syntax to work. Do you consider it cheating in regards to my question, or cheating in a "generally you should not do this" sense?
@Zecrates: _* means "ignore the following elements of the pattern". With the @ syntax I'm giving the "ignored" part a name, which feels like cheating. I don't think there is actually anything wrong with this code (of course head and tail might be inefficient on a given Seq).
You can also do this like so (if you know your Seq isn't empty: val Seq(x, xs@_*) = seq
@Jaap If you do that, you'll fall into infinite recursion.
yakshaver's answer is more up-to-date: stackoverflow.com/a/19147469/707926
a
agilesteel

There are two :: (pronounced cons) in Scala. One is an operator defined in class List and one is a class (subclass of List), which represents a non empty list characterized by a head and a tail.

head :: tail is a constructor pattern, which is syntactically modified from ::(head, tail).

:: is a case class, which means there is an extractor object defined for it.


I see, that is quite tricky! So, am I correct in saying you cannot pattern match on just any statement, it has to be a standalone object (or whatever we can call it)? I just tested and this seems the case, as I can pattern match on 2 but not on (1 + 1).
In generall, you are not matching on statements, you are matching against patterns. Extractors are the syntactical construct in Scala, which helps to define patterns. These are normally constructor patterns. When pattern matching on 2, the pattern is a constant, which is a completely different kind of pattern. 1 + 1 is an arbitrary expression, which does not conform to any kind of patterns, although it might look like one.
It's noteworthy that the cons :: operator will NOT work on Vector though it will work on Seq and and List. It's safer therefore to use +:
d
dhg

You can actually define an object for +: to do exactly what you are looking for:

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)

Then your code works exactly as expected.

This works because h +: t is equivalent to +:(h,t) when used for patten matching.


Note: s.size >= 1 can be slow on Seq and can be replaced with s.nonEmpty (which also seems more descriptive).
K
Kim Stebel

I don't think there is pattern matching support for arbitrary sequences in the standard library. You could do it with out pattern matching though:

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

You can define your own extractor objects though. See 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)
    }
  }
}

Does defining EmptySeq() buy you anything? Just using Seq() seems to work fine.
u
user unknown

A simple tranformation from Seq to List would do the job:

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)

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now