3
\$\begingroup\$

Let's say I have a struct…

struct Person {
   let isMale: Bool
   let name: String
}

and an array of Person structs. I want to trim all the men (isMale == true) from the start and end of the array (similarly to how you'd trim whitespace from the start & end of a string)…

func trimMen(people: [Person]) -> [Person]

    var trimmedPeople: [Person] = people

    while trimmedPeople.first?.isMale {
        trimmedPeople.removeFirst()
    }
    while trimmedPeople.last?.isMale {
        trimmedPeople.removeLast()
    }
    return trimmedPeople
}

Is there a more efficient way to do it?

\$\endgroup\$
4
\$\begingroup\$

Following implementation provides generic solution, which mimics other foundation methods like filter, map etc. Though extends Array instead of SequenceType.

Main benefit from this solution comparing to the original provided by Ashley Mills is that it does not mutate an array.

extension Array {

    func trim(@noescape predicate: (Element) throws -> Bool) rethrows -> [Element] {
        guard let
            start = try (indexOf { try !predicate($0) }),
            end = try (reverse().indexOf { try !predicate($0) }) else {
                return []
        }
        return Array(self[start..<end.base])
    }

}

Usage Example:

persons.trim { $0.isMale }
\$\endgroup\$
  • 1
    \$\begingroup\$ If the guard fails, then you want to return [], not self (the guard fails if all elements meet the predicate, thus all should be trimmed). You could also use the half-open range operator instead of the closed range operator (self[start..<end.base]). Also <Element> can be inferred in the return statement. \$\endgroup\$ – Hamish Aug 23 '16 at 19:14
  • \$\begingroup\$ Thanks for noticing guard issue. I knew about closed range operator, but was in a hurry and didn't notice that I can use it. \$\endgroup\$ – Silmaril Aug 23 '16 at 19:58
  • \$\begingroup\$ Will update my answer soon \$\endgroup\$ – Silmaril Aug 23 '16 at 19:59
  • 2
    \$\begingroup\$ Can you also provide some review of the original code. Alternate solutions without explanation are frowned on, and may not get high scores. See the How to give a good answer on this web page: codereview.stackexchange.com/help/how-to-answer. For example explain why your method is more efficient. \$\endgroup\$ – pacmaninbw Aug 23 '16 at 22:01
  • \$\begingroup\$ @pacmaninbw updated \$\endgroup\$ – Silmaril Aug 24 '16 at 0:58
2
\$\begingroup\$

Swift 3.1 has solved this problem with drop(while:) and prefix(while:)

struct Person {
    let isMale: Bool
    let name: String
}

let people = [Person(isMale: true, name: "Bill"),
              Person(isMale: false, name: "Jane"),
              Person(isMale: true, name: "José"),
              Person(isMale: false, name: "Sarah"),
              Person(isMale: true, name: "Steve"),
              Person(isMale: true, name: "Tony")]

func trimMen(people: [Person]) -> [Person] {
    let removedPrefixMen = people.drop { $0.isMale }
    let removedSuffixMen = removedPrefixMen.reversed().drop { $0.isMale }
    return removedSuffixMen.reversed()
}

// [{isMale false, name "Jane"}, {isMale true, name "José"}, {isMale false, name "Sarah"}]

Also in Swift 3.1, concrete constrained extensions, means I can do this…

extension Array where Element == Person {

    func trimMen() -> [Element] {
        let removedPrefixMen = drop{ $0.isMale }
        let removedSuffixMen = removedPrefixMen.reversed().drop{ $0.isMale }
        return removedSuffixMen.reversed()
    }
}

people.trimMen()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.