Efficient algorithm to split a string based on multiple string delimiters - swift

I would like to know if there is an efficient way of splitting a string into multiple strings based on delimiters that are also strings.
Eg. updateStr = "23+45 = 56 0" , delimiters = ["+"," ","="]
Result = [23,45,56,0]
I tried the following code in swift:
for i in 0..<delimiter.count {
let res = updateStr.components(separatedBy: delimiter[i])
updateStr = res.joined(separator: "unique%")
}
splitTxt = updateStr.components(separatedBy: "unique%")
This works, but as the delimiters will be received dynamically I want a better approach.
Are there any efficient ways to avoid multiple loops to solve this?
An algorithm with more efficient solution that doesn't involve swift instance methods would also be appreciated.
Thanks for the answers but
To be clearer, I don't just want characters but strings as delimiters:
Eg2. updateStr = "I like playing with friends" , delimiters = ["li"," "la","ie"]
Result = ["I ","ke p","ying with fr","nds"]

The efficient way to do this sort of thing is with a Set:
let equation = "23+45 = 56 0"
let delimiters : [Character] = ["+"," ","="]
let setOfSeparators = Set(delimiters)
let result = equation.split {setOfSeparators.contains($0)}
print(result)
That's efficient because contains on a Set is extremely fast, so that cost is negligible and we are looping implicitly through the original string just once.
On the other hand, you could take advantage of the Cocoa CharacterSet class. For that, I would say:
let equation = "23+45 = 56 0"
let delimiters = ["+"," ","="]
let characterSet = CharacterSet(charactersIn: delimiters.joined())
let result = equation.components(separatedBy: characterSet).filter {!$0.isEmpty}
print(result)
Another fun way is to use a Scanner (these are underutilized in my opinion):
let equation = "23+45 = 56 0"
let delimiters = ["+"," ","="]
let characterSet = CharacterSet(charactersIn: delimiters.joined())
let scanner = Scanner(string: equation)
var result = [String]()
while let word = scanner.scanUpToCharacters(from: characterSet) {
result.append(word)
scanner.scanCharacters(from: characterSet)
}
print(result)

One of the components(separatedBy:) overloads will handle this automatically using a CharacterSet:
let delimiters = ["+"," ","="].compactMap(UnicodeScalar.init)
let splitTxt = updateStr.components(separatedBy: CharacterSet(delimiters))

NSRegularExpression provides the facility to split on general regular expressions, so this would enable splitting on a finite set of string delimiters using a delim1|delim2|delim3 regex. The following split operation does this job:
static func stringSubrange(str : String, st : Int, en : Int) -> String
{ var result : [Character] = [Character]()
var count : Int = 0
for index in str.indices
{ let c : Character = str[index]
count = count + 1
if count >= st && count <= en
{ result.append(c) }
else if count > en
{ return String(result) }
}
return String(result)
}
static func split(str: String, pattern: String) -> [String]
{ let rge = NSRange(location: 0, length: str.utf16.count)
let regexp = try! NSRegularExpression(pattern: pattern)
let pred = regexp.matches(in: str, options: [], range: rge)
var result : [String] = [String]()
var prev : Int = 1;
for p in pred
{ let range = p.range
let splitString = Ocl.stringSubrange(str: str, st: prev, en: range.location)
prev = range.location + range.length + 1
if splitString.count > 0
{ result.append(splitString) }
}
if prev < str.count
{ result.append(Ocl.stringSubrange(str: str, st: prev, en: str.count)) }
return result
}

Related

Replacing two ranges in a String simultaneously

Say you have a string that looks likes this:
let myStr = "Hello, this is a test String"
And you have two Ranges,
let rangeOne = myStr.range(of: "Hello") //lowerBound: 0, upperBound: 4
let rangeTwo = myStr.range(of: "this") //lowerBound: 7, upperBound: 10
Now you wish to replace those ranges of myStr with new characters, that may not be the same length as their original, you end up with this:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
myStr.replaceSubrange(rangeOne, with: "Bonjour") //Bonjour, this is a test String
myStr.replaceSubrange(rangeTwo, with: "ce") //Bonjourceis is a test String
Because rangeTwo is based on the pre-altered String, it fails to properly replace it.
I could store the length of the replacement and use it to reconstruct a new range, but there is no guarantee that rangeOne will be the first to be replaced, nor that rangeOne will actually be first in the string.
The solution is the same as removing multiple items from an array by index in a loop.
Do it backwards
First replace rangeTwo then rangeOne
myStr.replaceSubrange(rangeTwo, with: "ce")
myStr.replaceSubrange(rangeOne, with: "Bonjour")
An alternative could be also replacingOccurrences(of:with:)
This problem can be solved by shifting the second range based on the length of first the replaced string.
Using your code, here is how you would do it:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
let shift = "Bonjour".count - "Hello".count
let shiftedTwo = myStr.index(rangeTwo.lowerBound, offsetBy: shift)..<myStr.index(rangeTwo.upperBound, offsetBy: shift)
myStr.replaceSubrange(rangeOne, with: "Bonjour") // Bonjour, this is a test String
myStr.replaceSubrange(shiftedTwo, with: "ce") // Bonjour, ce is a test String
You can sort the range in descending order, then replace backwards, from the end to the start. So that any subsequent replacement will not be affect by the previous replacements. Also, it is safer to use replacingCharacters instead of replaceSubrange in case when dealing with multi-codepoints characters.
let myStr = "Hello, this is a test String"
var ranges = [myStr.range(of: "Hello")!,myStr.range(of: "this")!]
ranges.shuffle()
ranges.sort(by: {$1.lowerBound < $0.lowerBound}) //Sort in reverse order
let newWords : [String] = ["Bonjour😀","ce"].reversed()
var newStr = myStr
for i in 0..<ranges.count
{
let range = ranges[i]
//check overlap
if(ranges.contains(where: {$0.overlaps(range)}))
{
//Some range over lap
throw ...
}
let newWord = newWords[i]
newStr = newStr.replacingCharacters(in: range, with: newWord)
}
print(newStr)
My solution ended up being to take the ranges and replacement strings, work backwards and replace
extension String {
func replacingRanges(_ ranges: [NSRange], with insertions: [String]) -> String {
var copy = self
copy.replaceRanges(ranges, with: insertions)
return copy
}
mutating func replaceRanges(_ ranges: [NSRange], with insertions: [String]) {
var pairs = Array(zip(ranges, insertions))
pairs.sort(by: { $0.0.upperBound > $1.0.upperBound })
for (range, replacementText) in pairs {
guard let textRange = Range(range, in: self) else { continue }
replaceSubrange(textRange, with: replacementText)
}
}
}
Which works out to be useable like this
var myStr = "Hello, this is a test."
let rangeOne = NSRange(location: 0, length: 5) // “Hello”
let rangeTwo = NSRange(location: 7, length: 4) // “this”
myStr.replaceRanges([rangeOne, rangeTwo], with: ["Bonjour", "ce"])
print(myStr) // Bonjour, ce is a test.

Split string to arrays with maximum variables in each array

I have a string of numbers (each number is separated by ,) that looks like this:
"12,3,5,75,584,364,57,88,94,4,79,333,7465,867,56,6,748,546,573,466"
I want to split the string to an array of strings, that each element is a string that has maximum 10 number in it.
For the example I've added I want to achieve something like this:
stringsArray:
Element 0: "12,3,5,75,584,364,57,88,94,4"
Element 1: "79,333,7465,867,56,6,748,546,573,466"
And so on...
I've been thinking a lot about a way to do this with Swift, but couldn't find anything...
Does anybody has an idea?
Thank you!
Step 1 - get fully separated array:
let numbers = "12,3,5".components(separatedBy: ",")
Step 2 - chunk your result to parts with ext:
extension Array {
func chunked(by chunkSize: Int) -> [[Element]] {
return stride(from: 0, to: self.count, by: chunkSize).map {
Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
}
}
}
let chunkedNumbers = numbers.chunked(by: 10)
Step 3:
let stringsArray = chunkedNumbers.map { $0.joined(separator: ",") }
Result: ["12,3,5,75,584,364,57,88,94,4", "79,333,7465,867,56,6,748,546,573,466"]
Link to gist playground.
I would look at the position of 10th comma in your original string, get the prefix up to this position, remove this prefix and repeat until remaining string is empty.
This is a bit brute force, but works.
I first add extension to String for convenience.
extension String {
func startIndexesOf(_ string: String) -> [Int] {
var result: [Int] = []
var start = startIndex
while let range = range(of: string, options: .literal, range: start..<endIndex) {
result.append(range.lowerBound.encodedOffset)
start = range.upperBound
}
return result
}
subscript (r: Range<Int>) -> String {
let start = index(self.startIndex, offsetBy: r.lowerBound)
let end = self.index(self.startIndex, offsetBy: r.upperBound)
return String(self[Range(start ..< end)])
}
}
let test = "12,3,5,75,584,364,57,88,94,4,79,333,7465,867,56,6,748,546,573,466,999"
var remaining = test
var arrayOf10 : [String] = []
repeat {
let indexes = remaining.startIndexesOf(",")
if indexes.count < 10 {
arrayOf10.append(remaining) // Just add what remains
break
}
let position = indexes[9]
let endBeginning = remaining.index(test.startIndex, offsetBy: position) // Beginning of what remain to parse
let beginningSubstring = remaining[remaining.startIndex ..< endBeginning]
let beginningText = String(beginningSubstring)
arrayOf10.append(beginningText)
let startNext = remaining.index(test.startIndex, offsetBy: position+1) // What will remain to parse after taking out the beginning
let remainingSubString = remaining[startNext ..< remaining.endIndex]
remaining = String(remainingSubString)
} while remaining.count > 0
for (c, s) in arrayOf10.enumerated() { print("Element", c, ": ", s)}
This will print as desired
Element 0 : 12,3,5,75,584,364,57,88,94,4
Element 1 : 79,333,7465,867,56,6,748,546,573,466
Element 2 : 999

Swift - Convert a binary string to its ascii values

I have a string of binary values e.g. "010010000110010101111001". Is there a simple way to convert this string into its ascii representation to get (in this case) "Hey"?
Only found the other way or things for Integer:
let binary = "11001"
if let number = Int(binary, radix: 2) {
print(number) // Output: 25
}
Do someone know a good and efficient solution for this case?
A variant of #OOPer's solution would be to use a conditionally binding while loop and index(_:offsetBy:limitedBy:) in order to iterate over the 8 character substrings, taking advantage of the fact that index(_:offsetBy:limitedBy:) returns nil when you try to advance past the limit.
let binaryBits = "010010000110010101111001"
var result = ""
var index = binaryBits.startIndex
while let next = binaryBits.index(index, offsetBy: 8, limitedBy: binaryBits.endIndex) {
let asciiCode = UInt8(binaryBits[index..<next], radix: 2)!
result.append(Character(UnicodeScalar(asciiCode)))
index = next
}
print(result) // Hey
Note that we're going via Character rather than String in the intermediate step – this is simply to take advantage of the fact that Character is specially optimised for cases where the UTF-8 representation fits into 63 bytes, which is the case here. This saves heap-allocating an intermediate buffer for each character.
Purely for the fun of it, another approach could be to use sequence(state:next:) in order to create a sequence of the start and end indices of each substring, and then reduce in order to concatenate the resultant characters together into a string:
let binaryBits = "010010000110010101111001"
// returns a lazily evaluated sequence of the start and end indices for each substring
// of 8 characters.
let indices = sequence(state: binaryBits.startIndex, next: {
index -> (index: String.Index, nextIndex: String.Index)? in
let previousIndex = index
// Advance the current index – if it didn't go past the limit, then return the
// current index along with the advanced index as a new element of the sequence.
return binaryBits.characters.formIndex(&index, offsetBy: 8, limitedBy: binaryBits.endIndex) ? (previousIndex, index) : nil
})
// iterate over the indices, concatenating the resultant characters together.
let result = indices.reduce("") {
$0 + String(UnicodeScalar(UInt8(binaryBits[$1.index..<$1.nextIndex], radix: 2)!))
}
print(result) // Hey
On the face of it, this appears to be much less efficient than the first solution (due to the fact that reduce should copy the string at each iteration) – however it appears the compiler is able to perform some optimisations to make it not much slower than the first solution.
You may need to split the input binary digits into 8-bit chunks, and then convert each chunk to an ASCII character. I cannot think of a super simple way:
var binaryBits = "010010000110010101111001"
var index = binaryBits.startIndex
var result: String = ""
for _ in 0..<binaryBits.characters.count/8 {
let nextIndex = binaryBits.index(index, offsetBy: 8)
let charBits = binaryBits[index..<nextIndex]
result += String(UnicodeScalar(UInt8(charBits, radix: 2)!))
index = nextIndex
}
print(result) //->Hey
Does basically the same as OOPer's solution, but he/she was faster and has a shorter, more elegant approach :-)
func getASCIIString(from binaryString: String) -> String? {
guard binaryString.characters.count % 8 == 0 else {
return nil
}
var asciiCharacters = [String]()
var asciiString = ""
let startIndex = binaryString.startIndex
var currentLowerIndex = startIndex
while currentLowerIndex < binaryString.endIndex {
let currentUpperIndex = binaryString.index(currentLowerIndex, offsetBy: 8)
let character = binaryString.substring(with: Range(uncheckedBounds: (lower: currentLowerIndex, upper: currentUpperIndex)))
asciiCharacters.append(character)
currentLowerIndex = currentUpperIndex
}
for asciiChar in asciiCharacters {
if let number = UInt8(asciiChar, radix: 2) {
let character = String(describing: UnicodeScalar(number))
asciiString.append(character)
} else {
return nil
}
}
return asciiString
}
let binaryString = "010010000110010101111001"
if let asciiString = getASCIIString(from: binaryString) {
print(asciiString) // Hey
}
A different approach
let bytes_string: String = "010010000110010101111001"
var range_count: Int = 0
let characters_array: [String] = Array(bytes_string.characters).map({ String($0)})
var conversion: String = ""
repeat
{
let sub_range = characters_array[range_count ..< (range_count + 8)]
let sub_string: String = sub_range.reduce("") { $0 + $1 }
let character: String = String(UnicodeScalar(UInt8(sub_string, radix: 2)!))
conversion += character
range_count += 8
} while range_count < characters_array.count
print(conversion)
You can do this:
extension String {
var binaryToAscii: String {
stride(from: 0, through: count - 1, by: 8)
.map { i in map { String($0)}[i..<(i + 8)].joined() }
.map { String(UnicodeScalar(UInt8($0, radix: 2)!)) }
.joined()
}
}

How to get `printf` to work as expected using Swift String? [duplicate]

What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:
printf("%-15s", var);
This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.
You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }
print("'\(formatted)'")
You are better to do it yourself
let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// " alpha"
// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha "
if you need something 'more general'
func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
let c = str.characters.count
let start = str.characters.startIndex
let end = str.characters.endIndex
var str = str
if c > fixLenght {
switch justifyToTheRigth {
case true:
let range = start.advancedBy(c - fixLenght)..<end
return String(str.characters[range])
case false:
let range = start..<end.advancedBy(fixLenght - c)
return String(str.characters[range])
}
} else {
var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
if justifyToTheRigth {
extraSpace.appendContentsOf(str)
return extraSpace
} else {
str.appendContentsOf(extraSpace)
return str
}
}
}
let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)
print(s0)
print(s1)
print(s2)
print(s3)
which prints
ABC
FGH
ABCDEFGH--
--ABCDEFGH
The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.
You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView
print(String(allChars)) // "ABCDEFGHIJKLMNO\n"
If you want to force padding, you could use an approach like this:
extension String {
func formatted(characterCount characterCount:Int) -> String {
if characterCount < characters.count {
return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
} else {
return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
}
}
}
let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print("!\(abc.formatted(characterCount: 15))!")
// "!ABC !\n"
print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Did you try this?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
print(formattedString)
//string1 - string2
We've got a ton of interesting answers now. Thank you everyone. I wrote the following:
func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {
var newString: String = stringToChange
newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
return newString
}
From one hand %# is used to format String objects:
import Foundation
var str = "Hello"
print(String(format: "%#", str))
But it does not support the width modifier:
print(String(format: "%-15#", str))
Will still print unpadded text:
"Hello\n"
However there is a modifier %s that seems to work with CStrings:
var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise
print(String(format: "%-15s", cstr!))
Output:
"Hello \n"
One nice thing is that you can use the same format specification with NSLog:
NSLog("%-15s", cstr!)
To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}

