简体   繁体   English

编码向量长度字段不与向量相邻

[英]Encoding vector length field not adjacent to the vector

I have the following structure I like to encode. 我有以下要编码的结构。 I'm aware that I can encode a vector with vector() if the size field is directly in front of the vector data. 我知道,如果size字段直接位于矢量数据的前面,则可以使用vector()对矢量进行编码。 But here the field encoding the vector size is not adjacent. 但是这里编码矢量大小的字段并不相邻。

case class Item(
    address: Int,
    size: Int,
)
case class Header {
    // lots of other fields before
    numOfItems: Int,
    // lots of other fields after
}
case class Outer(
    hdr: Header,
    items: Vector[]
)

Decoding of Outer is OK: 外部解码可以:

Header.numOfItems is read from the bit vector and items is created with vectorOfN(provide(hdr.numOfItems, Item.codec)) 从位向量读取Header.numOfItems,并使用vectorOfN(provide(hdr.numOfItems,Item.codec))创建项目

Encoding of Outer is the problem: 问题是外部编码:

When encoding I would like to have numOfItem be taken from the items.length. 编码时,我希望从items.length中获取numOfItem。 I'm aware that I could set numOfItems with additional code when the items Vector is updated or with something like a "before encoding callback". 我知道当项目Vector更新时,可以用其他代码设置numOfItems或类似“编码回调之前”的设置。

The question is if there is a more elegant solution. 问题是是否有更优雅的解决方案。 To me Header.numOfItems is redundant with Outer.items.length , so ideally only the Encoder should know about numOfItems. 对我来说Header.numOfItems是多余的Outer.items.length ,所以最好只在编码器应该知道numOfItems。

You could try building a Codec using consume() and start without building the Outer object: 您可以尝试使用consume()构建编解码器,而无需构建Outer对象即可开始:

case class OuterExpanded(
  fieldBefore: Int, // Field before number of items in the binary encoding
  fieldAdter: Int,  // Field after number of items in the binary encoding
  items: Vector[Item] // Encoded items
)

// Single Item codec
def itemC: Codec[Item] = (int32 :: int32).as[Item] 

def outerExpandedC: Codec[OuterExpanded] = ( 
  int32 ::                          // Field before count
  int32.consume( c =>               // Item count 
      int32 ::                      // Field after count
      vectorOfN(provide(c), itemC))   // 'consume' (use and forget) the count
    (_.tail.head.length)              // provide the length when encoding
  ).as[OuterExpanded]

As defined above, you get the following when encoding: outerExpandedC.encode(OuterExpanded(-1, -1, Vector(Item(1,2), Item(3,4)))) returns 如上所定义,您在编码时会得到以下内容: outerExpandedC.encode(OuterExpanded(-1, -1, Vector(Item(1,2), Item(3,4))))返回

Successful(BitVector(224 bits, 
     0xffffffff00000002fffffffe00000001000000020000000300000004))
              ^       ^       ^       ^-------^-> First Item
              |-1     |       |-2
                      |Vector length inserted between the two header fields

Afterwards, you can xmap() the Codec[OuterExpanded] to pack the other header fields together into their own object. 之后,您可以对Codec[OuterExpanded]进行xmap() ,以将其他标头字段打包到它们自己的对象中。 Ie (adding two conversion methods to Outer and OuterExpanded ): 即(在OuterOuterExpanded添加了两种转换方法):

def outerC: Codec[Outer] = 
  outerExpandedC.xmap(_.toOuter,_.expand)

case class OuterExpanded(fieldBefore: Int, fieldAfter: Int,  items: Vector[Item]) {
  def toOuter = Outer(Hdr(fieldBefore,fieldAfter), items)
}

case class Outer(header: Hdr, items: Vector[Item]) {
  def expand = OuterExpanded(header.beforeField1, header.beforeField1, items)
}

This can probably be adapted to more complex cases, though I'm not entirely familar with shapeless' heterogeneous lists – or HList – and there might be nicer ways to get to the length of the vector rather than calling _.tail.head.length in the example above, especially if you end up with more than one field after the number of encoded values. 尽管我并不完全熟悉无形的异构列表(或HList ),这可能可以适应更复杂的情况,并且可能有更好的方法来获取向量的长度,而不是调用_.tail.head.length在上面的示例中,尤其是如果您在编码值数量之后得到多个字段时。

Also, the Codec scaladoc is a nice place to discover useful operators 另外, 编解码器scaladoc是发现有用运算符的好地方

Based on the previous answer I came up with something like the code below. 根据先前的答案,我想出了类似下面的代码。 I used the consume trick form above and an AtomicInteger to hold the size of the vector. 我使用了上面的消耗技巧和一个AtomicInteger来保存向量的大小。

import java.util.concurrent.atomic.AtomicInteger
import scala.Vector
import org.scalatest._
import scodec._
import scodec.Attempt._
import scodec.codecs._
import scodec.bits._

object SomeStructure {
  case class Item(
    address: Int,
    size: Int)

  def itemC: Codec[Item] = (int32 :: int32).as[Item]

  case class Hdr(
    beforeField1: Int,
    // vectorSize would be here
    afterField1: Int)
  // vectorSize is an "in" param when encoding and an "out" param when decoding
  def hdrC(vectorSize: AtomicInteger): Codec[Hdr] =
    (int32 ::
      int32.consume(c => {
        vectorSize.set(c);
        int32
      })((i) => vectorSize.get)).as[Hdr]

  case class Outer(
    hdr: Hdr,
    var items: Vector[Item])

  def outerC() = {
    // when decoding the length is in this atomic integer
    // when encoding it is set before 
    val c = new AtomicInteger(-1)
    (hdrC(c) :: lazily(vectorOfN(provide(c.get), itemC)))
      .xmapc(identity)((g) => { c.set(g.tail.head.length); g })
  }.as[Outer]
}

import SomeStructure._
class SomeStructureSpec extends FlatSpec with Matchers {
  val bv = hex"ffffffff00000002ffffffff00000001000000020000000300000004".bits
  val v = Vector(Item(1, 2), Item(3, 4))
  val bv2 = hex"ffffffff00000003ffffffff000000010000000200000003000000040000000500000006".bits
  val v2 = Vector(Item(1, 2), Item(3, 4), Item(5, 6))
  val o = Outer(Hdr(-1, -1), v)

  "outerC" should "encode" in {
    o.items = v
    outerC.encode(o) shouldBe Successful(bv)
    o.items = v2
    outerC.encode(o) shouldBe Successful(bv2)
  }
  it should "decode" in {
    o.items = v
    outerC.decode(bv) shouldBe Successful(DecodeResult(o, BitVector.empty))
    o.items = v2
    outerC.decode(bv2) shouldBe Successful(DecodeResult(o, BitVector.empty))
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM