Skip to content
Merged
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
6 changes: 2 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ if(COLLECTIONS_SINGLE_MODULE)
endif()
install(FILES $<TARGET_PROPERTY:${COLLECTIONS_MODULE_NAME},Swift_MODULE_DIRECTORY>/${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 $<TARGET_PROPERTY:${COLLECTIONS_MODULE_NAME},Swift_MODULE_DIRECTORY>/${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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 7 additions & 2 deletions Sources/HeapModule/Heap+UnsafeHandle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
24 changes: 23 additions & 1 deletion Sources/HeapModule/Heap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Element>
) -> Self {
var result = self
result.append(contentsOf: elements)
return result
}
}

117 changes: 117 additions & 0 deletions Tests/HeapTests/HeapTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
14 changes: 14 additions & 0 deletions Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 24 additions & 0 deletions cmake/modules/PlatformInfo.cmake
Original file line number Diff line number Diff line change
@@ -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()
58 changes: 5 additions & 53 deletions cmake/modules/SwiftSupport.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -111,9 +62,10 @@ function(_install_target module)
endif()

install(FILES $<TARGET_PROPERTY:${module},Swift_MODULE_DIRECTORY>/${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 $<TARGET_PROPERTY:${module},Swift_MODULE_DIRECTORY>/${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()