How to encode and decode any string into Base‑36 in Swift

1 Answer

0 votes
import Foundation

/*
    Base‑36 encoding/decoding in idiomatic Swift (manual big integer)
    ----------------------------------------------------------------
    Base‑36 digits: 0–9, A–Z

    Encoding:
      - Convert string → big integer (base‑256) stored as [UInt32] "limbs"
      - Convert big integer → base‑36 string

    Decoding:
      - Convert base‑36 string → big integer (base‑36 → [UInt32])
      - Convert big integer → original bytes (base‑256)
*/

let base36Digits = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")

// Helper: convert Base‑36 character → numeric value
func base36Value(_ c: Character) -> Int {
    if let idx = base36Digits.firstIndex(of: c) {
        return idx
    }
    return 0
}

// Encode string → Base‑36
func encodeToBase36(_ input: String) -> String {
    // Big integer stored as little‑endian 32‑bit limbs
    var number: [UInt32] = [0]

    // Convert string → big integer (base‑256)
    for byte in input.utf8 {
        var carry = UInt32(byte)
        for i in 0..<number.count {
            let value = UInt64(number[i]) * 256 + UInt64(carry)
            number[i] = UInt32(value & 0xFFFF_FFFF)
            carry = UInt32(value >> 32)
        }
        if carry > 0 {
            number.append(carry)
        }
    }

    // Convert big integer → Base‑36
    var encoded = ""
    while !(number.count == 1 && number[0] == 0) {
        var remainder: UInt32 = 0

        // Divide big integer by 36
        for i in stride(from: number.count - 1, through: 0, by: -1) {
            let value = (UInt64(remainder) << 32) + UInt64(number[i])
            number[i] = UInt32(value / 36)
            remainder = UInt32(value % 36)
        }

        // Remove leading zeros
        while number.count > 1 && number.last == 0 {
            number.removeLast()
        }

        encoded.append(base36Digits[Int(remainder)])
    }

    return String(encoded.reversed())
}

// Decode Base‑36 → original string
func decodeFromBase36(_ encoded: String) -> String {
    // Big integer stored as little‑endian 32‑bit limbs
    var number: [UInt32] = [0]

    // Convert Base‑36 → big integer
    for c in encoded {
        let digit = UInt32(base36Value(c))
        var carry = digit

        for i in 0..<number.count {
            let value = UInt64(number[i]) * 36 + UInt64(carry)
            number[i] = UInt32(value & 0xFFFF_FFFF)
            carry = UInt32(value >> 32)
        }
        if carry > 0 {
            number.append(carry)
        }
    }

    // Convert big integer → original bytes (base‑256)
    var outputBytes: [UInt8] = []

    while !(number.count == 1 && number[0] == 0) {
        var remainder: UInt32 = 0

        // Divide big integer by 256
        for i in stride(from: number.count - 1, through: 0, by: -1) {
            let value = (UInt64(remainder) << 32) + UInt64(number[i])
            number[i] = UInt32(value / 256)
            remainder = UInt32(value % 256)
        }

        // Remove leading zeros
        while number.count > 1 && number.last == 0 {
            number.removeLast()
        }

        outputBytes.append(UInt8(truncatingIfNeeded: remainder))
    }

    outputBytes.reverse()
    return String(bytes: outputBytes, encoding: .utf8) ?? ""
}

// Demo
func main() {
    let text = "Hello Universe!"
    let encoded = encodeToBase36(text)
    let decoded = decodeFromBase36(encoded)

    print("Original: \(text)")
    print("Base-36 encoded: \(encoded)")
    print("Decoded: \(decoded)")
}

main()


/*
run:

Original: Hello Universe!
Base-36 encoded: LP4N024HJ1YVBVD84Y8TYQP
Decoded: Hello Universe!

*/

 



answered Jul 7 by avibootz
...