Swift: создать произвольную базовую строку из байтового массива - PullRequest
0 голосов
/ 29 июня 2019

У меня есть массив UInt8, который мне нужно превратить в строку с основанием 36. Я считаю, что мне нужно преобразовать его во что-то, соответствующее Binary Integer, но не знаю, как.

let bArray: [UInt8] = ..... //some array of UInt8
let foo = ? // bArray -> a binary integer ... or another step?
let baseString = String(foo, radix: 36, uppercase: false)

Это правильный процесс или я должен использовать другой подход?


Пример данных

Строка "test" хешируется с использованием SHA1 для получения байтового массива:

[169, 74, 143, 229, 204, 177, 155, 166, 28, 76, 8, 115, 211, 145, 233, 135, 152, 47, 187, 211]

с ожидаемой базой36:

jrwjerxiekdtj9k82lg930wpkr6tq6r

и гекс:

a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

Ответы [ 2 ]

2 голосов
/ 29 июня 2019

Похоже, вы пытаетесь преобразовать данные (байты) в целое число, а затем в основание 36:

let bArray: [UInt8] = [255, 255, 255, 255, 255, 255, 255, 127] // Int.max 9223372036854775807
let foo = bArray.withUnsafeBytes { $0.load(as: Int.self) }
let baseString = String(foo, radix: 36, uppercase: true)   // "1Y2P0IJ32E8E7"

String(Int.max, radix: 36, uppercase: true) // 1Y2P0IJ32E8E7
2 голосов
/ 29 июня 2019

Я уже написал это для базы 10 , поэтому вот общая версия для любой базы из 2...36:

func bytesToRadix<C: RangeReplaceableCollection>(_ bytes: C, radix: Int, isUppercase: Bool = false, isBigEndian: Bool = true) -> String where C.Element == UInt8 {

    // Nothing to process or radix outside of 2...36, return an empty string.
    guard !bytes.isEmpty, 2...36 ~= radix else { return "" }

    let bytes = isBigEndian ? bytes : C(bytes.reversed())

    // For efficiency in calculation, combine 7 bytes into one Int.
    let chunk = 7
    let numvalues = bytes.count
    var ints = Array(repeating: 0, count: (numvalues + chunk - 1)/chunk)
    var rem = numvalues % chunk == 0 ? chunk : numvalues % chunk
    var index = 0
    var accum = 0

    for value in bytes {
        accum = (accum << 8) + Int(value)
        rem -= 1
        if rem == 0 {
            rem = chunk
            ints[index] = accum
            index += 1
            accum = 0
        }
    }

    // Array to hold the result, in reverse order
    var digits = [Int]()

    // Repeatedly divide value by radix, accumulating the remainders.
    // Repeat until original number is zero
    while !ints.isEmpty {
        var carry = 0
        for (index, value) in ints.enumerated() {
            var total = (carry << (8 * chunk)) + value
            carry = total % radix
            total /= radix
            ints[index] = total
        }

        digits.append(carry)

        // Remove leading Ints that have become zero.
        ints = .init(ints.drop { $0 == 0 })
    }

    // Create mapping of digit Int to String
    let letterOffset = Int(UnicodeScalar(isUppercase ? "A" : "a").value - 10)
    let letters = (0 ..< radix).map { d in d < 10 ? "\(d)" : String(UnicodeScalar(letterOffset + d)!) }

    // Reverse the digits array, convert them to String, and join them
    return digits.reversed().map { letters[$0] }.joined()
}

Примеры:

let face: [UInt8] = [0xFA, 0xCE]

print(bytesToRadix(face, radix: 16))  // "face"
print(bytesToRadix(face, radix: 16, isUppercase: true))  // "FACE"
print(bytesToRadix(face, radix: 16, isBigEndian: false))  // "cefa""
print(bytesToRadix(face, radix: 16, isUppercase: true, isBigEndian: false))  // "CEFA"
print(bytesToRadix(face, radix: 10)) // "64206"
print(bytesToRadix(face, radix: 2))  // "111101011001110"
print(bytesToRadix(face, radix: 36)) // "1dji"

// also works with Data
let faceData = Data([0xFA, 0xCE])
print(bytesToRadix(face, radix: 16))  // "face"

Некоторые крайние случаи:

print(bytesToRadix([9], radix: 16))   // "9"
print(bytesToRadix([10], radix: 16))  // "a"
print(bytesToRadix([15], radix: 16))  // "f"
print(bytesToRadix([16], radix: 16))  // "10"
print(bytesToRadix([35], radix: 36))  // "z"
print(bytesToRadix([36], radix: 36))  // "10"

