- Proposal:
- Authors: Lance Parker
- Review Manager: TBD
- Status: Awaiting Review
Finding, replacing, removing, counting, and splitting one ordered collection on the contents of another is extremely useful (and would fill some holes in today's String API as well). We propose adding these and some other related methods to BidirectionalCollection
and RangeReplaceableCollection
as appropriate.
Two of these additions are currently being pitched here: Count(of:) and contains(other:) for BidirectionalCollection - Pitches - Swift Forums I included these two methods here as well because I think it makes sense to consider them as a whole rather than individually as they should all be named more or less uniformly.
Mutating a collection based on its contents is a common thing to do. For example, you might want to split a collection up based on a delimiter. We have this in Swift today:
func split(separator: Element, maxSplits: Int = default, omittingEmptySubsequences: Bool = default) -> [AnyBidirectionalCollection<Element>]
This is very useful, but doesn't allow you you split the collection up by another ordered collection that contains the same element. For example, it would be useful to be able to do this*:
let moons = "Metis, Adrastea, Amalthea, Thebe".split(separatedBy: ", ")
*you can do this on String
today but it requires bridging to NSString
. It also doesn't work on Array
.
It is also useful to be able to find ranges, replace, remove, count, check for containment, and split the collection based on ordered collections of the same type.
extension RangeReplaceableCollection where Self: BidirectionalCollection, Element: Equatable {
//remove
mutating func removeFirst<C: BidirectionalCollection>(occurrenceOf pattern: C) where C.Element == Element
mutating func removeLast<C: BidirectionalCollection>(occurrenceOf pattern: C) where C.Element == Element
mutating func removeAll<C: BidirectionalCollection>(occurencesOf pattern: C) where C.Element == Element
//replace
mutating func replaceFirst<C: BidirectionalCollection>(occurrenceOf pattern: C, with replacement: C) where C.Element == Element
mutating func replaceLast<C: BidirectionalCollection>(occurrenceOf pattern: C, with replacement: C) where C.Element == Element
mutating func replaceAll<C: BidirectionalCollection>(occurrencesOf pattern: C, with replacement: C) where C.Element == Element
}
extension BidirectionalCollection where Element: Equatable {
func contains<C: BidirectionalCollection>(occurrenceOf pattern: C) -> Bool where C.Element == Element
func count<C: BidirectionalCollection>(of pattern: C, overlapping: Bool = false) -> Int where C.Element == Element
//Ranges
func lastRange<C: BidirectionalCollection>(of pattern: C) -> Range<Index>? where C.Element == Element
func firstRange<C: BidirectionalCollection>(of pattern: C) -> Range<Index>? where C.Element == Element
func allRanges<C: BidirectionalCollection>(of pattern: C, overlapping: Bool = false) -> [Range<Index>] where C.Element == Element
func split<C: BidirectionalCollection>(separatedBy pattern: C, maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true) -> [SubSequence] where C.Element == Element
}
Many of these methods would benefit from specializations from some of the more concrete Swift types, namely Array
and String
. Therefore, we should make the ones that make sense, customization points on the respective collection. That is, they would be requirements on the protocol with default implementations, rather than being extension methods only.
We could put all the BidirectionalCollection
methods on Collection
, none of the new methods really make sense on unordered collections like Dictionary
and Set
. BidirectionalCollection
also makes all of the last
methods much more performant as we can search from the back. There are also algorithmic benefits for specializations of these methods where having the ability to traverse the supplied collection in both directions would yield better performance.
Under this proposal, the following String
methods would (eventually?) be deprecated as their functionality is replaced by the above methods*:
extension String {
public func contains<T : StringProtocol>(_ other: T) -> Bool
public func components<T : StringProtocol>(separatedBy separator: T) -> [String]
public func components(separatedBy separator: CharacterSet) -> [String]
public func replacingOccurrences<Target : StringProtocol, Replacement : StringProtocol>(
of target: Target, with replacement: Replacement, options: String.CompareOptions = [], range searchRange: Range<Index>? = default
) -> String
}
*Note that the only thing that will be deprecated is the extension on String that exposes these NSString
methods on String
, users who still need these could cast to NSString
and get them back.
I think it makes sense to propose the following progression:
Collection.range(of:)
From this we can derive remove, replace, count, contains, split. It should be a customization point, as Bidis, RACs, and maybe some notion of contiguously stored can use alternative techniques than naive O(n^2). Also, we can derive the different
fooAll
s from that.BidirectionalCollection.lastIndex(of:)
,BidirectionalCollection.lastIndex(where:)
,BidirectionalCollection.lastRange(of:)
Similarly, we can derive removeLast, etc.