-1

What is the most succinct way to get the first character of each word in a string?

Beginning:

let str = "First Middle Last"

Expected Output:

FML

Would you use a for loop to grab the prefix of each word, a map function, or reduce — what is the most efficient way to do this?

Ryan
  • 107
  • 7
  • 30

5 Answers5

4

You could achieve it like this:

let result = str.components(separatedBy: " ").map { String($0.prefix(1))}.joined()
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
2

You can use components(separatedBy:) to split the String into words on each space character (or whitespace, including newlines if that is what you need),then call map on the [String] to get the first character of each word.

let startingLetters = str.components(separatedBy: " ").map{$0.prefix(1)} // ["F", "M", "L"]

In case you want to join those letters into a single String, you should use reduce instead of map (here I also used a CharacterSet containing all whitespace and newline characters instead of the hardcoded " " for demonstration purposes, choose the separator that best fits your exact needs).

let joinedStartingLetters = str.components(separatedBy: CharacterSet.whitespacesAndNewlines).reduce("", {$0+$1.prefix(1)}) // "FML"
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
  • Hi David. Hope this would be -somehow- useful: https://stackoverflow.com/questions/48850292/what-is-the-difference-between-combining-array-by-using-reduce-or-joined – Ahmad F Mar 12 '19 at 14:53
  • 1
    @AhmadF in general, I'd agree that `joined` might be a better choice for concanating Strings than `reduce`, but in this particular scenario, `joined` could only be called after performing a `map`, while `reduce` can achieve the same with a single iteration over the String. – Dávid Pásztor Mar 12 '19 at 15:08
1

I believe the most efficient way would be to iterate over the characters :

let str = "First Middle Last"

let beforeLast = str.isEmpty ? str.startIndex : str.index(before: str.endIndex)

var result = ""

var c = str.startIndex

OuterLoop: do {
    while c < str.endIndex {

        while str[c] == " " {
            if c < beforeLast { c = str.index(after: c) } 
            else { break OuterLoop }
        }

        result.append(str[c])

        while str[c] != " " {
            if c < beforeLast { c = str.index(after: c) } 
            else { break OuterLoop }
        }
    }
}

print(result)   //"FML"

This is more efficient than creating an intermediary array of single words, and then prefix the elements and cast the prefix to String, and then join the elements of that new array.

Try It Online! (only take results with higher CPU share into account, and run codes separately)

Here are some benchmarks:

@RamyMohamed's solution      : 0.000708s
@DávidPásztor's 2nd solution : 0.000669s //separatedBy: CharacterSet.whitespacesAndNewlines 
@AhmadF's solution           : 0.000561s
@DávidPásztor's 1st solution : 0.000550  //separatedBy: " "
This solution                : 0.000110s

If you'd like to take into account newlines ant tabulations, a simple solution is to define an extension on Character:

extension Character {
    static let blanks = [" ", "\n", "\t", "\r"]
    func isBlank() -> Bool {
        return Character.blanks.contains(String(self))
    }
}

and use it like so :

OuterLoop: do {
    while c < str.endIndex {
        while str[c].isBlank() {
            if c < beforeLast { c = str.index(after: c) } 
            else { break OuterLoop }
        }

        result.append(str[c])

        while !str[c].isBlank() {
            if c < beforeLast { c = str.index(after: c) } 
            else { break OuterLoop }
        }
    }
}

It clocks at 0.000174s

ielyamani
  • 17,807
  • 10
  • 55
  • 90
0

You want to do this for every word which leaves you with no choice but to iterate over the whole sequence of characters.

However you can do this elegantly in Swift by extending the String object

extension String
{
   public func getAcronym(separator: String = "") -> String
  {
    let acronym = self.components(separatedBy: CharacterSet.whitespacesAndNewlines).map({ 
      String($0.characters.first!) }).joined(separator: separator)
    return acronym
   }
}

Then use it like this:

let acronym = "First Middle Last".getAcronym()
//output: FML

let acronymDotted = "First Middle Last".getAcronym(separator: ".")
//output: F.M.L

Notice: I added CharacterSet.whitespacesAndNewlines to ignore extra spaces between words, so it cleans the string automatically.

mital solanki
  • 2,394
  • 1
  • 15
  • 24
Ramy M. Mousa
  • 5,727
  • 3
  • 34
  • 45
0

You may use Regex to get it.

 "Fried Hello Night".replacingOccurrences(of: "\\B[^\\s]*(\\s)?", with: "", options: .regularExpression, range: nil)

Another is if the first letters are uppercase, just use filter.

 String("Fried Hello Night".unicodeScalars.filter{CharacterSet.uppercaseLetters.contains($0)})
E.Coms
  • 11,065
  • 2
  • 23
  • 35