There's certainly different ways to do this problem in Swift. Here's an example:
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
min(max(self, limits.lowerBound), limits.upperBound)
}
}
extension Int {
var nonNegative: Int {
clamped(to: 0...Int.max)
}
}
extension Collection {
var tailLength: Int {
(count - 1).nonNegative
}
var head: SubSequence { prefix(1) }
var tail: SubSequence { suffix(tailLength) }
}
extension String {
var uppercaseFirst: String {
head.localizedUppercase + tail
}
var words: [String] {
components(separatedBy: " ")
}
var uppercaseFirstWords: String {
words
.map(\.uppercaseFirst)
.joined(separator: " ")
}
}
let example = "upper case the first letters please"
print(example.uppercaseFirstWords)
Now is that shorter than your code? No.
Is it better? It's definitely different, so why might we prefer one or the other?
One reason to avoid the for loop and mutation is that it's difficult for people to think about how the dynamic runtime behaviour is, when they just read the code, i.e. the magic vs the spell. Generally it's ok to begin with and then you end up drowning in mutation the more you build it up.
The for-loop imperative style has the focus on all the details about the how to do something, (the reader has to follow all that through when reading the code) where the functional style is about what to do. The detail can get in the way, like temporary variables: "let myArr =". Your brain has to work through how all the mutation will work, compare that with something like
words.map(\.uppercaseFirst).joined(separator: " ")
is more like the description of what is wanted - take the words in the string, uppercase their first letters, then join them up with spaces. Hopefully simple to read.
On the other hand the functional style has a learning curve to it, for loops are easier to get started with. But the state juggling can result in things like:
if myArr.last != word
Which I guess has to do with avoiding a dangling " " - though it's not clear, so time has to be spent to understand that and make sure it's right.
Another advantage would be breaking up the pieces into having more reusable parts, so for example having the 'words' computed property on String means you can reuse it for other things - perhaps it would be useful to have an array of words from a string, let's say to work with the last word, or to count the words:
example.words.count
example.words.suffix(1)
example.words.suffix(1).map(\.uppercaseFirst)
example.words.randomElement().map { $0 + "!" }
Be wary about writing functions that bake in a 'side-effect' like printing its result. That means if you want to use it as part of some other processing, you can't. Consider changing the function signature:
func upperCaseFirstCharacter(_ str: String) -> String
Think carefully about the function names too, does it uppercase the first character of the string passed in?