2016-10-20 77 views
2

我有我喜欢编码的以下结构。 我知道,如果大小字段直接位于矢量数据的前面,我可以使用vector()对矢量进行编码。但是,这里编码矢量大小的字段并不相邻。编码矢量长度字段不与矢量相邻

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[] 
) 

外的解码是OK:从比特向量读

Header.numOfItems和物品与vectorOfN创建(提供(hdr.numOfItems,Item.codec))的

编码外部是问题:

编码时,我想有numOfItem取自items.length。 我知道,当项目Vector更新或者像“编码回调之前”那样,我可以用额外的代码设置numOfItems。

问题是如果有更优雅的解决方案。对我来说Header.numOfItemsOuter.items.length是多余的,所以理想情况下只有 编码器应该知道numOfItems。

回答

3

你可以尝试使用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] 

如上定义,你得到的编码时,以下几点: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 

之后,您可以xmap()Codec[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) 
} 

这大概可以适应更复杂的情况,虽然我不是无形的异类名单完全familar - 或HList - 有可能是更好的方法来达到向量的长度,而不是在上面的例子中调用_.tail.head.length,特别是如果最终在编码值的数目后面有多个字段。

另外,Codec scaladoc是一个不错的地方,发现有用的运营商

+0

好的。与“Atomic ...”解决方案相比有点重复,但拥有“无状态”编解码器的优势。除非还有更好的解决办法,否则我会说这是公认的答案。 –

0

基于以前的答案我想出了类似下面的代码。 我使用了上面的消耗技巧形式和一个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)) 
    } 
} 
+0

我不明白你为什么需要原子整数:是不是与建议的例子一起工作? – Shastick

+0

我需要AtomicInteger,因为我不想扁平我的结构。编码器不应该规定我编写我的类的方式。 –

+0

好吧,明白了。在没有AtomicInteger的情况下,你仍然可以得到你想要的东西:我更新了答案,以不同的方式使用xmap()来访问'Codec [Outer]'。 – Shastick