1

I need to split a string into 2-letter pieces. Like “friend" -> "fr" "ie" "nd". (Okay, its a step for me to change HEX string to Uint8 Array)

My code is

    for i=0; i<chars.count/2; i++ {
        let str = input[input.startIndex.advancedBy(i*2)..<input.startIndex.advancedBy(i*2+1)]
        bytes.append(UInt8(str,radix: 16)!)
    }

But I don't know why I cannot use Range to do this split. And I have no idea what will happen when i*2+1 is bigger than string's length. So what's the best way to cut Swift string into 2-letter-strings?

Desmond
  • 767
  • 1
  • 6
  • 18
  • http://stackoverflow.com/questions/33546967/hex-string-convert-string-uint8-array-convert-string-swift-swift-2/33548238#33548238 – Leo Dabus Apr 25 '17 at 12:59

2 Answers2

3

Your range wasn't working because you need to use ... instead of ..<.

let input = "ff103"
var bytes = [UInt8]()

let strlen = input.characters.count
for i in 0 ..< (strlen + 1)/2 {
    let str = input[input.startIndex.advancedBy(i*2)...input.startIndex.advancedBy(min(strlen - 1, i*2+1))]
    bytes.append(UInt8(str,radix: 16) ?? 0)
}

print(bytes)  // [255, 16, 3]

Here is another take on splitting the string into 2-letter strings. advancedBy() is an expensive O(n) operation, so this version keeps track of start and just marches it ahead by 2 each loop, and end is based on start:

let input = "friends"
var strings = [String]()

let strlen = input.characters.count
var start = input.startIndex
let lastIndex = strlen > 0 ? input.endIndex.predecessor() : input.startIndex

for i in 0 ..< (strlen + 1)/2 {
    start = i > 0 ? start.advancedBy(2) : start
    let end = start < lastIndex ? start.successor() : start
    let str = input[start...end]
    strings.append(str)
}

print(strings) // ["fr", "ie", "nd", "s"]

Alternate Answer:

Using ranges is probably overkill. It is easy just to add the characters to an array and make Strings from those:

let input = "friends"
var strings = [String]()
var newchars = [Character]()

for c in input.characters {
    newchars.append(c)
    if newchars.count == 2 {
        strings.append(String(newchars))
        newchars = []
    }
}

if newchars.count > 0 {
    strings.append(String(newchars))
}

print(strings) // ["fr", "ie", "nd", "s"]

And here is the new version for making [UInt8]:

let input = "ff103"
var bytes = [UInt8]()
var newchars = [Character]()

for c in input.characters {
    newchars.append(c)
    if newchars.count == 2 {
        bytes.append(UInt8(String(newchars), radix: 16) ?? 0)
        newchars = []
    }
}

if newchars.count > 0 {
    bytes.append(UInt8(String(newchars), radix: 16) ?? 0)
}

print(bytes) // [255, 16, 3]

Based on @LeoDabus' answer, we can make an extension with a method that will return substrings of any length, and a computed property that returns [UInt8]:

extension String {
    func substringsOfLength(length: Int) -> [String] {
        if length < 1 { return [] }

        var result:[String] = []
        let chars = Array(characters)
        for index in 0.stride(to: chars.count, by: length) {
            result.append(String(chars[index ..< min(index+length, chars.count)]))
        }
        return result
    }

    var toUInt8: [UInt8] {
        var result:[UInt8] = []
        let chars = Array(characters)
        for index in 0.stride(to: chars.count, by: 2) {
            let str = String(chars[index ..< min(index+2, chars.count)])
            result.append(UInt8(str, radix: 16) ?? 0)
        }
        return result
    }
}

let input = "friends"
let str2 = input.substringsOfLength(2)  // ["fr", "ie", "nd", "s"]
let str0 = input.substringsOfLength(0)  // []
let str3 = input.substringsOfLength(3)  // ["fri", "end", "s"]

let bytes = "ff107".toUInt8  // [255, 16, 7]
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • My mistake! So this advancedBy is safe and there will be no overflow problem? – Desmond Dec 15 '15 at 02:13
  • The way you've written your `for` loop, the last odd character will never be reached. That is because `input.characters.count/2` is integer division and drops the remainder. For example, if there are 3 characters in the string, the loop will only run once. – vacawama Dec 15 '15 at 02:19
  • If you try to advance the index past the end of the string, you'll get `fatal error: can not increment endIndex`. – vacawama Dec 15 '15 at 02:22
  • What would you like to do with that last character in a string with odd length? – vacawama Dec 15 '15 at 02:25
  • Here I can make sure always send even length string to this function. But if I need to deal with odd length string, I can only check out whether it is safe in my `for` loop by adding some extra code. – Desmond Dec 15 '15 at 02:31
  • Do you want `"friends"` to become `["fr", "ie", "nd", "s"]`? – vacawama Dec 15 '15 at 02:35
  • Yes, that's what I want~ and I get to know how to reach that now. Thanks~ – Desmond Dec 15 '15 at 02:45
  • Okay, that's a very elegant way~ – Desmond Dec 15 '15 at 02:48
1

Another option just for fun:

extension String {
    var pairs:[String] {
        var result:[String] = []
        let chars = Array(characters)
        for index in 0.stride(to: chars.count, by: 2) {
            result.append(String(chars[index..<min(index+2, chars.count)]))
        }
        return result
    }
}
let input = "friends"
let pairs = input.pairs
print(pairs) // ["fr", "ie", "nd", "s"]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571