Skip to content

Instantly share code, notes, and snippets.

@lancep
Last active April 3, 2018 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lancep/501024035e9eff12af6cd1f7632daf5a to your computer and use it in GitHub Desktop.
Save lancep/501024035e9eff12af6cd1f7632daf5a to your computer and use it in GitHub Desktop.
Collection additions

Collection additions

  • Proposal:
  • Authors: Lance Parker
  • Review Manager: TBD
  • Status: Awaiting Review

Introduction

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.

Motivation

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.

Proposed Solution

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
}

Performance

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.

Alternatives considered

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.

Impact on String

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.

@milseman
Copy link

milseman commented Apr 3, 2018

I think it makes sense to propose the following progression:

  1. 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 fooAlls from that.

  1. BidirectionalCollection.lastIndex(of:), BidirectionalCollection.lastIndex(where:), BidirectionalCollection.lastRange(of:)

Similarly, we can derive removeLast, etc.

  1. Consider splitting commonPrefix/Suffix out from this proposal. They're separable and probably deserve more consideration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment