模式匹配Scala中列表的结尾/中间

时间:2011-10-27 12:01:45

标签: scala pattern-matching

有人可以给我一个更简单的解决方案来解决以下代码(在给定结构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")
  }
}

类似的东西:

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

但是,当然,::期待元素,而不是列表,所以它不起作用......

4 个答案:

答案 0 :(得分:3)

这是我的解决方案(虽然重新阅读,我认为这就像丹尼尔的解决方案)。它基于中缀操作模式,其中模式op(p, q)是相同的p op q

运算符以:开头,与::具有相同的前提,以:结尾,以便与右侧相关联。 (len, payload) :!: tail:!:((len, payload), tail)相同。基于长度的有效负载提取的实现有点复杂,但主要是因为我只想遍历列表一次。

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)
    }
  }
}

然后消息变得更简单(视觉上):

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

结果:

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)

答案 1 :(得分:3)

现在,Scala中使用':+'库对象支持序列末尾的模式匹配。我不确定何时添加了这个功能,但我在Dean Wampler和Alex Payne的第二版 Programming Scala 中读到了它。以下是检索列表中最后一个元素的字符串的简单示例:

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

答案 2 :(得分:2)

你可以利用一些语法糖来进行模式匹配:

case a Pattern b => ... 

与:

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

因此,如果你像这样修改你的EndToke提取器:

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
    }
}

您可以在以下模式中使用它:

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

(对不起,我不记得手头的优先规则,所以其中一些可能是不必要的。)

答案 3 :(得分:1)

除了隐式地,你不能将参数传递给匹配,并且提取器必须知道它需要提取什么。

人们无法真正简化您的解决方案。这是另一种写作方式,但它更多的是偏好而不是其他任何事情。

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
  }
}

修改:注意方法:<<::>>:上的冒号。在这段代码中,后者并不真正需要它们,但前者需要它们。

标识符末尾的冒号是左右相关性。这很重要,因为:::<<:右侧的参数必须是List,但0x0A0x0D不是列表。但是,正确的关联性意味着首先应用最右边的运算符,而左边的运算符应用于结果。换一种说法。 0x0A :: (0x0D :: Nil)代替(0x0A :: 0x0D) :: Nil

由于优先级,标识符开头的冒号是必需的。即使具有正确的关联性,错误的优先级也会将0xFC :: payload <<: ...变为(0xFC :: payload) <<: ...

请注意,我使用unapplySeqMessage中返回结果,因此可以像List一样提取结果。但是,这意味着您需要@ _*才能获得整个序列:

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