Конец / середина списка в Scala - PullRequest
6 голосов
/ 27 октября 2011

Может кто-нибудь дать мне более простое решение для следующего кода (который разворачивает список целых чисел с учетом структуры 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 ]

3 голосов
/ 22 марта 2015

Сопоставление с образцом в конце последовательности теперь поддерживается в Scala с использованием библиотечного объекта ': +. Я не уверен, когда эта функциональность была добавлена, но я читал об этом во 2-м выпуске Программирование Scala Дина Уэмплера и Алекса Пейна. Вот простой пример получения строки последнего элемента в списке:

def stringOfLastElement[T](list: List[T]): String = list match {
    case prefix :+ end => end.toString
    case Nil => "Nil"
}
3 голосов
/ 31 октября 2011

Вот мое решение (хотя при перечитывании я думаю, что это похоже на решение Дэниела).Он основан на шаблоне операции инфикса, где шаблон 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)
2 голосов
/ 27 октября 2011

Вы можете воспользоваться небольшим количеством синтаксического сахара для сопоставления с образцом здесь:

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

(Извините, я не помню, чтобы правила приоритета были сняты с рук, поэтому некоторые из этих паренов могут быть ненужными.)

1 голос
/ 28 октября 2011

Вы не можете передавать параметры в соответствие, кроме как неявным образом, и экстрактор должен знать, что ему нужно извлечь.

Нельзя сильно упростить ваше решение.Вот альтернативный способ написания этого, но это больше вопрос предпочтения, чем что-либо еще.

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, но 0x0A и 0x0D не являются списками.Правая ассоциативность, однако, означает, что самый правый оператор применяется первым, а левые применяются к результату.Другими словами.0x0A :: (0x0D :: Nil) вместо (0x0A :: 0x0D) :: Nil.

Двоеточие в начале идентификатора требуется из-за приоритета.Даже при правильной ассоциативности неправильный приоритет превратит 0xFC :: payload <<: ... в (0xFC :: payload) <<: ....

Обратите внимание, что я использую unapplySeq, чтобы вернуть результаты в Message, чтобы их можно было извлечь, как вList.Это означает, однако, что вам нужно @ _*, чтобы получить всю последовательность:

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