Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Don't merge] CI sanity check -- please ignore #197

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.5
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Collections open source project
Expand All @@ -17,7 +17,7 @@ import PackageDescription
// from the package manager command line:
//
// swift build -Xswiftc -DCOLLECTIONS_INTERNAL_CHECKS
var settings: [SwiftSetting]? = [
var settings: [SwiftSetting] = [

// Enables internal consistency checks at the end of initializers and
// mutating operations. This can have very significant overhead, so enabling
Expand All @@ -44,10 +44,6 @@ var settings: [SwiftSetting]? = [

]

// Prevent SPM 5.3 from throwing an error on empty settings arrays.
// (This has been fixed in 5.4.)
if settings?.isEmpty == true { settings = nil }

let package = Package(
name: "swift-collections",
products: [
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,30 @@ Read more about the package, and the intent behind it, in the [announcement on s

The package currently provides the following implementations:

- [`BitSet`][BitSet] and [`BitArray`][BitArray], dynamic bit vectors.

- [`Deque<Element>`][Deque], a double-ended queue backed by a ring buffer. Deques are range-replaceable, mutable, random-access collections.

- [`Heap`][Heap], a min-max heap backed by an array, suitable for use as a priority queue.

- [`OrderedSet<Element>`][OrderedSet], a variant of the standard `Set` where the order of items is well-defined and items can be arbitrarily reordered. Uses a `ContiguousArray` as its backing store, augmented by a separate hash table of bit packed offsets into it.

- [`OrderedDictionary<Key, Value>`][OrderedDictionary], an ordered variant of the standard `Dictionary`, providing similar benefits.

- [`PersistentSet`][PersistentSet] and [`PersistentDictionary`][PersistentDictionary], persistent hashed collections implementing Compressed Hash-Array Mapped Prefix Trees (CHAMP). These work similar to the standard `Set` and `Dictionary`, but they excel at use cases that mutate shared copies, offering dramatic memory savings and radical time improvements.

[BitSet]: Documentation/BitSet.md
[BitArray]: Documentation/BitArray.md
[Deque]: Documentation/Deque.md
[Heap]: Documentation/Heap.md
[OrderedSet]: Documentation/OrderedSet.md
[OrderedDictionary]: Documentation/OrderedDictionary.md
[PersistentSet]: Documentation/PersistentSet.md
[PersistentDictionary]: Documentation/PersistentDictionary.md

The following data structures are currently being worked on but they aren't ready for inclusion in a tagged release:

- [`Heap`][Heap] and [`PriorityQueue`](https://github.com/apple/swift-collections/pull/51), min-max heaps backed by an array.
- [`SortedSet` and `SortedDictionary`](https://github.com/apple/swift-collections/pull/65), sorted collections backed by in-memory persistent b-trees.
- [`HashSet` and `HashMap`](https://github.com/apple/swift-collections/pull/31), persistent hashed collections implemented as Compressed Hash-Array Mapped Prefix-Trees (CHAMP).
- [`BitArray` and `BitSet`](https://github.com/apple/swift-collections/pull/83), dynamic bit vectors.
- [`SparseSet`](https://github.com/apple/swift-collections/pull/80), a constant time set construct, trading off memory for speed.

[Heap]: Documentation/Heap.md
Expand All @@ -47,7 +55,7 @@ The Swift Collections package is source stable. The version numbers follow [Sema

[semver]: https://semver.org

The public API of version 1.0 of the `swift-collections` package consists of non-underscored declarations that are marked `public` in the `Collections`, `DequeModule` and `OrderedCollections` modules.
The public API of version 1.1 of the `swift-collections` package consists of non-underscored declarations that are marked `public` in the `Collections`, `BitCollections`, `DequeModule`, `HeapModule`, `OrderedCollections` and `PersistentCollections` modules.

Interfaces that aren't part of the public API may continue to change in any release, including patch releases.
If you have a use case that requires using underscored APIs, please [submit a Feature Request][enhancement] describing it! We'd like the public interface to be as useful as possible -- although preferably without compromising safety or limiting future evolution.
Expand All @@ -65,20 +73,30 @@ Future minor versions of the package may update these rules as needed.

We'd like this package to quickly embrace Swift language and toolchain improvements that are relevant to its mandate. Accordingly, from time to time, we expect that new versions of this package will require clients to upgrade to a more recent Swift toolchain release. (This allows the package to make use of new language/stdlib features, build on compiler bug fixes, and adopt new package manager functionality as soon as they are available.) Requiring a new Swift release will only need a minor version bump.

The following table maps existing package releases to their minimum required Swift toolchain release:

| Package version | Swift version |
|---|---|---|
| swift-collections 1.0.x | >= Swift 5.3 |
| swift-collections 1.1.x | >= Swift 5.5 |

(Note: the package has no minimum deployment target, so while it does require clients to use a recent Swift toolchain to build it, the code itself is able to run on any OS release that supports running Swift code.)


## Using **Swift Collections** in your project

To use this package in a SwiftPM project, you need to set it up as a package dependency:

```swift
// swift-tools-version:5.6
// swift-tools-version:5.7
import PackageDescription

let package = Package(
name: "MyPackage",
dependencies: [
.package(
url: "https://github.com/apple/swift-collections.git",
.upToNextMajor(from: "1.0.3") // or `.upToNextMinor
.upToNextMinor(from: "1.1.0") // or `.upToNextMajor
)
],
targets: [
Expand Down
2 changes: 2 additions & 0 deletions Sources/BitCollections/BitArray/BitArray+Copy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//
//===----------------------------------------------------------------------===//

import _CollectionsUtilities

extension BitArray {
internal mutating func _copy(
from range: Range<Int>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension BitArray: CustomStringConvertible {
let b1: UInt8 = 49 // ASCII 1
var i = 0
for v in self {
target._initialize(at: i, to: v ? b1 : b0)
target.initializeElement(at: i, to: v ? b1 : b0)
i &+= 1
}
return i
Expand Down
6 changes: 3 additions & 3 deletions Sources/BitCollections/BitSet/BitSet+Initializers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,16 @@ extension BitSet {
_storage = Array(unsafeUninitializedCapacity: capacity) { buffer, count in
let sharedCount = Swift.min(w1.count, w2.count)
for w in 0 ..< sharedCount {
buffer._initialize(at: w, to: function(w1[w], w2[w]))
buffer.initializeElement(at: w, to: function(w1[w], w2[w]))
}
if includingTail {
if w1.count < w2.count {
for w in w1.count ..< w2.count {
buffer._initialize(at: w, to: function(_Word.empty, w2[w]))
buffer.initializeElement(at: w, to: function(_Word.empty, w2[w]))
}
} else {
for w in w2.count ..< w1.count {
buffer._initialize(at: w, to: function(w1[w], _Word.empty))
buffer.initializeElement(at: w, to: function(w1[w], _Word.empty))
}
}
}
Expand Down
16 changes: 7 additions & 9 deletions Sources/BitCollections/BitSet/BitSet._UnsafeHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//
//===----------------------------------------------------------------------===//

import _CollectionsUtilities

extension BitSet {
/// An unsafe-unowned bitset view over `UInt` storage, providing bit set
/// primitives.
Expand Down Expand Up @@ -273,7 +275,7 @@ extension BitSet._UnsafeHandle: BidirectionalCollection {
internal var startIndex: Index {
let word = _words.firstIndex { !$0.isEmpty }
guard let word = word else { return endIndex }
return Index(word: word, bit: _words[word].firstMember)
return Index(word: word, bit: _words[word].firstMember!)
}

@inlinable
Expand All @@ -299,7 +301,7 @@ extension BitSet._UnsafeHandle: BidirectionalCollection {
}
w = _words[word]
}
return Index(word: word, bit: w.firstMember)
return Index(word: word, bit: w.firstMember!)
}

@usableFromInline
Expand All @@ -318,7 +320,7 @@ extension BitSet._UnsafeHandle: BidirectionalCollection {
precondition(word >= 0, "Can't advance below startIndex")
w = _words[word]
}
return Index(word: word, bit: w.lastMember)
return Index(word: word, bit: w.lastMember!)
}

@usableFromInline
Expand Down Expand Up @@ -501,9 +503,7 @@ extension BitSet._UnsafeHandle {
_mutableWords[l.word].formIntersection(_Word(upTo: l.bit).complement())
if u.word < wordCount {
_mutableWords[u.word].formIntersection(_Word(upTo: u.bit))
_mutableWords[(u.word + 1)...]
._rebased()
.assign(repeating: .empty)
_mutableWords[(u.word + 1)...].update(repeating: .empty)
}
}

Expand Down Expand Up @@ -542,9 +542,7 @@ extension BitSet._UnsafeHandle {
}

_mutableWords[l.word].subtract(_Word(upTo: l.bit).complement())
_mutableWords[(l.word + 1) ..< u.word]
._rebased()
.assign(repeating: .empty)
_mutableWords[(l.word + 1) ..< u.word].update(repeating: .empty)
if u.word < wordCount {
_mutableWords[u.word].subtract(_Word(upTo: u.bit))
}
Expand Down
81 changes: 8 additions & 73 deletions Sources/BitCollections/Shared/UInt+Tricks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,9 @@
//
//===----------------------------------------------------------------------===//

extension UInt {
@inlinable @inline(__always)
internal var _nonzeroBitCount: UInt {
Self(truncatingIfNeeded: nonzeroBitCount)
}

internal var _reversed: UInt {
// https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
var shift: UInt = UInt(UInt.bitWidth)
var mask: UInt = ~0;
var result = self
while true {
shift &>>= 1
guard shift > 0 else { break }
mask ^= mask &<< shift
result = ((result &>> shift) & mask) | ((result &<< shift) & ~mask)
}
return result
}

@inlinable @inline(__always)
internal var _lastSetBit: UInt {
UInt(truncatingIfNeeded: self.trailingZeroBitCount)
}

@inlinable @inline(__always)
internal var _firstSetBit: UInt {
UInt(truncatingIfNeeded: UInt.bitWidth &- 1 &- self.leadingZeroBitCount)
}
import _CollectionsUtilities

extension UInt {
/// Returns the position of the `n`th set bit in `self`.
///
/// - Parameter n: The to retrieve. This value is
Expand All @@ -48,52 +21,14 @@ extension UInt {
/// - Returns: If this integer contains enough set bits to satisfy the
/// request, then this function returns the position of the bit found.
/// Otherwise it returns nil.
internal func _nthSetBit(_ n: inout UInt) -> UInt? {
// FIXME: Use bit deposit instruction when available (PDEP on Intel).
assert(UInt.bitWidth == 64 || UInt.bitWidth == 32, "Unsupported UInt bitWidth")

var shift: UInt = 0

let c = self._nonzeroBitCount
internal func _rank(ofBit n: inout UInt) -> UInt? {
let c = self.nonzeroBitCount
guard n < c else {
n &-= c
n &-= UInt(bitPattern: c)
return nil
}

if UInt.bitWidth == 64 {
let c32 = (self & 0xFFFFFFFF)._nonzeroBitCount
if n >= c32 {
shift &+= 32
n &-= c32
}
}
let c16 = ((self &>> shift) & 0xFFFF)._nonzeroBitCount
if n >= c16 {
shift &+= 16
n &-= c16
}
let c8 = ((self &>> shift) & 0xFF)._nonzeroBitCount
if n >= c8 {
shift &+= 8
n &-= c8
}
let c4 = ((self &>> shift) & 0xF)._nonzeroBitCount
if n >= c4 {
shift &+= 4
n &-= c4
}
let c2 = ((self &>> shift) & 0x3)._nonzeroBitCount
if n >= c2 {
shift &+= 2
n &-= c2
}
let c1 = (self &>> shift) & 0x1
if n >= c1 {
shift &+= 1
n &-= c1
}
precondition(n == 0 && (self &>> shift) & 0x1 == 1)
return shift
let m = Int(bitPattern: n)
n = 0
return _bit(ranked: m)!
}
}

This file was deleted.

10 changes: 6 additions & 4 deletions Sources/BitCollections/Shared/_Word.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
//
//===----------------------------------------------------------------------===//

import _CollectionsUtilities

@usableFromInline
@frozen
internal struct _Word {
Expand Down Expand Up @@ -52,12 +54,12 @@ extension _Word {
/// then this function returns the member found. Otherwise it returns nil.
@inline(never)
internal func nthElement(_ n: inout UInt) -> UInt? {
value._nthSetBit(&n)
value._rank(ofBit: &n)
}

@inline(never)
internal func nthElementFromEnd(_ n: inout UInt) -> UInt? {
guard let i = value._reversed._nthSetBit(&n) else { return nil }
guard let i = value._reversed._rank(ofBit: &n) else { return nil }
return UInt(UInt.bitWidth) &- 1 &- i
}
}
Expand Down Expand Up @@ -102,13 +104,13 @@ extension _Word {

@inlinable
@inline(__always)
internal var firstMember: UInt {
internal var firstMember: UInt? {
value._lastSetBit
}

@inlinable
@inline(__always)
internal var lastMember: UInt {
internal var lastMember: UInt? {
value._firstSetBit
}

Expand Down
Loading