Идиоматический метод разбора потоков данных swift(3)

Я пытаюсь сделать простой анализ BSON объектов Swift3 Data. Я чувствую, что борюсь с системой.

Давайте начнем с некоторых входных данных и схемы:

let input = Data(bytes: [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43])

Это всего лишь простой поток данных, причем легкомысленная схема заключается в том, что начальный байт указывает, сколько байтов следует за ним, образуя следующий фрагмент. Таким образом, в приведенном выше примере первая цифра 2 указывает на то, что 0x20, 0x21 являются первым блоком, за которым следует 3-байтовый блок, содержащий байты 0x30, 0x31, 0x32 и т. д.

Потоки

Моя первая мысль — сделать это с потоком (эм, генератор, итератор, что угодно). Итак, я получаю что-то вроде:

var iter = input.makeIterator()

func parse(_ stream:inout IndexingIterator<Data>) -> Data {
    var result = Data()
    if let count = stream.next() {
        for _ in 0..<count {
            result.append(Data(bytes:[stream.next()!]))
        }
    }
    return result
}

parse(&iter)
parse(&iter)
parse(&iter)
parse(&iter)

Это приводит к нескольким вопросам/наблюдениям:

1) Зачем кому-то let итератор? Весь смысл этой штуки в том, чтобы отслеживать эволюцию позиции над коллекцией. Я действительно борюсь с тем, почему авторы Swift решили отправить итераторы в дыру «все приветствуют семантику значений». Это означает, что я должен поставить inout на все мои функции синтаксического анализа.

2) Мне кажется, что я слишком указываю тип аргумента с помощью IndexingIterator. Может быть, мне просто нужно привыкнуть к многословным дженерикам?

Структура в стиле Python

Разочарованный таким подходом, я подумал, что мог бы эмулировать стиль struct.unpack() python, где кортеж возвращается из проанализированных данных, а также неиспользуемых данных. Поскольку якобы Данные волшебны и эффективны, пока я их не мутирую. Получилось так:

func parse2(_ data:Data) -> (Data, Data) {
    let count = Int(data[0])
    return (data.subdata(in: 1..<count+1), data.subdata(in: count+1..<data.count))
}

var remaining = input
var chunk = Data()
(chunk, rest) = parse2(remaining)
chunk
(chunk, rest) = parse2(remaining)
chunk
(chunk, rest) = parse2(remaining)
chunk
(chunk, rest) = parse2(remaining)
chunk

Я столкнулся с двумя проблемами с этим.

1) Что мне очень хотелось вернуть, так это data[1..count], data.subdata(in: count+1..<data.count). Но это возвращает MutableRandomAccessSlice. Который кажется совершенно другим типом? Так что в итоге я использовал более сложный subdata.

2) Можно подписать данные с закрытым диапазоном, но метод subdata будет использовать только открытый диапазон. Что с этим?

Открытый бунт, старые привычки вступают в силу

Раздраженный тем, что этот старый Smalltalker, кажется, не может найти здесь счастья, я просто сворачиваю свой собственный:

class DataStream {
    let data:Data
    var index = 0
    var atEnd:Bool {
        return index >= self.data.count
    }

    init(data:Data) {
        self.data = data
    }

    func next() -> UInt8 {
        let byte = self.data[self.index]
        self.index += 1
        return byte
    }

    func next(_ count:Int) -> Data {
        let subdata = self.data.subdata(in: self.index..<self.index + count)
        self.index += count
        return subdata
    }

}

func parse3(_ stream:DataStream) -> Data {
    let count = Int(stream.next())
    return stream.next(count)
}

let stream = DataStream(data: input)
parse3(stream)
parse3(stream)
parse3(stream)
parse3(stream)

Это решение, которым я доволен с точки зрения конечного использования. Я могу конкретизировать DataStream, чтобы делать все что угодно. Но... теперь я сбился с проторенной дорожки и чувствую, что не «понимаю» (лампочка Swiftish).

TL;версия DR

После этой игры мне стало любопытно, какой самый идиоматический способ потоковой передачи через структуры данных, извлечения из них данных на основе того, что в них встречается.


