4

Suppose I am given a string like this:

D7C17A4F

How do I convert each individual character to a hex value?

So D should be 0xD, 7 should be 0x7

Right now, I have each individual character represented as it's ASCII value. D is 68, 7 is 55. I'm trying to pack those two values into one byte. For example: D7 becomes 0xD7 and C1 becomes 0xC1. I can't do that using the ASCII decimal values though.

Dave C
  • 7,729
  • 4
  • 49
  • 65
Manning10118
  • 43
  • 1
  • 4
  • 2
    Do you want an array of 8 numbers (13, 7, ...) or an array of 4 numbers (0xD7, 0xC1, ...) ? Is the string always 8 hex characters long, or can it be longer or shorter? – Martin R May 12 '15 at 17:42
  • The string is always 32 characters long. And array of 4 numbers is better. Either one is ok though – Manning10118 May 12 '15 at 17:45
  • But a string of 32 characters should be 16 numbers ? – Martin R May 12 '15 at 18:39

5 Answers5

5

A possible solution:

let string = "D7C17A4F"

let chars = Array(string)
let numbers = map (stride(from: 0, to: chars.count, by: 2)) {
    strtoul(String(chars[$0 ..< $0+2]), nil, 16)
}

Using the approach from https://stackoverflow.com/a/29306523/1187415, the string is split into substrings of two characters. Each substring is interpreted as a sequence of digits in base 16, and converted to a number with strtoul().

Verify the result:

println(numbers)
// [215, 193, 122, 79]

println(map(numbers, { String(format: "%02X", $0) } ))
// [D7, C1, 7A, 4F]

Update for Swift 2 (Xcode 7):

let string = "D7C17A4F"
let chars = Array(string.characters)

let numbers = 0.stride(to: chars.count, by: 2).map {
    UInt8(String(chars[$0 ..< $0+2]), radix: 16) ?? 0
}

print(numbers) 

or

let string = "D7C17A4F"

var numbers = [UInt8]()
var from = string.startIndex
while from != string.endIndex {
    let to = from.advancedBy(2, limit: string.endIndex)
    numbers.append(UInt8(string[from ..< to], radix: 16) ?? 0)
    from = to
}

print(numbers) 

The second solution looks a bit more complicated but has the small advantage that no additional chars array is needed.

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • What happened to using map on a stride in swift 2? that seemed like an elegant solution. – dwaz Dec 05 '15 at 16:39
1

Swift 3 version, modified from @Martin R's answer. This variant also accepts incoming string with odd length.

let string = "D7C17A4F"

let chars = Array(string.characters)
let numbers = stride(from: 0, to: chars.count, by: 2).map() {
    strtoul(String(chars[$0 ..< min($0 + 2, chars.count)]), nil, 16)
}
Sivda
  • 101
  • 6
0

Use chunks!

"D7C17A4F"
  .chunks(ofCount: 2)
  .map { UInt8($0, radix: 0x10)! }
-1

My variation of @martin-r answer:

extension String {

    func hexToByteArray() -> [UInt8] {
        let byteCount = self.utf8.count / 2
        var array = [UInt8](count: byteCount, repeatedValue: 0)
        var from = self.startIndex
        for i in 0..<byteCount {
            let to = from.successor()
            let sub = self.substringWithRange(from...to)
            array[i] = UInt8(sub, radix: 16) ?? 0
            from = to.successor()
        }
        return array
    }

}
Sergey Aldoukhov
  • 22,316
  • 18
  • 72
  • 99
-1

here is the more generic, "pure swift" approach (no Foundation required :-))

extension UnsignedInteger {
    var hex: String {
        var str = String(self, radix: 16, uppercase: true)
        while str.characters.count < 2 * MemoryLayout<Self>.size {
            str.insert("0", at: str.startIndex)
        }
        return str
    }
}

extension Array where Element: UnsignedInteger {
    var hex: String {
        var str = ""
        self.forEach { (u) in
            str.append(u.hex)
        }
        return str
    }
}

let str = [UInt8(1),22,63,41].hex  // "01163F29"
let str2 = [UInt(1),22,63,41].hex  // "00000000000000010000000000000016000000000000003F0000000000000029"

extension String {
    func toUnsignedInteger<T:UnsignedInteger>()->[T]? {
        var ret = [T]()
        let nibles = MemoryLayout<T>.size * 2
        for i in stride(from: 0, to: characters.count, by: nibles) {
            let start = self.index(startIndex, offsetBy: i)
            guard let end = self.index(start, offsetBy: nibles, limitedBy: endIndex),
                let ui = UIntMax(self[start..<end], radix: 16) else { return nil }
            ret.append(T(ui))
        }
        return ret
    }
}

let u0:[UInt8]? = str.toUnsignedInteger()                   // [1, 22, 63, 41]
let u1 = "F2345f".toUnsignedInteger() as [UInt8]?           // [18, 52, 95]
let u2 = "12345f".toUnsignedInteger() as [UInt16]?          // nil
let u3 = "12345g".toUnsignedInteger() as [UInt8]?           // nil
let u4 = "12345f".toUnsignedInteger() as [UInt]?            // nil
let u5 = "12345678".toUnsignedInteger() as [UInt8]?         // [18, 52, 86, 120]
let u6 = "12345678".toUnsignedInteger() as [UInt16]?        // [4660, 22136]
let u7 = "1234567812345678".toUnsignedInteger() as [UInt]?  // [1311768465173141112]

It is very easily to do the same for SignedInteger as well, but better approach will be to map results to signed type

let u8 = u1?.map { Int8(bitPattern: $0) }                    // [-14, 52, 95]
user3441734
  • 16,722
  • 2
  • 40
  • 59