Reputation: 797
I am looking for the simplest way to manipulate a string with tokens inside in Swift (in C# I would have used string.Format("{0}", "str"). For example :
let exampleString = "The word {0} is composed of {1} syllable."
I would like to have :
exampleString.setToken(index: 0, token: "frame") // 1
exampleString.setToken(index: 1, token: "two")
print(
exampleString.asArray() // 2
== ["The world ", "frame", " is composed of ", "two", " syllable."]
) // Prints "true"
// Final use case :
exampleString.asArray().forEach {
if $0.isToken { // 3
doSomething($0)
} else {
doSomethingElse($0)
}
}
How can I manage both the string, tokens indexes and values in an easy way (1, 2 and 3) ? Does the Foundation API provides any way to handle this or do I have to go through a custom model and extentions ?
Upvotes: 2
Views: 858
Reputation: 56635
Foundation has format strings with a syntax that includes the type of the argument (and optionally an order of arguments).
In your case the format string could look like this (with or without the explicit argument order)
String(format: "The word %1$@ is composed of %2$ld syllables.", "frame", 2)
String(format: "The word %@ is composed of %ld syllables.", "frame", 2)
where the %1$@
is argument number one (1$
) and of an "object" type (@
), and %2$ld
is argument number two (2$
) and of an "64-bit integer" type (ld
).
You can read more about the format specifiers in the String Format Specifiers page of the String Programming Guide.
Additionally, if that works for your use-case, Swift's String Interpolation allows you to write the same thing as:
"The word \("frame") is composed of \(2) syllables."
Upvotes: 0
Reputation: 5521
While I don't fully understand why something like this would really be useful in the real world I came up with some ideas:
You could create a custom Class for handling these things. For my example I'm just calling it CustomString
, which is a bad name.
class CustomString {
internal var text: String
private var tokens: [Int : String]
internal var components: [String] {
get {
return self.text.components(separatedBy: " ")
}
}
init(text: String) {
self.text = text
self.tokens = [Int : String]()
}
func setToken(_ token: String, atIndex index: Int) {
self.tokens[index] = token
}
func token(forIndex index: String) -> String? {
let converted = Int(index.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: ""))
guard let validInt = converted else { return nil }
return self.tokens[validInt]
}
func fullString() -> String {
var fullString = self.text
self.tokens.forEach({ fullString = fullString.replacingOccurrences(of: "{\($0.key)}", with: $0.value) })
return fullString
}
func asArray() -> [String] {
return self.fullString().components(separatedBy: " ")
}
}
In addition, I added a computed property to a String
extension to handle the isToken
request. This could be implemented as a function on CustomString
too, but would require passing a string into the function, which would just not look as nice.
extension String {
var isToken: Bool {
get {
guard self.components(separatedBy: " ").count == 1 else { return false }
return self.first == "{" && self.last == "}"
}
}
}
And for usage, it'd be something like this:
let exampleString = "The word {0} is composed of {1} syllable."
let customVersion = CustomString(text: exampleString)
customVersion.setToken("string", atIndex: 0)
customVersion.setToken("one", atIndex: 1)
customVersion.fullString() //"The word string is composed of one syllable."
customVersion.asArray() //["The", "word", "string", "is", "composed", "of", "one", "syllable."]
customVersion.components.forEach({
if $0.isToken {
print("\(customVersion.token(forIndex: $0)) is a token")
}
})
//Prints: string is a token --- one is a token
I, personally, would avoid doing something like this in production code because there's just a ton of potential for bugs and unexpected behavior. The easiest to point out is this example:
let exampleString = "What word goes here: {0}?"
exampleString.setToken("Nothing", atIndex: 1)
This example also illustrates my main point about whether or not this is actually a useful task. If you have a static sentence with a known number of tokens, and you have to set each token in code, why not just use individual variables, or an array/dictionary, to hold all the "tokens" and use string interpolation with your sentence? This would also make all the other "functionality" easier as you wouldn't have to check the full sentence to find the tokens - you already have them in their own place, and can do whatever you want with them.
If the argument for doing this is that you are getting the pieces of the sentence from some external source, like an API or something, it seems even more unnecessary to me as your API could just give you the assembled sentence anyway.
Either way, this code is tested and works under normal conditions - it will fail if there isn't a token set for every index, or if any of the tokens contain { or } characters, the sentence isn't separated by spaces, etc. As a basic starting point this works.
Upvotes: 1
Reputation: 3210
I would try it like that:
var exampleString = "The word {0} is composed of {1} syllable."
exampleString = exampleString.replacingOccurrences(of: "{0}", with: "frame") // or "{\(0)}"
exampleString = exampleString.replacingOccurrences(of: "{1}", with: "two") // or "{\(1)}"
// ["The", "word", "frame", "is", "composed", "of", "two", "syllable."]
let exampleStringArray = exampleString.componentsSeparatedByString(" ")
for item in exampleStringArray {
// this could be more dynamic
if item == "frame" || item == "two" {
doSomething(item) // is a token
} else {
doSomethingElse(item) // is not a token
}
}
Is it something like that what you are looking for?
Code not testet, but it should work like that
To be more dynamic to check if an item is a token, you could have a tokens array like
let tokens = ["frame", "two"]
change the replacing occurences with something like that to loop over the tokens
for index in 0..<tokens.count {
exampleString = exampleString.replacingOccurrences(of: "{\(index)}", with: tokens[index])
}
and check if the item is a token with
for item in exampleStringArray {
if tokens.contains(item) {
doSomething(item)
} else {
doSomethingElse(item)
}
}
Hope this helps.
Upvotes: 0