2016-10-20 4 views
2

Ich habe die folgende Struktur, die ich gerne verschlüsseln. Ich bin mir bewusst, dass ich einen Vektor mit vector() codieren kann, wenn das Größenfeld direkt vor den Vektordaten ist. Aber hier ist das Feld, das die Vektorgröße codiert, nicht benachbart.Codierung Vektor Länge Feld nicht neben dem Vektor

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

Decodierung von Outer ist OK:

Header.numOfItems aus dem Bit-Vektor lesen und Gegenstände mit vectorOfN (liefern (hdr.numOfItems, Item.codec)) erstellt

Encoding von Äußerlich ist das Problem:

Bei der Codierung möchte ich numOfItem aus der items.length genommen werden. Ich bin mir bewusst, dass ich numOfItems mit zusätzlichen Code festlegen konnte, wenn der Artikel Vector aktualisiert wird oder mit etwas wie einem "vor der Codierung Rückruf".

Die Frage ist, ob es eine elegantere Lösung gibt. Für mich ist Header.numOfItems redundant mit Outer.items.length, also sollte idealerweise nur der Encoder über numOfItems Bescheid wissen.

Antwort

3

Sie könnten einen Codec versuchen Aufbau consume() mit und starten, ohne den Aufbau der Outer Objekt:

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] 

Wie oben definiert, erhalten Sie das folgende beim Encodieren: outerExpandedC.encode(OuterExpanded(-1, -1, Vector(Item(1,2), Item(3,4)))) kehrt

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

Sie danach kann xmap() die Codec[OuterExpanded] die anderen Header-Felder zusammen in ein eigenes Objekt packen. Ie (Hinzufügen von zwei Umwandlungsverfahren zu Outer und OuterExpanded):

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

Dies kann wahrscheinlich angepasst komplexeren Fälle, obwohl ich nicht ganz vertraut bin mit unförmigen heterogenen Listen - oder HList - und es könnte sein, bessere Möglichkeiten, um die Länge des Vektors zu erhalten, anstatt _.tail.head.length im obigen Beispiel aufzurufen, besonders wenn Sie nach der Anzahl der codierten Werte mehr als ein Feld haben.

Auch ist die Codec scaladoc ein schöner Ort, nützliche Betreiber zu entdecken

+0

Ok. Ein bisschen Wiederholung beim Vergleich mit der "Atomic ..." Lösung, aber der Vorteil eines "zustandslosen" Codecs. Wenn es keine bessere Lösung gibt, würde ich sagen, dass dies die akzeptierte Antwort ist. –

0

Basierend auf der vorherige Antwort, die ich unten mit so etwas wie der Code kam. Ich habe das oben abgebildete Trick-Formular und einen AtomicInteger verwendet, um die Größe des Vektors zu halten.

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

Ich verstehe nicht, warum Sie die atomare Ganzzahl brauchen: hat etwas mit dem vorgeschlagenen Beispiel nicht funktioniert? – Shastick

+0

Ich brauche den AtomicInteger, da ich meine Struktur nicht abflachen wollte. Der Encoder sollte nicht die Art bestimmen, wie ich meine Klassen schreibe. –

+0

Ok, verstanden. Sie können immer noch ohne AtomicInteger zu dem kommen, was Sie wollen: Ich habe die Antwort aktualisiert, um xmap() anders zu benutzen, um zu einem 'Codec [Outer]' zu kommen. – Shastick

Verwandte Themen