48

I have a string which contains binary digits. How to separate it in to pairs of digits?

Suppose the string is:

let x = "11231245"

I want to add a separator such as ":" (i.e., a colon) after each 2 characters.

I would like the output to be:

"11:23:12:45"

How could I do this in Swift ?

Benjohn
  • 13,228
  • 9
  • 65
  • 127
Bolo
  • 2,668
  • 4
  • 27
  • 34
  • For the record and your own benefit in the future, `:` is a "colon". "Comma" means `,`. – Arc676 Dec 24 '15 at 14:54
  • what have you tried so far? note that the only code line you provided is in fact not even valid code – luk2302 Dec 24 '15 at 14:54

13 Answers13

76

Swift 5.2 • Xcode 11.4 or later

extension Collection {

    func unfoldSubSequences(limitedTo maxLength: Int) -> UnfoldSequence<SubSequence,Index> {
        sequence(state: startIndex) { start in
            guard start < endIndex else { return nil }
            let end = index(start, offsetBy: maxLength, limitedBy: endIndex) ?? endIndex
            defer { start = end }
            return self[start..<end]
        }
    }

    func every(n: Int) -> UnfoldSequence<Element,Index> {
        sequence(state: startIndex) { index in
            guard index < endIndex else { return nil }
            defer { let _ = formIndex(&index, offsetBy: n, limitedBy: endIndex) }
            return self[index]
        }
    }

    var pairs: [SubSequence] { .init(unfoldSubSequences(limitedTo: 2)) }
}

extension StringProtocol where Self: RangeReplaceableCollection {

    mutating func insert<S: StringProtocol>(separator: S, every n: Int) {
        for index in indices.every(n: n).dropFirst().reversed() {
            insert(contentsOf: separator, at: index)
        }
    }

    func inserting<S: StringProtocol>(separator: S, every n: Int) -> Self {
        .init(unfoldSubSequences(limitedTo: n).joined(separator: separator))
    }
}

Testing

let str = "112312451"

let final0 = str.unfoldSubSequences(limitedTo: 2).joined(separator: ":")
print(final0)      // "11:23:12:45:1"

let final1 = str.pairs.joined(separator: ":")
print(final1)      // "11:23:12:45:1"

let final2 = str.inserting(separator: ":", every: 2)
print(final2)      // "11:23:12:45:1\n"

var str2 = "112312451"
str2.insert(separator: ":", every: 2)
print(str2)   // "11:23:12:45:1\n"

var str3 = "112312451"
str3.insert(separator: ":", every: 3)
print(str3)   // "112:312:451\n"

