snippetswiftMinor
Swift format string with separator every n characters where n changes
Viewed 0 times
swiftformatwithwhereeveryseparatorchangescharactersstring
Problem
In Swift I have a string where a dash needs to be inserted after every 3 characters, if there are 4 characters or 2 characters at the end of the string they should be shown as "-xx-xx" or "-xx" respectively.
I have the following solution:
It will add a dash before each third char unless it is the first or last char or it is two from the end but only if the char count is divisible by 3 then it adds a char to the formatted string as normal.
Is there a more optimal way to achieve this formatting? What approach should I be taking with this kind of problem?
E.g "203940399345" to "203-940-399-345"
"2039403993454" to "203-940-399-34-54"
"20394039934546" to "203-940-399-345-46"
"2039403993454699409399" to "203-940-399-345-469-940-93-99"I have the following solution:
func format(_ unformatted:String) -> String {
var formatted = ""
let count = unformatted.characters.count
unformatted.characters.enumerated().forEach {
if $0.offset % 3 == 0 && $0.offset != 0 && $0.offset != count - 1 || $0.offset == count - 2 && count % 3 != 0 {
formatted += "-" + String($0.element)
return
}
formatted += String($0.element)
return
}
return formatted
}It will add a dash before each third char unless it is the first or last char or it is two from the end but only if the char count is divisible by 3 then it adds a char to the formatted string as normal.
Is there a more optimal way to achieve this formatting? What approach should I be taking with this kind of problem?
Solution
There is one small bug in your code, a two-character string is formatted with an initial dash:
There are various ways to fix that, e.g. by replacing the condition
with
Now some suggestions to simplify the code and make it more readable.
Instead of
I would use
There is no need to convert
append a character directly:
There is also no need to call
The condition
is quite complex, I would split it into two conditions with
Putting it all together, the function would then look like this:
The condition whether to insert the separator at an offset is quite complex and
it is easy to make an error. A completely different approach would be
a recursive implementation, which is (almost) self-explaining:
More suggestions:
print(format("12")) // -12There are various ways to fix that, e.g. by replacing the condition
$0.offset == count - 2 && count % 3 != 0with
$0.offset == count - 2 && $0.offset % 3 == 2Now some suggestions to simplify the code and make it more readable.
Instead of
unformatted.characters.enumerated().forEach {
// ... use `$0.offset` and `$0.element` ...
}I would use
for ... in, so that we can give the loop variables names:for (offset, char) in unformatted.characters.enumerated() {
// ... use `offset` and `char` ...
}There is no need to convert
$0.element to a String because you canappend a character directly:
formatted.append($0.element)There is also no need to call
return at the end of the iteration block.The condition
if $0.offset % 3 == 0 && $0.offset != 0 && $0.offset != count - 1 || $0.offset == count - 2 && $0.offset % 2 == 2is quite complex, I would split it into two conditions with
if/else if.Putting it all together, the function would then look like this:
func format(_ unformatted: String) -> String {
var formatted = ""
let count = unformatted.characters.count
for (offset, char) in unformatted.characters.enumerated() {
if offset > 0 && offset % 3 == 0 && offset != count - 1 {
formatted.append("-")
} else if offset % 3 == 2 && offset == count - 2 {
formatted.append("-")
}
formatted.append(char)
}
return formatted
}The condition whether to insert the separator at an offset is quite complex and
it is easy to make an error. A completely different approach would be
a recursive implementation, which is (almost) self-explaining:
func format(_ str: String) -> String {
switch str.characters.count {
case 0...3: // No separators for strings up to length 3.
return str
case 4: // "abcd" -> "ab-cd"
let idx = str.index(str.startIndex, offsetBy: 2)
return str.substring(to: idx) + "-" + str.substring(from: idx)
default: // At least 5 characters. Separate the first three and recurse:
let idx = str.index(str.startIndex, offsetBy: 3)
return str.substring(to: idx) + "-" + format(str.substring(from: idx))
}
}More suggestions:
- Choose a different name instead of
format()which indicates the purpose of the formatting.
- Make the separator character a parameter of the function.
Code Snippets
print(format("12")) // -12$0.offset == count - 2 && count % 3 != 0$0.offset == count - 2 && $0.offset % 3 == 2unformatted.characters.enumerated().forEach {
// ... use `$0.offset` and `$0.element` ...
}for (offset, char) in unformatted.characters.enumerated() {
// ... use `offset` and `char` ...
}Context
StackExchange Code Review Q#160415, answer score: 3
Revisions (0)
No revisions yet.