Thoughts on these generalized BidirectionalCollection trim functions

I wasn't happy using Foundation for the String.trimmingCharacters() function, so I decided to roll my own more general one. I'd be interested to hear any thoughts.

public enum Trim {
    case both, leading, trailing
}

extension BidirectionalCollection {
    public typealias ElementEvaluation = (Element) throws -> Bool
    public typealias ElementEvaluationMethod = (Element) throws -> () throws -> Bool
    public typealias ElementProperty = KeyPath<Element, Bool>
    
    public var lastIndex: Index { return index(before: endIndex) }
    
    public func trimming(_ trimEnd: Trim = .both, where predicate: ElementEvaluation) rethrows -> SubSequence {
        let leftIndex = (trimEnd == .trailing) ? startIndex : try firstIndex { !(try predicate($0)) } ?? startIndex
        let rightIndex = (trimEnd == .leading) ? lastIndex : try lastIndex { !(try predicate($0)) } ?? lastIndex
        return self[leftIndex ... rightIndex]
    }
    
    public func trimming(_ trimEnd: Trim = .both, where predicate: ElementEvaluationMethod) rethrows -> SubSequence {
        return try trimming(trimEnd) { try predicate($0)() }
    }
    
    public func trimming(_ trimEnd: Trim = .both, while property: ElementProperty) -> SubSequence {
        return trimming(trimEnd) { $0[keyPath: property] }
    }
}

extension BidirectionalCollection where Element: Equatable {
    public func trimming(_ trimEnd: Trim = .both, while trimElement: Element) -> SubSequence {
        return trimming(trimEnd) { $0 == trimElement }
    }
}

extension BidirectionalCollection where Self == SubSequence {
    public mutating func trim(_ trimEnd: Trim = .both, where predicate: ElementEvaluation) rethrows {
        self = try trimming(trimEnd, where: predicate)
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, where predicate: ElementEvaluationMethod) rethrows {
        self = try trimming(trimEnd, where: predicate)
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, while property: ElementProperty) {
        self = trimming(trimEnd, while: property)
    }
}

extension BidirectionalCollection where Self == SubSequence, Element: Equatable {
    public mutating func trim(_ trimEnd: Trim = .both, while trimElement: Element) {
        self = trimming(trimEnd, while: trimElement)
    }
}

extension StringProtocol {
    public func trimming(_ trimEnd: Trim = .both) -> SubSequence {
        return trimming(trimEnd, while: " ")
    }
}

extension StringProtocol where Self == SubSequence {
    public mutating func trim(_ trimEnd: Trim = .both) {
        self = trimming(trimEnd, while: " ")
    }
}

extension String {
    public mutating func trim(_ trimEnd: Trim = .both, where predicate: ElementEvaluation) rethrows {
        self = String(try trimming(trimEnd, where: predicate))
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, where predicate: ElementEvaluationMethod) rethrows {
        self = String(try trimming(trimEnd, where: predicate))
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, while property: ElementProperty) {
        self = String(trimming(trimEnd, while: property))
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, while trimElement: Character = " ") {
        self = String(trimming(trimEnd, while: trimElement))
    }
}

extension Array {
    public func trimming(_ trimEnd: Trim = .both, where predicate: ElementEvaluation) rethrows -> Array {
        return Array(try trimming(trimEnd, where: predicate) as ArraySlice)
    }
    
    public func trimming(_ trimEnd: Trim = .both, where predicate: ElementEvaluationMethod) rethrows -> Array {
        return Array(try trimming(trimEnd, where: predicate) as ArraySlice)
    }
    
    public func trimming(_ trimEnd: Trim = .both, while property: ElementProperty) -> Array {
        return Array(trimming(trimEnd, while: property) as ArraySlice)
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, where predicate: ElementEvaluation) rethrows {
        self = Array(try trimming(trimEnd, where: predicate))
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, where predicate: ElementEvaluationMethod) rethrows {
        self = Array(try trimming(trimEnd, where: predicate))
    }
    
    public mutating func trim(_ trimEnd: Trim = .both, while property: ElementProperty) {
        self = Array(trimming(trimEnd, while: property))
    }
}

extension Array where Element: Equatable {
    public mutating func trim(_ trimEnd: Trim = .both, while trimElement: Element) {
        self = Array(trimming(trimEnd, while: trimElement))
    }
}

I'll include some usage in a reply below.

1 Like

Example usage:

extension Character {
    public var isVowel: Bool {
        return Set(["a","e","i","o","u"]).contains(String(self).lowercased())
    }
    
    public func isSpace() -> Bool {
        return isSame(" ")
    }

    public func isSame(_ c: Character) -> Bool {
        return self == c
    }
}

extension FixedWidthInteger {
    public var isZero: Bool { return self == 0 }
}

let ss = "  trim me  ".trimming()
print("'\(ss)'")

let sp = " ? ".trimming(where: Character.isSpace)
print(sp)

var q = "XqY".trimming(while: "Y")
print(q)
q.trim(where: Character.isSame("X"))
print(q)

var s = "  trim me"
s.append("aXX")
print(s)
s.trim(while: "X")
print(s)
s.trim(while: \.isVowel)
print(s)
s.trim(where: Character.isSpace)
print(s)
s.trim { $0 == "t" }
print(s)

var a = [0,1,2,3,2,1,0]
a.trim(while: 0)
print(a)
var aa = a.trimming(while: 1)
print(aa)
aa.trim(while: 2)
print(aa)

var a1 = [0, 1, 2, 3, 4, 3, 2, 1, 0]
a1.trim(while: \.isZero)
print(a1)

let s1 = "   trim me  "
let s2 = " . trim me .  "

let ss1 = s1.trimming(while: "X")
print(ss1)

let ss1a = s1.trimming { $0.isSpace() }
print(ss1a)

let ss1b = s1.trimming(where: Character.isSpace)
print(ss1b)

let ss1bss = ss1b.trimming(while: \.isVowel)
print(ss1bss)

let ss1bss2 = ss1b.trimming(where: Character.isSame("e"))
print(ss1bss2)

let ss2 = s2.trimming { [" ", "."].contains($0) }
print(ss2)

let ss3 = s2.trimming { Set([" ", "."]).contains($0) }
print(ss3)

let ss4 = s2.trimming { " .".contains($0) }
print(ss4)

let sa1 = a1.trimming { $0 == 0 }
print(sa1)

let sa2 = a1.trimming { [1, 0].contains($0) }
print(sa2)

let sa3 = a1.trimming { Set([1, 0]).contains($0) }
print(sa3)

var a2 = [0, 1, 2, 3, 4, 3, 2, 1, 0]
a2.trim { $0 == 0 }
print(a2)