Swift: Format String width

What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:
printf("%-15s", var);
This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.
You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }
print("'\(formatted)'")
You are better to do it yourself
let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// " alpha"
// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha "
if you need something 'more general'
func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
let c = str.characters.count
let start = str.characters.startIndex
let end = str.characters.endIndex
var str = str
if c > fixLenght {
switch justifyToTheRigth {
case true:
let range = start.advancedBy(c - fixLenght)..<end
return String(str.characters[range])
case false:
let range = start..<end.advancedBy(fixLenght - c)
return String(str.characters[range])
}
} else {
var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
if justifyToTheRigth {
extraSpace.appendContentsOf(str)
return extraSpace
} else {
str.appendContentsOf(extraSpace)
return str
}
}
}
let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)
print(s0)
print(s1)
print(s2)
print(s3)
which prints
ABC
FGH
ABCDEFGH--
--ABCDEFGH
The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.
You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView
print(String(allChars)) // "ABCDEFGHIJKLMNO\n"
If you want to force padding, you could use an approach like this:
extension String {
func formatted(characterCount characterCount:Int) -> String {
if characterCount < characters.count {
return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
} else {
return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
}
}
}
let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print("!\(abc.formatted(characterCount: 15))!")
// "!ABC !\n"
print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Did you try this?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
print(formattedString)
//string1 - string2
We've got a ton of interesting answers now. Thank you everyone. I wrote the following:
func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {
var newString: String = stringToChange
newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
return newString
}
From one hand %# is used to format String objects:
import Foundation
var str = "Hello"
print(String(format: "%#", str))
But it does not support the width modifier:
print(String(format: "%-15#", str))
Will still print unpadded text:
"Hello\n"
However there is a modifier %s that seems to work with CStrings:
var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise
print(String(format: "%-15s", cstr!))
Output:
"Hello \n"
One nice thing is that you can use the same format specification with NSLog:
NSLog("%-15s", cstr!)
To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}