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

У меня длинная строка (иногда более 1000 символов), которую я хочу преобразовать в массив логических значений. И это нужно делать это много раз, очень быстро.

let input: String = "001"
let output: [Bool] = [false, false, true]

Моя наивная попытка заключалась в следующем:

input.characters.map { $0 == "1" }

Но это намного медленнее, чем хотелось бы. Мое профилирование показало мне, что map - это то место, где происходит замедление, но я не уверен, насколько проще я могу это сделать.

Я чувствую, что это было бы беззаботно быстро без накладных расходов Swift/ObjC. В C я думаю, что это простой цикл for, в котором байт памяти сравнивается с константой, но я не уверен, какие функции или синтаксис я должен смотреть.

Есть ли способ сделать это намного быстрее?

UPDATE:

Я также пробовал

output = []
for char in input.characters {
    output.append(char == "1")
}

И это примерно на 15% быстрее. Я надеюсь на гораздо больше.

Ответ 1

Это быстрее:

// Algorithm 'A'
let input = "0101010110010101010"
var output = Array<Bool>(count: input.characters.count, repeatedValue: false)
for (index, char) in input.characters.enumerate() where char == "1" {
    output[index] = true
}

Обновление: под input = "010101011010101001000100000011010101010101010101"

0.0741/0.0087, где этот подход быстрее, чем автор в 8,46 раза. С большей корреляцией данных более позитивно.

Кроме того, при использовании nulTerminatedUTF8 скорость немного увеличилась, но не всегда скорость выше, чем алгоритм A:

// Algorithm 'B'
let input = "10101010101011111110101000010100101001010101"
var output = Array<Bool>(count: input.nulTerminatedUTF8.count, repeatedValue: false)
for (index, code) in input.nulTerminatedUTF8.enumerate() where code == 49 {
    output[index] = true
}

В графе результатов отображается длина ввода 2196, где первая и последняя 0..1, A - вторая, B - третья точка. A: 0,311 сек, B: 0,304 сек

График сравнения алгоритмов

Ответ 2

import Foundation

let input:String = "010101011001010101001010101100101010100101010110010101010101011001010101001010101100101010100101010101011001010101001010101100101010100101010"
var start  = clock()
var output = Array<Bool>(count: input.nulTerminatedUTF8.count, repeatedValue: false)
var index = 0
for val in input.nulTerminatedUTF8 {
    if val != 49 {
        output[index] = true
    }
    index+=1
}
var diff = clock() - start;
var msec = diff * 1000 / UInt(CLOCKS_PER_SEC);
print("Time taken \(Double(msec)/1000.0) seconds \(msec%1000) milliseconds");

Это должно быть очень быстро. Попробуйте. Для 010101011010101001000100000011010101010101010101 требуется 0.039 сек.

Ответ 3

Это должно быть немного быстрее, чем версия enumerate() where char == "1" (0.557s для 500_000 чередующихся и нулей против алгоритма 1.159s "A" от diampiax)

let input = inputStr.utf8
let n = input.count
var output = [Bool](count: n, repeatedValue: false)
let one = UInt8(49) // 1
for (idx, char) in input.enumerate() {
    if char == one { output[idx] = true }
}

но это также намного менее читаемо; -p

edit: обе версии медленнее, чем вариант карты, возможно, вы забыли скомпилировать с оптимизацией?

Ответ 4

Я бы предположил, что это как можно быстрее:

let targ = Character("1")
let input: String = "001" // your real string goes here
let inputchars = Array(input.characters)
var output:[Bool] = Array.init(count: inputchars.count, repeatedValue: false)
inputchars.withUnsafeBufferPointer {
    inputbuf in
    output.withUnsafeMutableBufferPointer {
        outputbuf in
        var ptr1 = inputbuf.baseAddress
        var ptr2 = outputbuf.baseAddress
        for _ in 0..<inputbuf.count {
            ptr2.memory = ptr1.memory == targ
            ptr1 = ptr1.successor()
            ptr2 = ptr2.successor()
        }
    }
}
// output now contains the result

Причина в том, что благодаря использованию указателей буфера мы просто циклически перемещаемся по непрерывной памяти, точно так же, как вы циклически перемещаетесь по массиву C, увеличивая его указатель. Таким образом, как только мы закончим первоначальную настройку, это должно быть так же быстро, как и в C.

РЕДАКТИРОВАТЬ В реальном тесте разница во времени между оригинальным методом OP и этим является разницей между

13.3660290241241

и

0.219357967376709

что является довольно резким ускорением. Я спешу добавить, однако, что я исключил первоначальную настройку из теста времени. Эта строка:

let inputchars = Array(input.characters)

... особенно дорого.

Ответ 5

Еще один шаг должен ускорить это еще больше. Использование reserveCapacity изменит размер массива раз перед запуском циклов вместо того, чтобы пытаться сделать это по мере запуска цикла.

var output = [Bool]()
output.reserveCapacity(input.characters.count)
for char in input.characters {
    output.append(char == "1")
}

Ответ 6

Используйте withCString(_:) для получения необработанного UnsafePointer<Int8>. Перейдем к этому и сравним с 49 (значение ascii "1").

Ответ 7

Как насчет более функционального стиля? Это не самый быстрый (47 мс), сегодня, наверняка...

import Cocoa

let start = clock()

let bools = [Bool](([Character] ("010101011001010101001010101100101010100101010110010101010101011001010101001010101100101010100101010101011001010101001010101100101010100101010".characters)).map({$0 == "1"}))

let msec = (clock() - start) * 1000 / UInt(CLOCKS_PER_SEC);
print("Time taken \(Double(msec)/1000.0) seconds \(msec%1000) milliseconds");

Ответ 8

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

Вы пробовали:

let output = [Bool](input.characters.lazy.map { $0 == "1" })

Это может быть только одна итерация.

Другая вещь, которая может ускорить работу, заключается в том, что вы можете избежать использования строк, но вместо этого использовать массивы символов подходящей кодировки (особенно если это более фиксированные единицы размера (например, UTF16 или ASCII). Тогда тогда поиск по длине будет равен O (1), а не O (n), и итерация может быть более быстрой.

BTW всегда тестирует производительность с включенным оптимизатором и никогда не находится на игровой площадке, потому что характеристики производительности совершенно разные, иногда в 100 раз.