How add separator to string at every N characters in swift?

34,651

Solution 1

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 { 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"

Solution 2

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

Solution 3

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

Solution 4

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

Solution 5

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()
    }
Share:
34,651
Bolo
Author by

Bolo

un cafesito por favor

Updated on July 09, 2022

Comments

  • Bolo
    Bolo almost 2 years

    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 ?

  • Leo Dabus
    Leo Dabus over 8 years
    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
    Leo Dabus over 8 years
    intoString.characters.enumerate().forEach { index, c in if index % afterEveryXChars == 0 && index > 0 { ...
  • luk2302
    luk2302 over 8 years
    @LeoDabus not at all, go ahead - are you still playing around with it? :P
  • Leo Dabus
    Leo Dabus over 8 years
    not playing around. I am happy with mine :) just to let you know how to use enumerate()
  • luk2302
    luk2302 over 8 years
    that code leaves a trailing :, try adding the : in front of the $0.element if and only if you are not at the first character.
  • Leo Dabus
    Leo Dabus over 8 years
    just add .flatten().dropLast()
  • 0x416e746f6e
    0x416e746f6e over 8 years
    The request was "after each 2 characters". If trailing ":" is not desired then, yes what you suggest will do, that is: $0.index > 0 && $0.index % 2 == 0 ? [":", $0.element] : [$0.element]
  • 0x416e746f6e
    0x416e746f6e over 8 years
    @LeoDabus, no, dropLast will consume payload character in case if original string is of odd length.
  • Leo Dabus
    Leo Dabus over 8 years
    @AntonBronnikov easy solution just add it in front of it and use dropFirst
  • Leo Dabus
    Leo Dabus over 8 years
    @AntonBronnikov String(x.characters.enumerate().map() { $0.index % 2 == 1 ? [$0.element] : [":",$0.element] }.flatten().dropFirst())
  • TheValyreanGroup
    TheValyreanGroup about 7 years
    @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.
  • Leo Dabus
    Leo Dabus about 7 years
    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.
  • TheValyreanGroup
    TheValyreanGroup about 7 years
    @LeoDabus Great, take a look here. stackoverflow.com/questions/43005306/…
  • Leo Dabus
    Leo Dabus about 7 years
    @TheValyreanGroup so you want to split your string every space after the 9th ?
  • TheValyreanGroup
    TheValyreanGroup about 7 years
    @LeoDabus No, Every space before the 9th.
  • Leo Dabus
    Leo Dabus about 7 years
    So what if you have words longer than 9. The problem is that the rules are not clear
  • Leo Dabus
    Leo Dabus about 7 years
    I have written an extension that breaks up the string into an array of words that might help you achieve what you want stackoverflow.com/a/39667966/2303865
  • TheValyreanGroup
    TheValyreanGroup about 7 years
    Yea I thought about just splitting it into an array of words, but figured their might be a cleaner way. I guess not. Thanks.
  • Jonny
    Jonny about 7 years
    I need it to go from the back (who doesn't?). This one doesn't do that, AFAIK.
  • Renata Faria
    Renata Faria over 5 years
    My xcode ask for a return when declare the function, something like this: func separate(every stride: Int = 4, with separator: Character = " ") -> String {
  • Leo Dabus
    Leo Dabus over 5 years
    newText.append(character)
  • TruMan1
    TruMan1 about 5 years
    This doesn't compile.
  • TruMan1
    TruMan1 about 5 years
    This doesn't compile.
  • Stéphane de Luca
    Stéphane de Luca about 5 years
    @TruMan1: what Xcode and Swift version are you using?
  • Stéphane de Luca
    Stéphane de Luca about 5 years
    @TruMan1: give me the compilation error you get plz
  • TruMan1
    TruMan1 about 5 years
    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]]>)'
  • michalmichalek
    michalmichalek almost 4 years
    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"
  • Leo Dabus
    Leo Dabus almost 4 years
    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 } }
  • michalmichalek
    michalmichalek almost 4 years
    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".
  • Leo Dabus
    Leo Dabus almost 4 years
    my bad I accidentally removed the dropFirst method for index in indices.dropFirst().reversed() {
  • michalmichalek
    michalmichalek almost 4 years
    Oh yes... it was so easy. I'm so dumb. Thanks you very much for your help. Have a great day!
  • laka
    laka over 3 years
    This is a pretty great solution. I think it's the most swifty and easiest to understand solution.
  • MihaiL
    MihaiL over 3 years
    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).
  • Leo Dabus
    Leo Dabus over 3 years
    @StéphanedeLuca you can simply use flatMap instead of map and joined .init(enumerated().flatMap { $0 > 0 && $0 % stride == 0 ? [separator, $1] : [$1]})
  • Ahmadreza
    Ahmadreza almost 3 years
    what if we want to use a string as a separator?
  • Goppinath
    Goppinath over 2 years
    Thanks @MihaiL, Simple and effective solution.