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
75 changes: 75 additions & 0 deletions Sources/OpenSwiftUICore/Util/StandardLibraryAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -841,3 +841,78 @@ package struct EquatableOptionalObject<T>: Equatable where T: AnyObject {
return lhs.wrappedValue === rhs.wrappedValue
}
}

// MARK: - BidirectionalCollection + insertionSort [6.5.4]

extension BidirectionalCollection where Self: MutableCollection {
/// Sorts the collection in place using the insertion sort algorithm with a custom comparison.
///
/// Insertion sort is a simple sorting algorithm that builds the sorted collection one element
/// at a time by repeatedly taking elements from the unsorted portion and inserting them
/// into their correct position in the sorted portion.
///
/// This implementation is stable, meaning that elements that compare equal retain their
/// relative order from the original collection.
///
/// - Parameter areInIncreasingOrder: A predicate that returns `true` if its first
/// argument should be ordered before its second argument; otherwise, `false`.
/// If `areInIncreasingOrder` throws an error during the sort, the elements may be
/// in an invalid order, but the `mutating` guarantee is still upheld.
/// - Complexity: O(*n*²) in the worst case, where *n* is the length of the collection.
/// Best case is O(*n*) when the collection is already sorted.
///
/// Example usage:
/// ```swift
/// var numbers = [3, 1, 4, 1, 5, 9]
/// numbers.insertionSort(by: <)
/// // numbers is now [1, 1, 3, 4, 5, 9]
/// ```
package mutating func insertionSort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows {
guard !isEmpty else { return }
var currentIndex = index(after: startIndex)

while currentIndex != endIndex {
let currentElement = self[currentIndex]
var insertionIndex = currentIndex
repeat {
let previousIndex = index(before: insertionIndex)
let previousElement = self[previousIndex]
do {
guard try areInIncreasingOrder(currentElement, previousElement) else {
break
}
self[insertionIndex] = previousElement
} catch {
self[insertionIndex] = currentElement
throw error
}
formIndex(before: &insertionIndex)
} while insertionIndex != startIndex

if insertionIndex != currentIndex {
self[insertionIndex] = currentElement
}
formIndex(after: &currentIndex)
}
}
}

