Идиоматический метод парсинга swift(3) потоков данных
Я пытаюсь сделать простой анализ BSON объектов данных Swift3. Я чувствую, что борюсь с системой.
Давайте начнем с некоторого ввода и схемы:
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 и т. Д.
Streams
Моя первая мысль - сделать это с помощью потока (например, Generator, Iterator, что угодно). В итоге я получаю что-то вроде:
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'esque
Разочаровавшись в этом подходе, я подумал, что мог бы эмулировать стиль pythons struct.unpack(), где возвращается кортеж проанализированных данных, а также неиспользованных данных. Так как якобы данные волшебны и эффективны, пока я их не мутирую. Это оказалось так:
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)
Это решение меня устраивает от конечного использования POV. Я могу дополнить DataStream всеми видами вещей. Но... я сейчас в глуши и чувствую, что не "понимаю" (лампочка Swiftish).
TL; версия DR
После этой игры мне стало любопытно, какой самый идиоматичный способ для потоковой передачи через структуры данных, извлечения данных из них на основе того, что в них встречается.
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
}
}
}
(Отказ от ответственности: после прочтения вопроса OP я понимаю (не только с использованием TL;DR eyes;), что это на самом деле не отвечает на вопрос 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-ых песочницах не входит 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>