How can I insert a character for every 1-3 characters in a string?

Let's say I have a variable that contains a string
var someString = "@@@@@@@@@@"

and I'd like to insert a "#" symbol for every 1-3 characters in the string, so basically I wanna create something like this:
var someString = "@@#@#@@@#@#@@#@"

How can I achieve this in Swift?

Here's one way:

var currentIndex = someString.startIndex
var counter: Int = .random(in: 1...3)
while currentIndex != someString.endIndex {
	if counter == 0 {
		someString.insert("#", at: currentIndex)
		someString.formIndex(after: &currentIndex) // skip newly inserted character
		counter = .random(in: 1...3) // restart counter
	} else {
		counter -= 1
	}
	someString.formIndex(after: &currentIndex) // go to next character
}
1 Like

I'm not sure if that works. Crashes on my raspberry pi, but it has spotty unicode support anyway:

var someString = "@@@@@@@@@@@@@@@@@@@@@@@@@@"

print(someString)

var currentIndex = someString.startIndex
var counter: Int = .random(in: 1...3)
while currentIndex != someString.endIndex {
	if counter == 0 {
		someString.insert("😀", at: currentIndex)
		someString.formIndex(after: &currentIndex) // skip newly inserted character
		counter = .random(in: 1...3) // restart counter
	} else {
		counter -= 1
	}
	someString.formIndex(after: &currentIndex) // go to next character
}

print(someString)
@@@@@@@@@@@@@@@@@@@@@@@@@@
Fatal error: file /home/buildSwiftOnARM/swift/stdlib/public/core/UnicodeHelpers.swift, line 142
Current stack trace:
Stack dump:
0.	Program arguments: /usr/bin/swift -frontend -interpret coll.swift -disable-objc-interop -color-diagnostics -module-name coll 
Illegal instruction

Also, there's an off-by-one error. Sometimes it has strings of 4 @s.

I can't think of a better way to do it right now, though.

1 Like

That gave me exactly what I wanted! You're a lifesaver! Thanks so much!!

Ah yes. Can't rely on the index to still work after someString.insert:

Calling this method invalidates any existing indices for use with this string.

So the above code is prone to crash, even though it might not crash every time.

This one should be correct since it avoids mutating the string while iterating on it:

var slices: [Substring] = []
var beginIndex = someString.startIndex
var currentIndex = someString.startIndex
var counter: Int = .random(in: 0..<3)
while currentIndex != someString.endIndex {
	if counter == 0 {
		slices.append(someString[beginIndex..<currentIndex])
		beginIndex = currentIndex // starting new slice at current index
		counter = .random(in: 0..<3)
	} else {
		counter -= 1
	}
	someString.formIndex(after: &currentIndex) // go to next character
}
slices.append(someString[beginIndex..<currentIndex])

someString = slices.joined(separator: "#")
4 Likes

Thanks a lot!!

extension String {
  func split<Lengths: Sequence>(toLengths lengths: Lengths) -> [Substring] where Lengths.Element == Int {
    var substrings: [Substring] = []
    var remainingString = self[...]
    
    for length in lengths {
      let prefix = remainingString.prefix(length)
      if prefix.count == 0 { break }
      substrings.append(prefix)
      remainingString = remainingString.dropFirst(length)
    }
    
    return substrings
  }
}

let someString = "abcdefghijklmnopqrstuvwxyz"
let lengths = sequence(first: Int.random(in: 1...3)) { _ in Int.random(in: 1...3) }
print(someString.split(toLengths: lengths).joined(separator: "#"))
2 Likes