extension BidirectionalCollection where Self: MutableCollection, Element: Comparable {
/// Sorts the collection in place using the insertion sort algorithm.
///
/// This method sorts the collection using the less-than operator (`<`) for comparison.
/// Elements are arranged in ascending order.
///
/// - Complexity: O(*n*²) in the worst case, where *n* is the length of the collection.
/// Best case is O(*n*) when the collection is already sorted.
///
/// Example usage:
/// ```swift
/// var numbers = [3, 1, 4, 1, 5, 9]
/// numbers.insertionSort()
/// // numbers is now [1, 1, 3, 4, 5, 9]
/// ```
package mutating func insertionSort() {
insertionSort(by: <)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// BidirectionalCollectionInsertionSortTestsStub.c
// OpenSwiftUISymbolDualTestsSupport

#include "OpenSwiftUIBase.h"

#if OPENSWIFTUI_TARGET_OS_DARWIN

#import <SymbolLocator.h>

DEFINE_SL_STUB_SLF(OpenSwiftUITestStub_BidirectionalCollectionInsertionSortBy, SwiftUI, $sSK7SwiftUISMRzrlE13insertionSort2byySb7ElementSTQz_AEtKXE_tKF);

#endif
143 changes: 143 additions & 0 deletions Tests/OpenSwiftUICoreTests/Util/StandardLibraryAdditionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,146 @@ struct CollectionOfTwoTests {
#expect(!collection.contains("orange"))
}
}

// MARK: - BidirectionalCollectionInsertionSortTests

struct BidirectionalCollectionInsertionSortTests {
@Test
func alreadySorted() {
var array = [1, 2, 3, 4, 5]
array.insertionSort()
#expect(array == [1, 2, 3, 4, 5])
}

@Test
func reverseSorted() {
var array = [5, 4, 3, 2, 1]
array.insertionSort()
#expect(array == [1, 2, 3, 4, 5])
}

@Test
func randomOrder() {
var array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
array.insertionSort()
#expect(array == [1, 1, 2, 3, 3, 4, 5, 5, 6, 9])
}

@Test
func customComparison() {
var array = [1, 2, 3, 4, 5]
array.insertionSort(by: >)
#expect(array == [5, 4, 3, 2, 1])
}

@Test
func stringArray() {
var array = ["zebra", "apple", "banana", "cherry"]
array.insertionSort()
#expect(array == ["apple", "banana", "cherry", "zebra"])
}

@Test
func structComparison() {
struct Person: Comparable {
let name: String
let age: Int

static func < (lhs: Person, rhs: Person) -> Bool {
lhs.age < rhs.age
}

static func == (lhs: Person, rhs: Person) -> Bool {
lhs.name == rhs.name && lhs.age == rhs.age
}
}

var people = [
Person(name: "Alice", age: 30),
Person(name: "Bob", age: 25),
Person(name: "Charlie", age: 35),
]

people.insertionSort()

#expect(people[0].name == "Bob")
#expect(people[1].name == "Alice")
#expect(people[2].name == "Charlie")
}

@Test
func arraySliceSort() {
var array = [5, 1, 4, 2, 3]
var slice = array[1 ... 3]
slice.insertionSort()

array[1 ... 3] = slice
#expect(array == [5, 1, 2, 4, 3])
}

@Test
func throwingComparison() {
enum V: Equatable, CustomStringConvertible {
case value(Int)
case invalid

var description: String {
guard case let .value(value) = self else { return "invalid" }
return value.description
}
}

enum ComparisonError: Error {
case invalid
}
var array: [V] = [.value(5), .value(4), .invalid, .value(2), .value(1)]
do {
try array.insertionSort { first, second in
guard case let .value(firstValue) = first,
case let .value(secondValue) = second
else {
throw ComparisonError.invalid
}
return firstValue < secondValue
}
} catch {
#expect(error is ComparisonError)
}
#expect(array == [.value(4), .value(5), .invalid, .value(2), .value(1)])
}

@Test("Verify self[insertionIndex] = currentElement")
func throwingComparison2() {
enum V: Equatable, CustomStringConvertible {
case value(Int)
case invalid

var description: String {
guard case let .value(value) = self else { return "invalid" }
return value.description
}
}

enum ComparisonError: Error {
case invalid
}
var array: [V] = [.value(5), .value(4), .invalid, .value(2), .value(1)]
do {
try array.insertionSort { first, second in
guard case let .value(firstValue) = first,
case let .value(secondValue) = second
else {
if first == .invalid, second == .value(4) {
throw ComparisonError.invalid
} else {
return true
}
}
return firstValue < secondValue
}
} catch {
#expect(error is ComparisonError)
}
#expect(array == [.value(4), .invalid, .value(5), .value(2), .value(1)])
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//
// StandardLibraryAdditionsTests.swift
// OpenSwiftUISymbolDualTests

#if canImport(SwiftUI, _underlyingVersion: 6.5.4)
import Testing

// MARK: - BidirectionalCollectionInsertionSortTests

extension BidirectionalCollection where Self: MutableCollection {
@_silgen_name("OpenSwiftUITestStub_BidirectionalCollectionInsertionSortBy")
mutating func insertionSort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
}

struct BidirectionalCollectionInsertionSortTests {
@Test
func alreadySorted() {
var array = [1, 2, 3, 4, 5]
array.insertionSort()
#expect(array == [1, 2, 3, 4, 5])
}

@Test
func reverseSorted() {
var array = [5, 4, 3, 2, 1]
array.insertionSort()
#expect(array == [1, 2, 3, 4, 5])
}

@Test
func randomOrder() {
var array = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3]
array.insertionSort()
#expect(array == [1, 1, 2, 3, 3, 4, 5, 5, 6, 9])
}

@Test
func customComparison() {
var array = [1, 2, 3, 4, 5]
array.insertionSort(by: >)
#expect(array == [5, 4, 3, 2, 1])
}

@Test
func stringArray() {
var array = ["zebra", "apple", "banana", "cherry"]
array.insertionSort()
#expect(array == ["apple", "banana", "cherry", "zebra"])
}

@Test
func structComparison() {
struct Person: Comparable {
let name: String
let age: Int

static func < (lhs: Person, rhs: Person) -> Bool {
lhs.age < rhs.age
}

static func == (lhs: Person, rhs: Person) -> Bool {
lhs.name == rhs.name && lhs.age == rhs.age
}
}

var people = [
Person(name: "Alice", age: 30),
Person(name: "Bob", age: 25),
Person(name: "Charlie", age: 35),
]

people.insertionSort()

#expect(people[0].name == "Bob")
#expect(people[1].name == "Alice")
#expect(people[2].name == "Charlie")
}

@Test
func arraySliceSort() {
var array = [5, 1, 4, 2, 3]
var slice = array[1 ... 3]
slice.insertionSort()

array[1 ... 3] = slice
#expect(array == [5, 1, 2, 4, 3])
}

@Test
func throwingComparison() {
enum V: Equatable, CustomStringConvertible {
case value(Int)
case invalid

var description: String {
guard case let .value(value) = self else { return "invalid" }
return value.description
}
}

enum ComparisonError: Error {
case invalid
}
var array: [V] = [.value(5), .value(4), .invalid, .value(2), .value(1)]
do {
try array.insertionSort { first, second in
guard case let .value(firstValue) = first,
case let .value(secondValue) = second
else {
throw ComparisonError.invalid
}
return firstValue < secondValue
}
} catch {
#expect(error is ComparisonError)
}
#expect(array == [.value(4), .value(5), .invalid, .value(2), .value(1)])
}

@Test("Verify self[insertionIndex] = currentElement")
func throwingComparison2() {
enum V: Equatable, CustomStringConvertible {
case value(Int)
case invalid

var description: String {
guard case let .value(value) = self else { return "invalid" }
return value.description
}
}

enum ComparisonError: Error {
case invalid
}
var array: [V] = [.value(5), .value(4), .invalid, .value(2), .value(1)]
do {
try array.insertionSort { first, second in
guard case let .value(firstValue) = first,
case let .value(secondValue) = second
else {
if first == .invalid, second == .value(4) {
throw ComparisonError.invalid
} else {
return true
}
}
return firstValue < secondValue
}
} catch {
#expect(error is ComparisonError)
}
#expect(array == [.value(4), .invalid, .value(5), .value(2), .value(1)])
}
}

#endif