3

I am trying to split a string into an array of letters, but keep some of the letters together. (I'm trying to break them into sound groups for pronunciation, for example). So, for example, all the "sh' combinations would be one value in the array instead of two.

It is easy to find an 's' in an array that I know has an "sh" in it, using firstIndex. But how do I get more than just the first, or last, index of the array?

The Swift documentation includes this example:

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
if let i = students.firstIndex(where: { $0.hasPrefix("A") }) {
    print("\(students[i]) starts with 'A'!")
}
// Prints "Abena starts with 'A'!"

How do I get both Abena and Akosua (and others, if there were more?)

Here is my code that accomplishes some of what I want (please excuse the rather lame error catching)

let message = "she sells seashells"
var letterArray = message.map { String($0)}
var error = false

while error == false {
    if message.contains("sh") {
        guard let locate1 = letterArray.firstIndex(of: "s") else{
            error = true
            break }
        let locate2 = locate1 + 1
        //since it keeps finding an s it doesn't know how to move on to rest of string and we get an infinite loop
        if letterArray[locate2] == "h"{
            letterArray.insert("sh", at: locate1)
            letterArray.remove (at: locate1 + 1)
            letterArray.remove (at: locate2)}}
    else { error = true }}
print (message, letterArray)
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
Tim Steen
  • 33
  • 2
  • 2
    Looks like a duplicate of https://stackoverflow.com/questions/45933149/swift-how-to-get-indexes-of-filtered-items-of-array , you might wanna take a look there. – lennartk May 20 '20 at 19:58
  • What is the expected outcome of `print (message, letterArray)` ? – koen May 21 '20 at 03:13

4 Answers4

3

Instead of first use filter you will get both Abena and Akosua (and others, if there were more?)

   extension Array where Element: Equatable {
    func allIndexes(of element: Element) -> [Int] {
        return self.enumerated().filter({ element == $0.element }).map({ $0.offset })
    }
}

You can then call

letterArray.allIndexes(of: "s") // [0, 4, 8, 10, 13, 18]
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
1

You can filter the collection indices:

let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let indices = students.indices.filter({students[$0].hasPrefix("A")})
print(indices)  // "[1, 4]\n"

You can also create your own indices method that takes a predicate:

extension Collection {
    func indices(where predicate: @escaping (Element) throws -> Bool) rethrows -> [Index] {
        try indices.filter { try predicate(self[$0]) }
    }
}

Usage:

let indices = students.indices { $0.hasPrefix("A") }
print(indices)  // "[1, 4]\n"

or indices(of:) where the collection elements are Equatable:

extension Collection where Element: Equatable {
    func indices(of element: Element) -> [Index] {
        indices.filter { self[$0] == element }
    }
}

usage:

let message = "she sells seashells"
let indices = message.indices(of: "s")
print(indices)

Note: If you need to find all ranges of a substring in a string you can check this post.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

Have fun!

["Kofi", "Abena", "Peter", "Kweku", "Akosua"].forEach {
    if $0.hasPrefix("A") {
        print("\($0) starts with 'A'!")
    }
}
Dmytro
  • 131
  • 4
  • `let locate1 = letterArray.firstIndex(of: "s") // instead of first` ... he want to get all indexes where s is placed ... – Jawad Ali May 20 '20 at 20:07
  • his calculations based on indexes that he want to get ... again ... he want to get all indexes where S is ... – Jawad Ali May 20 '20 at 20:13
0

If you really want to use the firstIndex method, here's a recursive(!) implementation just for fun :D

extension Collection where Element: Equatable {

    /// Returns the indices of an element from the specified index to the end of the collection.
    func indices(of element: Element, fromIndex: Index? = nil) -> [Index] {
        let subsequence = suffix(from: fromIndex ?? startIndex)
        if let elementIndex = subsequence.firstIndex(of: element) {
            return [elementIndex] + indices(of: element, fromIndex: index(elementIndex, offsetBy: 1))
        }
        return []
    }

}

Recursions

Given n instances of element in the collection, the function will be called n+1 times (including the first call).

Complexity

Looking at complexity, suffix(from:) is O(1), and firstIndex(of:) is O(n). Assuming that firstIndex terminates once it encounters the first match, any recursions simply pick up where we left off. Therefore, indices(of:fromIndex:) is O(n), just as good as using filter. Sadly, this function is not tail recursive... although we can change that by keeping a running total.

Performance

[Maybe I'll do this another time.]

Disclaimer

Recursion is fun and all, but you should probably use Leo Dabus' solution.

Daniel
  • 3,188
  • 14
  • 34
  • You can also use subscript with a PartialRangeFrom `func indices(of element: Element, from index: Index? = nil) -> [Index] { guard let index = self[(index ?? startIndex)...].firstIndex(of: element) else { return [] } return [index] + indices(of: element, from: self.index(after: index)) }` :) – Leo Dabus May 21 '20 at 01:28
  • Btw when creating a collection of a single item **You can use a CollectionOfOne instance when you need to efficiently represent a single value as a collection. For example, you can add a single element to an array by using a CollectionOfOne instance with the concatenation operator (+):** `func indices(of element: Element, from index: Index? = nil) -> [Index] { guard let index = self[(index ?? startIndex)...].firstIndex(of: element) else { return [] } return CollectionOfOne(index) + indices(of: element, from: self.index(after: index)) }` – Leo Dabus May 21 '20 at 01:40
  • I knew there was a recursive solution! I am glad that there was another :) but thanks – Tim Steen May 21 '20 at 22:05
  • @TimSteen be aware that not all collections starts from zero. You should avoid using the enumerated offset (accepted answer) approach if you need the index of the element (offset is not always the same as index) – Leo Dabus May 21 '20 at 22:55