How add separator to string at every N characters in swift?
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()
}
Comments
-
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 over 8 yearsDo 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 over 8 yearsintoString.characters.enumerate().forEach { index, c in if index % afterEveryXChars == 0 && index > 0 { ...
-
luk2302 over 8 years@LeoDabus not at all, go ahead - are you still playing around with it? :P
-
Leo Dabus over 8 yearsnot playing around. I am happy with mine :) just to let you know how to use enumerate()
-
luk2302 over 8 yearsthat 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 over 8 yearsjust add .flatten().dropLast()
-
0x416e746f6e over 8 yearsThe 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 over 8 years@LeoDabus, no, dropLast will consume payload character in case if original string is of odd length.
-
Leo Dabus over 8 years@AntonBronnikov easy solution just add it in front of it and use dropFirst
-
Leo Dabus over 8 years@AntonBronnikov String(x.characters.enumerate().map() { $0.index % 2 == 1 ? [$0.element] : [":",$0.element] }.flatten().dropFirst())
-
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 about 7 yearsYes 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 about 7 years@LeoDabus Great, take a look here. stackoverflow.com/questions/43005306/…
-
Leo Dabus about 7 years@TheValyreanGroup so you want to split your string every space after the 9th ?
-
TheValyreanGroup about 7 years@LeoDabus No, Every space before the 9th.
-
Leo Dabus about 7 yearsSo what if you have words longer than 9. The problem is that the rules are not clear
-
Leo Dabus about 7 yearsI 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 about 7 yearsYea I thought about just splitting it into an array of words, but figured their might be a cleaner way. I guess not. Thanks.
-
Jonny about 7 yearsI need it to go from the back (who doesn't?). This one doesn't do that, AFAIK.
-
Renata Faria over 5 yearsMy xcode ask for a return when declare the function, something like this: func separate(every stride: Int = 4, with separator: Character = " ") -> String {
-
Leo Dabus over 5 years
newText.append(character)
-
TruMan1 about 5 yearsThis doesn't compile.
-
TruMan1 about 5 yearsThis doesn't compile.
-
Stéphane de Luca about 5 years@TruMan1: what Xcode and Swift version are you using?
-
Stéphane de Luca about 5 years@TruMan1: give me the compilation error you get plz
-
TruMan1 about 5 yearsSorry 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 almost 4 yearsDo 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 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 almost 4 yearsIt'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 almost 4 yearsmy bad I accidentally removed the dropFirst method
for index in indices.dropFirst().reversed() {
-
michalmichalek almost 4 yearsOh yes... it was so easy. I'm so dumb. Thanks you very much for your help. Have a great day!
-
laka over 3 yearsThis is a pretty great solution. I think it's the most swifty and easiest to understand solution.
-
MihaiL over 3 yearsThanks. 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 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 almost 3 yearswhat if we want to use a string as a separator?
-
Goppinath over 2 yearsThanks @MihaiL, Simple and effective solution.