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

发布于 2024-12-11 23:06:37 字数 1027 浏览 0 评论 0原文

简单的解决方案来解决以下代码(它正在展开给定结构 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
  }
}

但是,当然, :: 需要的是元素,而不是列表,所以它不起作用......

Can someone give me a simpler solution to the following code (which is unfolding a list of integers given a 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")
  }
}

Something like:

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, :: is expecting elements, not lists, so it doesn't work...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

梦途 2024-12-18 23:06:37

这是我的解决方案(尽管重新阅读后我认为这就像丹尼尔的解决方案)。它基于中缀操作模式,其中模式 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)

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

The operator starts with : to have same precedece as :: and ends with : to associate to the right. (len, payload) :!: tail is the same as :!:((len, payload), tail). The implementation of the payload extraction based on length is a bit more complex but mostly because I wanted to traverse the 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 message becomes simpler (visually):

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

The 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)
╰沐子 2024-12-18 23:06:37

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

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

Pattern matching on the end of a 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 2nd edition of Programming Scala by Dean Wampler and Alex Payne. Here is a simple example of retrieving a string of the last element in a list:

def stringOfLastElement[T](list: List[T]): String = list match {
    case prefix :+ end => end.toString
    case Nil => "Nil"
}
过去的过去 2024-12-18 23:06:37

您可以在此处利用一些语法糖进行模式匹配:

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)) => ...

抱歉,我不记得优先级规则了手,所以其中一些括号可能是不必要的。)

You can take advantage of a bit of syntactic sugar for pattern matching here:

case a Pattern b => ... 

is the same as:

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

So if you modify your EndToke extractor like so:

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 patterns like:

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

(Sorry, I don't remember the precedence rules off hand so some of those parens may be unnecessary.)

£噩梦荏苒 2024-12-18 23:06:37

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

人们无法真正简化您的解决方案。这是另一种编写方式,但这更多的是偏好问题。

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)

You can't pass parameters to a match, except implicitly, and the extractor must know what it needs to extract.

One can't really simplify your solution much. Here's an alternative way of writing it, but it's 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 about the colons on methods :<<: and :>>:. They aren't really needed for the latter in this code, but they are needed for the former.

The colon at the end of the identifier is about 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. Right associativity, however, means the right-most operator is applied first, and the ones to the left apply over the result. In other words. 0x0A :: (0x0D :: Nil) instead of (0x0A :: 0x0D) :: Nil.

The colon at the beginning of the identifier is required because of precedence. Even with the right-associativity, wrong precedence would turn 0xFC :: payload <<: ... into (0xFC :: payload) <<: ....

Note that I'm using unapplySeq to return the results in Message, so that can be extracted like in List. That 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)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文