Closed mdznr closed 3 years ago
Thanks for suggesting this @mdznr! This operation is commonly called partition
in other languages. The Swift standard library happens to already have an in-place partition(by:)
(with a stable variant residing in this package) and this could be considered the not-in-place version of that. What do you think about calling this method partitioned(by:)
?
Your implementation of the Collection
overload is nicely able to avoid any array resizing, but in doing so it takes a couple of (potentially) O(n) hits. We should probably run some benchmarks using Swift Collections Benchmark to make sure this added complexity is worthwhile. There are some additional optimizations we could consider:
ArraySlice
s that both point to the same buffer.ReversedCollection<ArraySlice>
.There are some trade-offs here regarding performance, implementation simplicity, and having a clean API. A good next step would be to get our hands on some benchmark results that might tell us which directions are worth pursuing.
Thanks for suggesting this @mdznr! This operation is commonly called
partition
in other languages. The Swift standard library happens to already have an in-placepartition(by:)
(with a stable variant residing in this package) and this could be considered the not-in-place version of that. What do you think about calling this methodpartitioned(by:)
?
I like that idea a lot. I don’t know how I missed the existing stable partition function in this package. 🤦♂️
Your implementation of the
Collection
overload is nicely able to avoid any array resizing, but in doing so it takes a couple of (potentially) O(n) hits. We should probably run some benchmarks using Swift Collections Benchmark to make sure this added complexity is worthwhile. There are some additional optimizations we could consider:
- Don't allocate two new arrays for the return values but instead return
ArraySlice
s that both point to the same buffer.- Taking that to extremes, don't reverse the elements on the right side at all, and instead return (a wrapper around) a
ReversedCollection<ArraySlice>
.
I like that idea. That allows the caller to decide whether the conversation to Array
is needed or not.
- Another direction entirely could be to not allocate a new buffer with the same size of the input collection at all, but instead store which elements satisfy the predicate in a bitset. Once that is done we'll know exactly how much capacity to reserve for the return values.
I see _UnsafeBitset
in swift-collections, but it needs to know the size ahead of time. Is there another API I should be looking at for this?
There are some trade-offs here regarding performance, implementation simplicity, and having a clean API. A good next step would be to get our hands on some benchmark results that might tell us which directions are worth pursuing.
Sounds like a good idea. I'll use https://github.com/apple/swift-collections-benchmark to guide this.
GitHub won’t let me re-open this PR because I’ve force-pushed changes onto the branch. Following up with https://github.com/apple/swift-algorithms/pull/152
Description
Adds a
bifurcate(_:)
algorithm. This is very similar tofilter(_:)
, but instead of just getting an array of the elements that did match the given predicate, also get a second array for the elements that did not match the given predicate.This is more performant than calling
filter(_:)
twice on the same input with mutually-exclusive predicates since:Detailed Design
Naming
It was hard to find a word to describe this behavior without using terms like “split” or “separate”, which might have other interpretations (like only being able to get the prefix and suffix), so I chose “bifurcate”, but definitely open to hearing different names.
For consistency with
filter(_:)
, I avoided calling this function something likebifurcating(_:)
,bifurcated(_:)
, or giving the parameter a name at the call-site, likebifurcate(where:)
.Documentation Plan
Test Plan
Source Impact
This is purely additive
Checklist