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

add a couple new things #19

Merged
merged 12 commits into from
Jan 19, 2025
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ SpeziFoundation contributors
* [Paul Schmiedmayer](https://github.com/PSchmiedmayer)
* [Andreas Bauer](https://github.com/Supereg)
* [Philipp Zagar](https://github.com/philippzagar)
* [Lukas Kollmer](https://github.com/lukaskollmer)
lukaskollmer marked this conversation as resolved.
Show resolved Hide resolved
16 changes: 13 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,33 @@ let package = Package(
.library(name: "SpeziFoundation", targets: ["SpeziFoundation"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0")
.package(url: "https://github.com/apple/swift-atomics.git", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
.package(url: "https://github.com/StanfordBDHG/XCTRuntimeAssertions", from: "1.1.3")
lukaskollmer marked this conversation as resolved.
Show resolved Hide resolved
] + swiftLintPackage(),
targets: [
.target(
name: "SpeziFoundation",
dependencies: [
.product(name: "Atomics", package: "swift-atomics")
.target(name: "SpeziFoundationObjC"),
.product(name: "Atomics", package: "swift-atomics"),
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "XCTRuntimeAssertions", package: "XCTRuntimeAssertions")
],
resources: [
.process("Resources")
],
plugins: [] + swiftLintPlugin()
),
.target(
name: "SpeziFoundationObjC",
sources: nil
),
.testTarget(
name: "SpeziFoundationTests",
dependencies: [
.target(name: "SpeziFoundation")
.target(name: "SpeziFoundation"),
.product(name: "XCTRuntimeAssertions", package: "XCTRuntimeAssertions")
],
plugins: [] + swiftLintPlugin()
)
Expand Down
18 changes: 18 additions & 0 deletions Sources/SpeziFoundation/Collection Builders/ArrayBuilder.swift
lukaskollmer marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// An `ArrayBuilder<T>` is a result builder that constructs an `Array<T>`.
public typealias ArrayBuilder<T> = RangeReplaceableCollectionBuilder<[T]>

extension Array {
/// Constructs a new array, using a result builder.
public init(@ArrayBuilder<Element> build: () -> Self) {
self = build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// A result builder that constructs an instance of a `RangeReplaceableCollection`.
@resultBuilder
public enum RangeReplaceableCollectionBuilder<C: RangeReplaceableCollection> {
/// The `Element` of the `RangeReplaceableCollection` that will be built up.
public typealias Element = C.Element
}


extension RangeReplaceableCollectionBuilder {
/// :nodoc:
public static func buildExpression(_ expression: Element) -> C {
C(CollectionOfOne(expression))
}

/// :nodoc:
public static func buildExpression(_ expression: some Sequence<Element>) -> C {
C(expression)
}

/// :nodoc:
public static func buildOptional(_ expression: C?) -> C {
expression ?? C()
}

/// :nodoc:
public static func buildEither(first expression: some Sequence<Element>) -> C {
C(expression)
}

/// :nodoc:
public static func buildEither(second expression: some Sequence<Element>) -> C {
C(expression)
}

/// :nodoc:
public static func buildPartialBlock(first: some Sequence<Element>) -> C {
C(first)
}

/// :nodoc:
public static func buildPartialBlock(accumulated: some Sequence<Element>, next: some Sequence<Element>) -> C {
C(accumulated) + C(next)
}

/// :nodoc:
public static func buildBlock() -> C {
C()
}

/// :nodoc:
public static func buildArray(_ components: [some Sequence<Element>]) -> C {
components.reduce(into: C()) { $0.append(contentsOf: $1) }
}

/// :nodoc:
public static func buildFinalResult(_ component: C) -> C {
component
}
}
73 changes: 73 additions & 0 deletions Sources/SpeziFoundation/Collection Builders/SetBuilder.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//


/// ``SetBuilder`` is a result builder for constructing `Set`s.
@resultBuilder
public enum SetBuilder<Element: Hashable> {}


extension Set {
/// Constructs a new `Set` using a result builder.
public init(@SetBuilder<Element> build: () -> Set<Element>) {
self = build()
}
}


extension SetBuilder {
/// :nodoc:
public static func buildExpression(_ expression: Element) -> Set<Element> {
Set(CollectionOfOne(expression))
}

/// :nodoc:
public static func buildExpression(_ expression: some Sequence<Element>) -> Set<Element> {
Set(expression)
}

/// :nodoc:
public static func buildOptional(_ expression: Set<Element>?) -> Set<Element> { // swiftlint:disable:this discouraged_optional_collection
expression ?? Set()
}

/// :nodoc:
public static func buildEither(first expression: some Sequence<Element>) -> Set<Element> {
Set(expression)
}

/// :nodoc:
public static func buildEither(second expression: some Sequence<Element>) -> Set<Element> {
Set(expression)
}

/// :nodoc:
public static func buildPartialBlock(first: some Sequence<Element>) -> Set<Element> {
Set(first)
}

/// :nodoc:
public static func buildPartialBlock(accumulated: some Sequence<Element>, next: some Sequence<Element>) -> Set<Element> {
Set(accumulated).union(next)
}

/// :nodoc:
public static func buildBlock() -> Set<Element> {
Set()
}

/// :nodoc:
public static func buildArray(_ components: [some Sequence<Element>]) -> Set<Element> {
components.reduce(into: Set()) { $0.formUnion($1) }
}

/// :nodoc:
public static func buildFinalResult(_ component: Set<Element>) -> Set<Element> {
component
}
}
73 changes: 73 additions & 0 deletions Sources/SpeziFoundation/Misc/BinarySearch.swift
lukaskollmer marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// This source file is part of the Stanford Spezi open-source project
//
// SPDX-FileCopyrightText: 2025 Stanford University and the project authors (see CONTRIBUTORS.md)
//
// SPDX-License-Identifier: MIT
//

import enum Foundation.ComparisonResult


/// The result of a binary search looking for some element in a collection.
public enum BinarySearchIndexResult<Index> {
/// The searched-for element was found in the collection, at the specified index.
case found(Index)
/// The searched-for element was not found in the collection, but if it were a member of the collection, it would belong at the specified index.
case notFound(Index)
}


extension BinarySearchIndexResult: Equatable where Index: Equatable {}
extension BinarySearchIndexResult: Hashable where Index: Hashable {}


extension Collection {
/// Performs a binary search over the collection, looking for
/// - parameter compare: closure that gets called with an element of the collection to determine, whether the binary search algorithm should go left/right, or has already found its target.
/// The closure should return `.orderedSame` if the element passed to it matches the search destination, `.orderedAscending` if the search should continue to the left,
/// and `.orderedDescending` if it should continue to the right.
/// E.g., when looking for a position for an element `x`, the closure should perform a `compare(x, $0)`.
public func binarySearchForIndex(
of element: Element,
using compare: (Element, Element) -> ComparisonResult
) -> BinarySearchIndexResult<Index> {
binarySearchForIndex(of: element, in: startIndex..<endIndex, using: compare)
}

/// Performs a binary search over the collection, looking for the index at which the element either is, or should be if it were a member of the collection.
/// - parameter element: The element whose position should be determined.
/// - parameter range: The range in which to look for the element.
/// - parameter compare: Closure used to compare two `Element`s against each other. The result of this is used to determine, if the elements are not equal,
/// whether the binary search algorithm should continue to the left or to the right.
/// The closure should return `.orderedSame` if the element passed to it matches the search destination, `.orderedAscending` if the search should continue to the left,
/// and `.orderedDescending` if it should continue to the right.
public func binarySearchForIndex(
of element: Element,
in range: Range<Index>,
using compare: (Element, Element) -> ComparisonResult
) -> BinarySearchIndexResult<Index> {
guard let middle: Self.Index = middleIndex(of: range) else {
return .notFound(range.upperBound)
}
switch compare(element, self[middle]) {
case .orderedAscending: // lhs < rhs
return binarySearchForIndex(of: element, in: range.lowerBound..<middle, using: compare)
case .orderedDescending: // lhs > rhs
return binarySearchForIndex(of: element, in: index(after: middle)..<range.upperBound, using: compare)
case .orderedSame: // lhs == rhs
return .found(middle)
}
}

/// Computes, for a non-empty range over the collection, the middle of the range.
/// If the range is empty, this function will return `nil`.
public func middleIndex(of range: Range<Index>) -> Index? {
guard !range.isEmpty else {
return nil
}
let distance = self.distance(from: range.lowerBound, to: range.upperBound)
let resultIdx = self.index(range.lowerBound, offsetBy: distance / 2)
return resultIdx
}
}
Loading
Loading