diff --git a/stdlib/public/core/RangeReplaceableCollection.swift b/stdlib/public/core/RangeReplaceableCollection.swift index 5b42713aa8351..a5cb10020a4cd 100644 --- a/stdlib/public/core/RangeReplaceableCollection.swift +++ b/stdlib/public/core/RangeReplaceableCollection.swift @@ -353,6 +353,15 @@ public protocol RangeReplaceableCollection : Collection /// - Complexity: O(*n*), where *n* is the length of the collection. mutating func removeAll(keepingCapacity keepCapacity: Bool /*= false*/) + /// Removes from the collection all elements that satisfy the given predicate. + /// + /// - Parameter predicate: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + mutating func removeAll(where predicate: (Element) throws -> Bool) rethrows + // FIXME(ABI): Associated type inference requires this. subscript(bounds: Index) -> Element { get } @@ -1075,3 +1084,47 @@ extension RangeReplaceableCollection { return try Self(self.lazy.filter(isIncluded)) } } + +extension RangeReplaceableCollection where Self: MutableCollection { + /// Removes from the collection all elements that satisfy the given predicate. + /// + /// - Parameter predicate: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @_inlineable + public mutating func removeAll( + where predicate: (Element) throws -> Bool + ) rethrows { + if var i = try index(where: predicate) { + var j = index(after: i) + while j != endIndex { + if try !predicate(self[j]) { + swapAt(i, j) + formIndex(after: &i) + } + formIndex(after: &j) + } + removeSubrange(i...) + } + } +} + +extension RangeReplaceableCollection { + /// Removes from the collection all elements that satisfy the given predicate. + /// + /// - Parameter predicate: A closure that takes an element of the + /// sequence as its argument and returns a Boolean value indicating + /// whether the element should be removed from the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @_inlineable + public mutating func removeAll( + where predicate: (Element) throws -> Bool + ) rethrows { + // FIXME: Switch to using RRC.filter once stdlib is compiled for 4.0 + // self = try filter { try !predicate($0) } + self = try Self(self.lazy.filter { try !predicate($0) }) + } +} diff --git a/test/stdlib/RemoveElements.swift b/test/stdlib/RemoveElements.swift new file mode 100644 index 0000000000000..667d446441f3f --- /dev/null +++ b/test/stdlib/RemoveElements.swift @@ -0,0 +1,107 @@ +//===--- RemoveElements.swift - tests for lazy filtering-------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +import StdlibUnittest + + +let RemoveElements = TestSuite("RemoveElements") + +extension RangeReplaceableCollection where Element: Equatable { + mutating func remove(equalTo: Element) { + removeAll(where: { $0 == equalTo }) + } +} + +RemoveElements.test("removing-array-with-predicate") { + var a = Array(0..<10) + a.removeAll(where: { $0 % 2 == 0 }) + expectEqualSequence([1,3,5,7,9], a) +} + +RemoveElements.test("removing-array-nothing") { + var a = Array(0..<5) + a.removeAll(where: { _ in false }) + expectEqualSequence(0..<5, a) +} + +RemoveElements.test("removing-array-everything") { + var a = Array(0..<5) + a.removeAll(where: { _ in true }) + expectEqualSequence([], a) +} + +RemoveElements.test("removing-array-from-empty") { + var a: [Int] = [] + a.removeAll(where: { _ in true }) + expectEqualSequence([], a) +} + +RemoveElements.test("removing-array-with-equatable") { + var a = Array(0..<5) + a.remove(equalTo: 6) + expectEqualSequence([0,1,2,3,4], a) + a.remove(equalTo: 3) + expectEqualSequence([0,1,2,4], a) + a.remove(equalTo: 0) + expectEqualSequence([1,2,4], a) + a.remove(equalTo: 4) + expectEqualSequence([1,2], a) + a.remove(equalTo: 1) + expectEqualSequence([2], a) + a.remove(equalTo: 2) + expectEqualSequence([], a) +} + +RemoveElements.test("removing-string-with-predicate") { + var s = "0123456789" + s.removeAll(where: { Int(String($0))! % 2 == 0 }) + expectEqualSequence("13579", s) +} + +RemoveElements.test("removing-string-nothing") { + var s = "01234" + s.removeAll(where: { _ in false }) + expectEqualSequence("01234", s) +} + +RemoveElements.test("removing-string-everything") { + var s = "01234" + s.removeAll(where: { _ in true }) + expectEqualSequence("", s) +} + +RemoveElements.test("removing-string-from-empty") { + var s = "" + s.removeAll(where: { _ in true }) + expectEqualSequence("", s) +} + +RemoveElements.test("removing-string-with-equatable") { + var s = "01234" + s.remove(equalTo: "6") + expectEqualSequence("01234", s) + s.remove(equalTo: "3") + expectEqualSequence("0124", s) + s.remove(equalTo: "0") + expectEqualSequence("124", s) + s.remove(equalTo: "4") + expectEqualSequence("12", s) + s.remove(equalTo: "1") + expectEqualSequence("2", s) + s.remove(equalTo: "2") + expectEqualSequence("", s) +} + +runAllTests() +