diff --git a/CMakeLists.txt b/CMakeLists.txt index bc4e81708..73ab35836 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,10 +7,6 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] -if(POLICY CMP0091) - cmake_policy(SET CMP0091 NEW) -endif() - cmake_minimum_required(VERSION 3.16) project(SwiftCollections LANGUAGES C Swift) @@ -28,6 +24,8 @@ if(NOT SWIFT_SYSTEM_NAME) endif() endif() +include(PlatformInfo) + set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift) set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index d737f5ede..6e1d3bdee 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -42,10 +42,10 @@ if(COLLECTIONS_SINGLE_MODULE) endif() install(FILES $/${COLLECTIONS_MODULE_NAME}.swiftdoc DESTINATION lib/${swift}/${swift_os}/${COLLECTIONS_MODULE_NAME}.swiftmodule - RENAME ${Swift_MODULE_TRIPLE}.swiftdoc) + RENAME ${SwiftCollections_MODULE_TRIPLE}.swiftdoc) install(FILES $/${COLLECTIONS_MODULE_NAME}.swiftmodule DESTINATION lib/${swift}/${swift_os}/${COLLECTIONS_MODULE_NAME}.swiftmodule - RENAME ${Swift_MODULE_TRIPLE}.swiftmodule) + RENAME ${SwiftCollections_MODULE_TRIPLE}.swiftmodule) else() _install_target(${COLLECTIONS_MODULE_NAME}) endif() diff --git a/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift b/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift index 62780b8da..6feb1dfe8 100644 --- a/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift +++ b/Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift @@ -92,10 +92,15 @@ extension _HashNode.Storage { bytes += itemAlignment - childAlignment } + let mincap = (bytes &+ childStride &- 1) / childStride let object = _HashNode.Storage.create( - minimumCapacity: (bytes &+ childStride &- 1) / childStride + minimumCapacity: mincap ) { buffer in +#if os(OpenBSD) + _HashNodeHeader(byteCapacity: mincap * childStride) +#else _HashNodeHeader(byteCapacity: buffer.capacity * childStride) +#endif } object.withUnsafeMutablePointers { header, elements in diff --git a/Sources/HashTreeCollections/HashNode/_HashTreeStatistics.swift b/Sources/HashTreeCollections/HashNode/_HashTreeStatistics.swift index 85468af5f..0dd1efedc 100644 --- a/Sources/HashTreeCollections/HashNode/_HashTreeStatistics.swift +++ b/Sources/HashTreeCollections/HashNode/_HashTreeStatistics.swift @@ -118,8 +118,7 @@ extension _HashNode { // Note: for simplicity, we assume that there is no padding between // the object header and the storage header. let start = _getUnsafePointerToStoredProperties(self.raw.storage) - let capacity = self.raw.storage.capacity - let end = $0._memory + capacity * MemoryLayout<_RawHashNode>.stride + let end = $0._memory + $0.byteCapacity stats.grossBytes += objectHeaderSize + (end - start) for child in $0.children { diff --git a/Sources/HeapModule/Heap+UnsafeHandle.swift b/Sources/HeapModule/Heap+UnsafeHandle.swift index b7d8ceebb..774ea1709 100644 --- a/Sources/HeapModule/Heap+UnsafeHandle.swift +++ b/Sources/HeapModule/Heap+UnsafeHandle.swift @@ -81,12 +81,17 @@ extension Heap._UnsafeHandle { @inlinable @inline(__always) internal func minValue(_ a: _HeapNode, _ b: _HeapNode) -> _HeapNode { - self[a] < self[b] ? a : b + // The expression used here matches the implementation of the + // standard `Swift.min(_:_:)` function. This attempts to + // preserve any pre-existing order in case `T` has identity. + // `(min(x, y), max(x, y))` should return `(x, y)` in case `x == y`. + self[b] < self[a] ? b : a } @inlinable @inline(__always) internal func maxValue(_ a: _HeapNode, _ b: _HeapNode) -> _HeapNode { - self[a] < self[b] ? b : a + // In case `a` and `b` match, we need to pick `b`. See `minValue(_:_:)`. + self[b] >= self[a] ? b : a } } diff --git a/Sources/HeapModule/Heap.swift b/Sources/HeapModule/Heap.swift index 7c60a6745..a4304c8d2 100644 --- a/Sources/HeapModule/Heap.swift +++ b/Sources/HeapModule/Heap.swift @@ -212,7 +212,7 @@ extension Heap { handle.swapAt(.leftMax, with: &removed) } } else { - let maxNode = handle.maxValue(.rightMax, .leftMax) + let maxNode = handle.maxValue(.leftMax, .rightMax) handle.swapAt(maxNode, with: &removed) handle.trickleDownMax(maxNode) } @@ -268,6 +268,28 @@ extension Heap { _checkInvariants() return removed } + + /// Removes all the elements that satisfy the given predicate. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// heap as its argument and returns a Boolean value indicating + /// whether the element should be removed from the heap. + /// + /// - Complexity: O(*n*), where *n* is the number of items in the heap. + @inlinable + public mutating func removeAll( + where shouldBeRemoved: (Element) throws -> Bool + ) rethrows { + defer { + if _storage.count > 1 { + _update { handle in + handle.heapify() + } + } + _checkInvariants() + } + try _storage.removeAll(where: shouldBeRemoved) + } /// Replaces the maximum value in the heap with the given replacement, /// then updates heap contents to reflect the change. diff --git a/Sources/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra union.swift b/Sources/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra union.swift index b8243a5b9..5847e5ed8 100644 --- a/Sources/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra union.swift +++ b/Sources/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra union.swift @@ -81,5 +81,28 @@ extension OrderedSet { result.formUnion(other) return result } + + /// Returns a new set with the contents of a sequence appended to the end of the set, excluding + /// elements that are already members. + /// + /// let a: OrderedSet = [1, 2, 3, 4] + /// let b: OrderedSet = [0, 2, 4, 6] + /// a.union(b) // [1, 2, 3, 4, 0, 6] + /// + /// This is functionally equivalent to `self.union(elements)`, but it's + /// more explicit about how the new members are ordered in the new set. + /// + /// - Parameter elements: A finite sequence of elements to append. + /// + /// - Complexity: Expected to be O(`self.count` + `elements.count`) on average, + /// if `Element` implements high-quality hashing. + @inlinable + public __consuming func appending( + contentsOf elements: __owned some Sequence + ) -> Self { + var result = self + result.append(contentsOf: elements) + return result + } } diff --git a/Tests/HeapTests/HeapTests.swift b/Tests/HeapTests/HeapTests.swift index 07cc17c45..3c05605e0 100644 --- a/Tests/HeapTests/HeapTests.swift +++ b/Tests/HeapTests/HeapTests.swift @@ -587,4 +587,121 @@ final class HeapTests: CollectionTestCase { } } } + + struct Distinguishable: Comparable, CustomStringConvertible { + var value: Int + var id: Int + + static func ==(left: Self, right: Self) -> Bool { + left.value == right.value + } + static func <(left: Self, right: Self) -> Bool { + left.value < right.value + } + var description: String { "\(value)/\(id)" } + } + + func test_tieBreaks_min() { + var heap: Heap = [ + Distinguishable(value: 1, id: 1), + Distinguishable(value: 1, id: 2), + Distinguishable(value: 1, id: 3), + Distinguishable(value: 1, id: 4), + Distinguishable(value: 1, id: 5), + ] + while !heap.isEmpty { + let oldID = heap.min!.id + let newID = 10 * oldID + let old = heap.replaceMin(with: Distinguishable(value: 1, id: newID)) + expectEqual(old.id, oldID) + expectEqual(heap.min?.id, 10 * oldID) + expectNotNil(heap.removeMin()) { min in + expectEqual(min.id, newID) + } + } + } + + func test_tieBreaks_max() { + var heap: Heap = [ + Distinguishable(value: 1, id: 1), + Distinguishable(value: 1, id: 2), + Distinguishable(value: 1, id: 3), + Distinguishable(value: 1, id: 4), + Distinguishable(value: 1, id: 5), + ] + while !heap.isEmpty { + let oldID = heap.max!.id + let newID = 10 * oldID + print(heap.unordered) + let old = heap.replaceMax(with: Distinguishable(value: 1, id: newID)) + expectEqual(old.id, oldID) + expectEqual(heap.max?.id, 10 * oldID) + expectNotNil(heap.removeMax()) { max in + expectEqual(max.id, newID) + } + } + } + + func test_removeAll_noneRemoved() { + withEvery("count", in: 0 ..< 20) { count in + withEvery("seed", in: 0 ..< 10) { seed in + var rng = RepeatableRandomNumberGenerator(seed: seed) + let input = (0 ..< count).shuffled(using: &rng) + var heap = Heap(input) + heap.removeAll { _ in false } + let expected = Array(0 ..< count) + expectEqualElements(heap.itemsInAscendingOrder(), expected) + } + } + } + + func test_removeAll_allRemoved() { + withEvery("count", in: 0 ..< 20) { count in + withEvery("seed", in: 0 ..< 10) { seed in + var rng = RepeatableRandomNumberGenerator(seed: seed) + let input = (0 ..< count).shuffled(using: &rng) + var heap = Heap(input) + heap.removeAll { _ in true } + expectTrue(heap.isEmpty) + } + } + } + + func test_removeAll_removeEvenNumbers() { + withEvery("count", in: 0 ..< 20) { count in + withEvery("seed", in: 0 ..< 10) { seed in + var rng = RepeatableRandomNumberGenerator(seed: seed) + let input = (0 ..< count).shuffled(using: &rng) + var heap = Heap(input) + heap.removeAll { $0 % 2 == 0 } + let expected = Array(stride(from: 1, to: count, by: 2)) + expectEqualElements(heap.itemsInAscendingOrder(), expected) + } + } + } + + func test_removeAll_throw() throws { + struct DummyError: Error {} + + try withEvery("count", in: 1 ..< 20) { count in + try withEvery("seed", in: 0 ..< 10) { seed in + var rng = RepeatableRandomNumberGenerator(seed: seed) + let input = (0 ..< count).shuffled(using: &rng) + var heap = Heap(input) + expectThrows( + try heap.removeAll { v in + if v == count / 2 { + throw DummyError() + } + return v % 2 == 0 + } + ) { error in + expectTrue(error is DummyError) + } + // Throwing halfway through `removeAll` is expected to reorder items, + // but not remove any. + expectEqualElements(heap.itemsInAscendingOrder(), 0 ..< count) + } + } + } } diff --git a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift index 53e5cd77d..b194d0174 100644 --- a/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift +++ b/Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift @@ -961,6 +961,20 @@ class OrderedSetTests: CollectionTestCase { } } + func test_appending_Self() { + withSampleRanges { r1, r2 in + let expected = Set(r1).union(r2).sorted() + + let u1 = OrderedSet(r1) + let u2 = OrderedSet(r2) + let actual1 = u1.appending(contentsOf: u2) + expectEqualElements(actual1, expected) + + let actual2 = actual1.appending(contentsOf: u2).appending(contentsOf: u1) + expectEqualElements(actual2, expected) + } + } + func test_formUnion_Self() { withSampleRanges { r1, r2 in let expected = Set(r1).union(r2).sorted() diff --git a/cmake/modules/PlatformInfo.cmake b/cmake/modules/PlatformInfo.cmake new file mode 100644 index 000000000..f1e40137a --- /dev/null +++ b/cmake/modules/PlatformInfo.cmake @@ -0,0 +1,24 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2025 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 +#]] + +set(target_info_cmd "${CMAKE_Swift_COMPILER}" -print-target-info) +if(CMAKE_Swfit_COMPILER_TARGET) + list(APPEND target_info_cmd -target ${CMAKE_Swift_COMPILER_TARGET}) +endif() +execute_process(COMMAND ${target_info_cmd} OUTPUT_VARIABLE target_info_json) +message(CONFIGURE_LOG "Swift target info: ${target_info_cmd}\n" +"${target_info_json}") + +if(NOT SwiftCollections_MODULE_TRIPLE) + string(JSON module_triple GET "${target_info_json}" "target" "moduleTriple") + set(SwiftCollections_MODULE_TRIPLE "${module_triple}" CACHE STRING "Triple used for installed swift{doc,module, interface} files") + mark_as_advanced(SwiftCollections_MODULE_TRIPLE) + + message(CONFIGURE_LOG "Swift module triple: ${module_triple}") +endif() diff --git a/cmake/modules/SwiftSupport.cmake b/cmake/modules/SwiftSupport.cmake index 16112d5f6..8920cfa0e 100644 --- a/cmake/modules/SwiftSupport.cmake +++ b/cmake/modules/SwiftSupport.cmake @@ -7,55 +7,6 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] -# Returns the architecture name in a variable -# -# Usage: -# get_swift_host_arch(result_var_name) -# -# Sets ${result_var_name} with the converted architecture name derived from -# CMAKE_SYSTEM_PROCESSOR. -function(get_swift_host_arch result_var_name) - if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") - set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif ("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "AArch64|aarch64|arm64|ARM64") - if(CMAKE_SYSTEM_NAME MATCHES Darwin) - set("${result_var_name}" "arm64" PARENT_SCOPE) - else() - set("${result_var_name}" "aarch64" PARENT_SCOPE) - endif() - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64") - set("${result_var_name}" "powerpc64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "ppc64le") - set("${result_var_name}" "powerpc64le" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "s390x") - set("${result_var_name}" "s390x" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv6l") - set("${result_var_name}" "armv6" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7l") - set("${result_var_name}" "armv7" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "armv7-a") - set("${result_var_name}" "armv7" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "amd64") - set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64") - set("${result_var_name}" "x86_64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "IA64") - set("${result_var_name}" "itanium" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86") - set("${result_var_name}" "i686" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i686") - set("${result_var_name}" "i686" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "wasm32") - set("${result_var_name}" "wasm32" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "wasm64") - set("${result_var_name}" "wasm64" PARENT_SCOPE) - elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "riscv64") - set("${result_var_name}" "riscv64" PARENT_SCOPE) - else() - message(FATAL_ERROR "Unrecognized architecture on host system: ${CMAKE_SYSTEM_PROCESSOR}") - endif() -endfunction() - # Returns the os name in a variable # # Usage: @@ -111,9 +62,10 @@ function(_install_target module) endif() install(FILES $/${module_name}.swiftdoc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${swift}/${swift_os}/${module_name}.swiftmodule - RENAME ${Swift_MODULE_TRIPLE}.swiftdoc) + DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${SwiftCollections_MODULE_TRIPLE}.swiftdoc) + install(FILES $/${module_name}.swiftmodule - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${swift}/${swift_os}/${module_name}.swiftmodule - RENAME ${Swift_MODULE_TRIPLE}.swiftmodule) + DESTINATION lib/${swift}/${swift_os}/${module_name}.swiftmodule + RENAME ${SwiftCollections_MODULE_TRIPLE}.swiftmodule) endfunction()