var str4 = "112312451"
str4.insert(separator: ":", every: 4)
print(str4)   // "1123:1245:1\n"
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • @LeoDabus Did you write these extensions? If so, could you help with what i'm trying to do? I'm trying to insert a "|" every 70 characters but only if that 70th character is a space. If it's not, stride backwards until it finds a space. I'm using this as a new line generator basically. I can't seem to figure out how to go about doing that and i've tried many different ways. – TheValyreanGroup Mar 24 '17 at 16:40
  • Yes I wrote all of them. Feel free to post your approach to the problem and what went wrong in a new question and I will take a look at it. – Leo Dabus Mar 24 '17 at 16:42
  • @LeoDabus Great, take a look here. http://stackoverflow.com/questions/43005306/insert-character-into-string-every-nth-index-unless-its-a-space – TheValyreanGroup Mar 24 '17 at 17:06
  • @TheValyreanGroup so you want to split your string every space after the 9th ? – Leo Dabus Mar 24 '17 at 17:15
  • @LeoDabus No, Every space before the 9th. – TheValyreanGroup Mar 24 '17 at 17:16
  • So what if you have words longer than 9. The problem is that the rules are not clear – Leo Dabus Mar 24 '17 at 17:16
  • I have written an extension that breaks up the string into an array of words that might help you achieve what you want http://stackoverflow.com/a/39667966/2303865 – Leo Dabus Mar 24 '17 at 17:18
  • Yea I thought about just splitting it into an array of words, but figured their might be a cleaner way. I guess not. Thanks. – TheValyreanGroup Mar 24 '17 at 17:27
  • I need it to go from the back (who doesn't?). This one doesn't do that, AFAIK. – Jonny Apr 17 '17 at 02:21
  • Do you know how to reverse this separator function? I want to add seperator but stars from the end of string. Desired result: `var str = "12345678"` `str.insert(separator: " ", every: 3)` `print(str) // "12 345 678\n"` – mmm Jun 02 '20 at 14:33
  • `var counter = 0` `for index in indices.reversed() {` `if (distance(from: endIndex, to: index) + counter).isMultiple(of: n) {` `insert(contentsOf: separator, at: index)` `counter += 1` `}` `}` – Leo Dabus Jun 02 '20 at 14:53
  • It's works almost fine except that sometimes it adds separator in 0 index. `var str = "12345678"` `str.insert(separator: "x", every: 2)` `print(str) // "x12x34x56x78\n"` I want to avoid this first "x". – mmm Jun 03 '20 at 06:05
  • my bad I accidentally removed the dropFirst method `for index in indices.dropFirst().reversed() {` – Leo Dabus Jun 03 '20 at 06:12
  • 1
    Oh yes... it was so easy. I'm so dumb. Thanks you very much for your help. Have a great day! – mmm Jun 03 '20 at 06:13
41

I'll go for this compact solution (in Swift 4) :

let s = "11231245"
let r = String(s.enumerated().map { $0 > 0 && $0 % 2 == 0 ? [":", $1] : [$1]}.joined())

You can make an extension and parameterize the stride and the separator so that you can use it for every value you want (In my case, I use it to dump 32-bit space-operated hexadecimal data):

extension String {
    func separate(every stride: Int = 4, with separator: Character = " ") -> String {
        return String(enumerated().map { $0 > 0 && $0 % stride == 0 ? [separator, $1] : [$1]}.joined())
    }
}

In your case this gives the following results:

let x = "11231245"
print (x.separate(every:2, with: ":")

$ 11:23:12:45
Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
  • 2
    My xcode ask for a return when declare the function, something like this: func separate(every stride: Int = 4, with separator: Character = " ") -> String { – Renata Faria Aug 24 '18 at 09:27
  • This doesn't compile. – TruMan1 Feb 28 '19 at 17:52
  • @TruMan1: what Xcode and Swift version are you using? – Stéphane de Luca Mar 01 '19 at 14:12
  • @TruMan1: give me the compilation error you get plz – Stéphane de Luca Mar 02 '19 at 15:42
  • 1
    Sorry I just realized I was trying to use a string as the separator. I like you're one liner and is actually easier to understand (I wonder the performance comparison). How can you adjust your extension to accept a string as the separator? The error I get when I switch to string type is: Cannot invoke initializer for type 'init(_:)' with an argument list of type '(FlattenCollection<[[Any]]>)' – TruMan1 Mar 02 '19 at 15:55
  • 1
    @StéphanedeLuca you can simply use flatMap instead of map and joined `.init(enumerated().flatMap { $0 > 0 && $0 % stride == 0 ? [separator, $1] : [$1]})` – Leo Dabus Oct 15 '20 at 14:18
21

Swift 5.3

    /// Adds a separator at every N characters
    /// - Parameters:
    ///   - separator: the String value to be inserted, to separate the groups. Default is " " - one space.
    ///   - stride: the number of characters in the group, before a separator is inserted. Default is 4.
    /// - Returns: Returns a String which includes a `separator` String at every `stride` number of characters.
    func separated(by separator: String = " ", stride: Int = 4) -> String {
        return enumerated().map { $0.isMultiple(of: stride) && ($0 != 0) ? "\(separator)\($1)" : String($1) }.joined()
    }
MihaiL
  • 1,558
  • 1
  • 9
  • 11
  • This is a pretty great solution. I think it's the most swifty and easiest to understand solution. – laka Sep 18 '20 at 15:34
  • 1
    Thanks. I've added a small fix. It's an additional check - as I was adding an extra space for the first character (0 is multiple of any number). – MihaiL Sep 21 '20 at 12:41
  • Thanks @MihaiL, Simple and effective solution. – Goppinath Jan 07 '22 at 07:21
18

Short and simple, add a let or two if you want

extension String {

    func separate(every: Int, with separator: String) -> String {
        return String(stride(from: 0, to: Array(self).count, by: every).map {
            Array(Array(self)[$0..<min($0 + every, Array(self).count)])
        }.joined(separator: separator))
    }
}

let a = "separatemepleaseandthankyou".separate(every: 4, with: " ")

a is

sepa rate mepl ease andt hank you

Joe Maher
  • 5,354
  • 5
  • 28
  • 44
13

Its my code in swift 4

let x = "11231245"

var newText = String()
    for (index, character) in x.enumerated() {
        if index != 0 && index % 2 == 0 {
            newText.append(":")
        }
        newText.append(String(character))
    }
    print(newText)

Outputs 11:23:12:45

Evgeniy
  • 131
  • 1
  • 2
7

My attempt at that code would be:

func insert(seperator: String, afterEveryXChars: Int, intoString: String) -> String {
    var output = ""
    intoString.characters.enumerate().forEach { index, c in
        if index % afterEveryXChars == 0 && index > 0 {
            output += seperator
        }
        output.append(c)
    }
    return output
}

insert(":", afterEveryXChars: 2, intoString: "11231245")

Which outputs

11:23:12:45

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
luk2302
  • 55,258
  • 23
  • 97
  • 137
  • Do you mind if I edit your code ? There is no need to use a counter, you can use enumerate(). You can also add an additional condition (index > 0) to add the separator to be able to get rid of that dropFirst method – Leo Dabus Dec 24 '15 at 15:57
  • 1
    intoString.characters.enumerate().forEach { index, c in if index % afterEveryXChars == 0 && index > 0 { ... – Leo Dabus Dec 24 '15 at 16:00
  • @LeoDabus not at all, go ahead - are you still playing around with it? :P – luk2302 Dec 24 '15 at 16:04
  • 1
    not playing around. I am happy with mine :) just to let you know how to use enumerate() – Leo Dabus Dec 24 '15 at 16:04
7
let y = String(
    x.characters.enumerate().map() {
        $0.index % 2 == 0 ? [$0.element] : [$0.element, ":"]
    }.flatten()
)
0x416e746f6e
  • 9,872
  • 5
  • 40
  • 68
6

A simple One line of code for inserting separater ( Swift 4.2 ):-

let testString = "123456789"

let ansTest = testString.enumerated().compactMap({ ($0 > 0) && ($0 % 2 == 0) ? ":\($1)" : "\($1)" }).joined() ?? ""
print(ansTest) // 12:34:56:78:9
ilyas
  • 94
  • 1
  • 2
  • 7
5

Swift 4.2.1 - Xcode 10.1

extension String {

    func insertSeparator(_ separatorString: String, atEvery n: Int) -> String {
        guard 0 < n else { return self }
        return self.enumerated().map({String($0.element) + (($0.offset != self.count - 1 && $0.offset % n ==  n - 1) ? "\(separatorString)" : "")}).joined()
    }

    mutating func insertedSeparator(_ separatorString: String, atEvery n: Int) {
        self = insertSeparator(separatorString, atEvery: n)
    }
}

Usage

let testString = "11231245"

let test1 = testString.insertSeparator(":", atEvery: 2)
print(test1) // 11:23:12:45

var test2 = testString
test2.insertedSeparator(",", atEvery: 3)
print(test2) // 112,312,45    
choofie
  • 2,575
  • 1
  • 25
  • 30
3

I'm little late here, but i like to use regex like in this:

extension String {
    func separating(every: Int, separator: String) -> String {
        let regex = #"(.{\#(every)})(?=.)"#
        return self.replacingOccurrences(of: regex, with: "$1\(separator)", options: [.regularExpression])
    }
}

"111222333".separating(every: 3, separator: " ")

the output:

"111 222 333"
gigomar
  • 75
  • 2
0
extension String{

func separate(every: Int) -> [String] {
    return stride(from: 0, to: count, by: every).map {
        let ix0 = index(startIndex, offsetBy: $0);
        let ix1 = index(after:ix0);
        if ix1 < endIndex {
            return String(self[ix0...ix1]);
        }else{
            return String(self[ix0..<endIndex]);
        }
    }
}

/// or O(1) implementation (without count)

func separate(every: Int) -> [String] {
    var parts:[String] = [];
    var ix1 = startIndex;
    while ix1 < endIndex {
        let ix0 = ix1;
        var n = 0;
        while ix1 < endIndex && n < every {
            ix1 = index(after: ix1);
            n += 1;
        }
        parts.append(String(self[ix0..<ix1]));
    }
    return parts;
}

"asdf234sdf".separate(every: 2).joined(separator: ":");
john07
  • 562
  • 6
  • 16
0

A simple String extension that doesn't require the original string to be a multiple of the step size (increment):

extension String {
    func inserted(_ newElement: Character,atEach increment:Int)->String {
        var newStr = self

        for indx in stride(from: increment, to: newStr.count, by: increment).reversed() {
            let index = String.Index(encodedOffset: indx)
            newStr.insert(newElement, at: index)
        }

        return newStr
    }
 }
Steig
  • 173
  • 2
  • 5
0

If you are looking for converting string into a macaddress format, you can use this extension

extension String {
    public func toMACAdress() -> String? {
        do {
            let regex = try NSRegularExpression(pattern: "(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})",
                                                options: NSRegularExpression.Options.caseInsensitive)
            var replace = "$1:$2:$3:$4:$5:$6"
            let range = NSMakeRange(0, count)
            let mac = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replace)
            return mac.lowercased()
            
        } catch {
            return nil
        }
        
    }
}
Sarath Kumar
  • 47
  • 1
  • 9