Большой тест:

let bArray = (0...255).map(UInt8.init)
print(bytesToRadix(bArray, radix: 16, isBigEndian: false))

fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0dfdedddcdbdad9d8d7d6d5d4d3d2d1d0cfcecdcccbcac9c8c7c6c5c4c3c2c1c0bfbebdbcbbbab9b8b7b6b5b4b3b2b1b0afaeadacabaaa9a8a7a6a5a4a3a2a1a09f9e9d9c9b9a999897969594939291908f8e8d8c8b8a898887868584838281807f7e7d7c7b7a797877767574737271706f6e6d6c6b6a696867666564636261605f5e5d5c5b5a595857565554535251504f4e4d4c4b4a494847464544434241403f3e3d3c3b3a393837363534333231302f2e2d2c2b2a292827262524232221201f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100

print(bytesToRadix(bArray, radix: 36))

168swoi6iuzj4fbwknlnh695zl88v65qcfgnwrwepqcxb9dysmluowqahvt3r9gsc1v47ssxdivjda3nttl6r044pzz7zwhtgu2mkow5ts28x2mbwenh3wfz4s1sarspfhlrakvqrgpmzb66sgtz2lzbotl7r28wcq8925c747b44l60vrk3scrin4zvnwn7pdsukgo6lgjhu1nuwj7yt1h9ujpe3os17onsk7sp4ysmytu568do2tqetwnrmbxb2dtd8kqorcoakaizlm9svr8axe1acxfursz11nubrhighfd64yhmp99ucvzr944n8co01o4x64cmbd8be0hqbm2zy5uwe4uplc4sa50xajel4bkkxb1kh21pisna37eqwpbpq11ypr


Тест с данными образца:

let bArray: [UInt8] = [169, 74, 143, 229, 204, 177, 155, 166, 28, 76, 8, 115, 211, 145, 233, 135, 152, 47, 187, 211]

print(bytesToRadix(bArray, radix: 16))

a94a8fe5ccb19ba61c4c0873d391e987982fbbd3

print(bytesToRadix(bArray, radix: 36))

jrwjerxiekdtj9k82lg930wpkr6tq6r


Функция реверса: radixToBytes

Вот краткая версия функции реверса. У него еще нет возможности использовать прописные цифры или обрабатывать endian (предполагается, что используется big endian).

func radixToBytes(_ radixString: String, radix: Int) -> [UInt8] {

    let digitMap: [Character : Int] = [
        "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5,
        "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11,
        "c": 12, "d": 13, "e": 14, "f": 15, "g": 16, "h": 17,
        "i": 18, "j": 19, "k": 20, "l": 21, "m": 22, "n": 23,
        "o": 24, "p": 25, "q": 26, "r": 27, "s": 28, "t": 29,
        "u": 30, "v": 31, "w": 32, "x": 33, "y": 34, "z": 35
    ]

    // Convert input string into array of Int digits
    let digits = Array(radixString).compactMap { digitMap[$0] }

    // Nothing to process? Return an empty array.
    guard digits.count > 0 else { return [] }

    let numdigits = digits.count

    // Array to hold the result, in reverse order
    var bytes = [UInt8]()

    // Convert array of digits into array of Int values each
    // representing 6 digits of the original number.  Six digits
    // was chosen to work on 32-bit and 64-bit systems.
    // Compute length of first number.  It will be less than 6 if
    // there isn't a multiple of 6 digits in the number.
    let chunk = 6
    var ints = Array(repeating: 0, count: (numdigits + chunk - 1)/chunk)
    var rem = numdigits % chunk
    if rem == 0 {
        rem = chunk
    }
    var index = 0
    var accum = 0
    for digit in digits {
        accum = accum * radix + digit
        rem -= 1
        if rem == 0 {
            rem = chunk
            ints[index] = accum
            index += 1
            accum = 0
        }
    }

    // Repeatedly divide value by 256, accumulating the remainders.
    // Repeat until original number is zero
    var mult = 1
    for _ in 1...chunk {
        mult *= radix
    }

    while ints.count > 0 {
        var carry = 0
        for (index, value) in ints.enumerated() {
            var total = carry * mult + value
            carry = total % 256
            total /= 256
            ints[index] = total
        }

        bytes.append(UInt8(truncatingIfNeeded: carry))

        // Remove leading Ints that have become zero
        ints = .init(ints.drop { $0 == 0 })
    }

    // Reverse the array and return it
    return bytes.reversed()
}
...