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\$

2 Answers 2

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\$
5
  • 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
    Commented Aug 23, 2016 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
    Commented Aug 23, 2016 at 19:58
  • \$\begingroup\$ Will update my answer soon \$\endgroup\$
    – Silmaril
    Commented Aug 23, 2016 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
    Commented Aug 23, 2016 at 22:01
  • \$\begingroup\$ @pacmaninbw updated \$\endgroup\$
    – Silmaril
    Commented Aug 24, 2016 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 and acknowledge you have read our privacy policy.

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