Match end-to-middle of a list in Scala

Can someone give me a simpler solution for the following code (which expands the list of integers given the structure 0xFC :: len :: payload :: ... :: 0x0A :: 0x0D ):

 object Payload { def unapply(z: List[Int]): Option[List[Int]] = if (z.length == z.head + 1) Some(z tail) else None } object EndToken { def unapply(z: List[Int]): Option[List[Int]] = z.reverse match { case 0x0D :: 0x0A :: tail => Some(tail.reverse) case _ => None } } object Message { def unapply(z: List[Int]): Option[List[Int]] = z match { case 0xFC :: EndToken(x) => Some(x) case _ => None } } object Main extends App { val x = List(0xFC, 0x03, 0x01, 0x02, 0x03, 0x0A, 0x0D) x match { case Message(Payload(payload)) => println (payload) case _ => println("No match") } } 

Sort of:

 object Message { def unapply(z: List[Int]): Option[List[Int]] = z match { case 0xFC :: Payload(x) :: 0x0A :: 0x0D => Some(x) case _ => None } } 

But of course :: expects items, not lists, so it doesn't work ...

+6
source share
4 answers

Here is my solution (although upon re-reading I think this is Daniel's solution). It is based on the infix operation pattern, where the pattern op(p, q) is the same p op q .

The statement begins with : to have the same use case as :: , and ends with : to link it to the right. (len, payload) :!: tail matches :!:((len, payload), tail) . Implementing length payload extraction is a bit more complicated, but mainly because I wanted to go through this list only once.

 object :!: { type LengthPayload = (Int, List[Int]) // (len, payload) // returns ((len, payload), unparsed) def unapply(z: List[Int]): Option[(LengthPayload, List[Int])] = { if (z == Nil) None else { val len = z.head // use ListBuffer to traverse the list only once val buf = collection.mutable.ListBuffer[Int]() def take(l: Int, list: List[Int]): Option[(LengthPayload, List[Int])] = { list match { case Nil if l > 0 => None case _ if l == 0 => Some((len, buf.toList), list) case _ => buf += list.head; take(l - 1, list.tail) } } take(len, z.tail) } } } 

Then the message becomes easier (visually):

 object Message { def unapply(z: List[Int]): Option[List[Int]] = z match { case 0xFC :: (len, payload) :!: 0x0A :: 0x0D :: Nil => Some(payload) case _ => None } } 

Result:

 val x = List(0xFC, 0x03, 0x01, 0x02, 0x03, 0x0A, 0x0D) x match { case Message(payload) => println(payload) case _ => println("No match") } // List(1, 2, 3) 
+3
source

Matching the pattern at the end of the sequence is now supported in Scala using the :: library object. I'm not sure when this functionality was added, but I read about it in the second edition of Scala programming by Dean Wembler and Alex Payne. Here is a simple example of retrieving a string from the last item in a list:

 def stringOfLastElement[T](list: List[T]): String = list match { case prefix :+ end => end.toString case Nil => "Nil" } 
+3
source

You can use some syntactic sugar to compare patterns here:

 case a Pattern b => ... 

matches with:

 case Pattern(a, b) => ... 

So, if you modify the EndToke extractor as follows:

 object EndToken { def unapply(xs: List[Int]): Option[(List[Int], List[Int])] = (xs takeRight 2) match { case suffix @ (_ :: _ :: Nil) => Some((xs dropRight 2, suffix)) case _ => None } } 

You can use it in templates, for example:

 case 1 :: 2 :: (payload EndToken (0xFF :: OxCC :: Nil)) => ... 

(Sorry, I don’t remember the priority rules, so some of these guys might not be necessary.)

+2
source

You cannot pass parameters to a match, except implicitly, and the extractor should know what it needs to extract.

It is not possible to significantly simplify the solution. Here's an alternative way to record, but this is more a matter of preference than anything else.

 object :>>: { def unapply(xs: List[Int])(implicit size: Int): Option[(List[Int], List[Int])] = if (xs.size >= size) Some(xs splitAt size) else None } object :<<: { def unapply(xs: List[Int]): Option[(List[Int], List[Int])] = xs match { case size :: rest => implicit val len = size rest match { case payload :>>: tail => Some((payload, tail)) case _ => None } case _ => None } } object Message { def unapplySeq(xs: List[Int]): Option[List[Int]] = xs match { case 0xFC :: payload :<<: 0x0A :: 0x0D :: Nil => Some(payload) case _ => None } } 

Edit: Note the colons on the methods :<<: and :>>: They are not needed for the latter in this code, but they are necessary for the former.

The colon at the end of the identifier refers to left and right associativity. This is important because the argument to the right of :: and :<<: must be a List , but 0x0A and 0x0D are not lists. However, right associativity means that the rightmost operator is applied first, and the left ones are applied to the result. In other words. 0x0A :: (0x0D :: Nil) instead of (0x0A :: 0x0D) :: Nil .

The colon at the beginning of the identifier is required due to priority. Even with the correct associativity, the wrong priority would turn 0xFC :: payload <<: ... into (0xFC :: payload) <<: ...

Note that I use unapplySeq to return the results in Message , so they can be retrieved, as in List . This means, however, that you need @ _* to get the whole sequence:

 scala> List(0xFC, 0x03, 0x01, 0x02, 0x03, 0x0A, 0x0D) match { | case Message(z @ _*) => println (z) | case _ => println("No match") | } List(1, 2, 3) 
+1
source

Source: https://habr.com/ru/post/900195/


All Articles