person Travis Griggs    schedule 27.06.2016    source источник
comment
для # 2 вы можете использовать псевдоним типа   -  person Alexander    schedule 27.06.2016
comment
Я вижу ваше разочарование, но Xcode 8 все еще находится в стадии бета-тестирования. Swift 2 не был суперстабильным до Xcode 7 Beta 4 или 5.   -  person Code Different    schedule 29.06.2016
comment
@CodeDifferent Я не уверен, что это имеет какое-то отношение к xcode8 или swift3, за исключением предполагаемого направления использования Data предпочтительно по сравнению с [UInt8]s в будущем. Основной фактор моего разочарования заключается в том, почему Итераторы лучше в качестве структур типа значений, а не объектов ссылочного типа.   -  person Travis Griggs    schedule 30.06.2016
comment
Другой вариант — создать InputStream из данных, а затем использовать метод чтения потоков. . — Но ваш DataStream — тоже хороший подход. Я бы изменил некоторые детали, например, проверил наличие достаточного количества данных и в противном случае вернул бы nil.   -  person Martin R    schedule 30.06.2016
comment
Хотелось бы, чтобы ваш комментарий был в форме ответа @MartinR; DataStream с вашими предложениями был решением, которое я использовал и остался доволен.   -  person Travis Griggs    schedule 02.08.2016
comment
@TravisGriggs: я предлагаю вам опубликовать ответ с вашим решением.   -  person Martin R    schedule 02.08.2016


Ответы (2)


В конце концов, как упоминалось в комментариях, я выбрал класс DataStream, включающий предложения MartinR. Вот реализация, которую я использую сегодня.

class DataStream {
    let data:Data
    var index = 0
    var atEnd:Bool {
        return index >= self.data.count
    }

    init(data:Data) {
        self.data = data
    }

    func next() -> UInt8? {
        guard self.atEnd.NOT else { return nil }
        let byte = self.data[self.index]
        self.index += 1
        return byte
    }

    func next(_ count:Int) -> Data? {
        guard self.index + count <= self.data.count else { return nil }
        let subdata = self.data.subdata(in: self.index..<self.index + count)
        self.index += count
        return subdata
    }

    func upTo(_ marker:UInt8) -> Data? {
        if let end = (self.index..<self.data.count).index( where: { self.data[$0] == marker } ) {
            let upTo = self.next(end - self.index)
            self.skip() // consume the marker
            return upTo
        }
        else {
            return nil
        }
    }

    func skip(_ count:Int = 1) {
        self.index += count
    }

    func skipThrough(_ marker:UInt8) {
        if let end = (self.index..<self.data.count).index( where: { self.data[$0] == marker } ) {
            self.index = end + 1
        }
        else {
            self.index = self.data.count
        }
    }
}
person Travis Griggs    schedule 02.09.2016

(Отказ от ответственности: после повторного прочтения вопроса OP: я понимаю (не только с использованием TL; DR глаз;), что это на самом деле не отвечает на вопрос OP: s, но я оставлю это, поскольку это может сделать несколько интересное дополнение к обсуждению; split здесь создается не поток, а фрагмент последовательностей данных)

Это чем-то похоже на ваше решение "Python Struct'esque"; вы можете использовать предикатное закрытие isSeparator метода split Data для анализа потока байтов.

func split(maxSplits: Int = default, omittingEmptySubsequences: Bool = default, 
           isSeparator: @noescape UInt8 throws -> Bool) 
           rethrows -> [MutableRandomAccessSlice<Data>]

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

Из Справочника по языку для структуры Data (Swift 3).

У меня не было времени скачать бета-версию XCode 8 (и песочница IBM Swift 3-dec по какой-то причине не включает структуру Data), но пример использования split(...) (здесь: только для массива) мог бы выглядеть кое-что строки:

/* Swift 2.2 example */
let input: [UInt8] = [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43]

var isSep = true
var byteCounter: (UInt8, UInt8) = (0,0)
let parsed = input.split() { elem -> Bool in
    if isSep {
        isSep = false
        byteCounter.0 = 0
        byteCounter.1 = elem
        return true
    }

    byteCounter.0 += 1
    if byteCounter.0 == byteCounter.1 {
        isSep = true // next is separator
    }
    return false

}.map { Array($0) }

print(parsed) // [[32, 33], [48, 49, 50], [16], [64, 65, 66, 67]]

Непроверенный эквивалентный фрагмент для структуры Swift 3 Data:

let input = Data(bytes: [2, 0x20, 0x21, 3, 0x30, 0x31, 0x32, 1, 0x10, 4, 0x40, 0x41, 0x42, 0x43])

var isSep = true
var byteCounter: (UInt8, UInt8) = (0,0)
let parsed = input.split(maxSplits: 1000, omittingEmptySubsequences: false) { elem -> Bool in
    if isSep {
        isSep = false
        byteCounter.0 = 0
        byteCounter.1 = elem
        return true
    }
    byteCounter.0 += 1
    if byteCounter.0 == byteCounter.1 {
        isSep = true // next is separator
    }
    return false
}.map { $0 }

Обратите внимание, что MutableRandomAccessSlice<Data> должно быть "тривиально" преобразовано в Data (само Data представляет собой MutableCollection байтов), сравните, например. на простую карту над собой, чтобы преобразовать ArraySlice<Int> в Array<Int>

let foo = [1, 2, 3]
let bar = foo[0..<2]     // ArraySlice<Int>
let baz = bar.map { $0 } // Array<Int>
person dfrib    schedule 27.06.2016