-
-
Save janodev/9fb63a408251e66d7f1756f2537bc4b6 to your computer and use it in GitHub Desktop.
Any base converter. https://stackoverflow.com/a/14780442/412916
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
enum BaseConverter | |
{ | |
static let asciiScalars = (0...127).compactMap { UnicodeScalar($0) } | |
static let alphanumericsAndSymbolsSet = CharacterSet.alphanumerics.union(CharacterSet.symbols) | |
static let base64AlphabetSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "+/=")) | |
static let asciiAlphabet = scalarsToString(asciiScalars) | |
static let asciiAlphanumericsAlphabet = scalarsToString(asciiScalars.filter { CharacterSet.alphanumerics.contains($0) }) | |
static let asciiAlphanumericsAndSymbolsAlphabet = scalarsToString(asciiScalars.filter { alphanumericsAndSymbolsSet.contains($0) }) | |
static let base64Alphabet = scalarsToString(asciiScalars.filter { base64AlphabetSet.contains($0) }) | |
enum BaseConverterError: Error { | |
case positionOfEmptyString(String) | |
case outOfRange(String) | |
case alphabetTooSmall(String) | |
} | |
static func scalarsToString(_ scalars: [UnicodeScalar]) -> String { | |
scalars.map { String(Character($0)) }.joined() | |
} | |
static func singleCharacter(of string: String, at offset: Int) throws -> Character { | |
guard !string.isEmpty else { | |
throw BaseConverterError.positionOfEmptyString("Requested character at offset \(offset) for an empty string") | |
} | |
guard let index = string.index(string.startIndex, offsetBy: offset, limitedBy: string.endIndex) else { | |
throw BaseConverterError.outOfRange("Index out of range for offset \(offset) and string \(string)") | |
} | |
return string[index] | |
} | |
/// Represent number `n` using the given `alphabet`. | |
/// The base of the representation is inferred from the alphabet length. e.g. alphabet "AB" uses base 2. | |
static func representNumber(number: Int, alphabet: String) throws -> String | |
{ | |
guard alphabet.count > 1 else { | |
throw BaseConverterError.alphabetTooSmall("Alphabet '\(alphabet)' is too small. Try at least two characters.") | |
} | |
let base = alphabet.count | |
if (number < base){ | |
// it can be represented as a single character so return that character | |
return try String(singleCharacter(of: alphabet, at: number)) | |
} else { | |
let rightmostDigit = number % base // e.g. 769 % 10 = 9 | |
let rightmostChar = try singleCharacter(of: alphabet, at: rightmostDigit) | |
let remainder = Int(trunc(Double(number)/Double(base))) | |
let prefix = try representNumber(number: remainder, alphabet: alphabet) | |
return "\(String(prefix))\(rightmostChar)" | |
} | |
} | |
static func representNumber(number: Int, inBase base: Int) throws -> String | |
{ | |
guard base <= asciiAlphanumericsAlphabet.count else { | |
throw BaseConverterError.alphabetTooSmall("Requested base \(base) but my alphabet allows at most base \(asciiAlphanumericsAlphabet.count)") | |
} | |
let alphabet = String(asciiAlphanumericsAlphabet.prefix(base)) | |
return try representNumber(number: number, alphabet: alphabet) | |
} | |
} | |
// 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz | |
print("asciiAlphanumericsAlphabet: \(BaseConverter.asciiAlphanumericsAlphabet)") | |
// $+0123456789<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ^`abcdefghijklmnopqrstuvwxyz|~ | |
print("asciiAlphanumericsAndSymbolsAlphabet: \(BaseConverter.asciiAlphanumericsAndSymbolsAlphabet)") | |
// +/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz | |
print("base64Alphabet: \(BaseConverter.base64Alphabet)") | |
assert(try! BaseConverter.representNumber(number: 4, inBase: 2) == "100") | |
assert(try! BaseConverter.representNumber(number: 4, alphabet: "01") == "100") | |
assert(try! BaseConverter.representNumber(number: 3735928559, inBase: 16) == "DEADBEEF") | |
assert(try! BaseConverter.singleCharacter(of: "01234", at: 4) == "4") | |
assert(try! BaseConverter.singleCharacter(of: "01234", at: 0) == "0") | |
// alphabetTooSmall("Alphabet \'0\' is too small. Try at least two characters.") | |
// try BaseConverter.representNumber(number: 4, inBase: 1) | |
// positionOfEmptyString("Requested character at offset 0 for an empty string") | |
// try BaseConverter.singleCharacter(of: "", at: 0) | |
// alphabetTooSmall("Requested base 4000 but my alphabet allows at most base 62") | |
// try BaseConverter.representNumber(number: 4, inBase: 4000) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment