Files
HtmlRW/Sources/HRW/Internal/TinyArray.swift
2025-09-23 20:22:59 -04:00

420 lines
13 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftCertificates open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftCertificates project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
//Taken from: https://github.com/apple/swift-certificates
/// ``_TinyArray`` is a ``RandomAccessCollection`` optimised to store zero or one ``Element``.
/// It supports arbitrary many elements but if only up to one ``Element`` is stored it does **not** allocate separate storage on the heap
/// and instead stores the ``Element`` inline.
public struct _TinyArray<Element> {
@usableFromInline
enum Storage {
case one(Element)
case arbitrary([Element])
}
@usableFromInline
var storage: Storage
}
// MARK: - TinyArray "public" interface
extension _TinyArray: Equatable where Element: Equatable {}
extension _TinyArray: Hashable where Element: Hashable {}
extension _TinyArray: Sendable where Element: Sendable {}
extension _TinyArray: ExpressibleByArrayLiteral {
@inlinable
public init(arrayLiteral elements: Element...) {
switch elements.count {
case 0:
self = .init()
case 1:
self = .init(CollectionOfOne(elements[0]))
default:
self = .init(elements)
}
}
}
extension _TinyArray: RandomAccessCollection {
public typealias Element = Element
public typealias Index = Int
@inlinable
public subscript(position: Int) -> Element {
get {
self.storage[position]
}
set {
self.storage[position] = newValue
}
}
@inlinable
public var startIndex: Int {
self.storage.startIndex
}
@inlinable
public var endIndex: Int {
self.storage.endIndex
}
}
extension _TinyArray {
@inlinable
public init(_ elements: some Sequence<Element>) {
self.storage = .init(elements)
}
@inlinable
public init(_ elements: some Sequence<Result<Element, some Error>>) throws {
self.storage = try .init(elements)
}
@inlinable
public init() {
self.storage = .init()
}
@inlinable
public mutating func append(_ newElement: Element) {
self.storage.append(newElement)
}
@inlinable
public mutating func append(contentsOf newElements: some Sequence<Element>) {
self.storage.append(contentsOf: newElements)
}
@discardableResult
@inlinable
public mutating func remove(at index: Int) -> Element {
self.storage.remove(at: index)
}
@inlinable
public mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows {
try self.storage.removeAll(where: shouldBeRemoved)
}
@inlinable
public mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows {
try self.storage.sort(by: areInIncreasingOrder)
}
@inlinable
public func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> _TinyArray<SegmentOfResult.Element> where SegmentOfResult : Sequence {
try self.storage.flatMap(transform)
}
@inlinable
public func flatMap<NEWTYPE>(_ transform: (Element) throws -> _TinyArray<NEWTYPE>) rethrows -> _TinyArray<NEWTYPE> where Element == _TinyArray<NEWTYPE> {
try self.storage.flatMap(transform)
}
@inlinable
public func flatMap<NEWTYPE>() -> _TinyArray<NEWTYPE> where Element == _TinyArray<NEWTYPE> {
self.storage.flatMap()
}
}
// MARK: - TinyArray.Storage "private" implementation
extension _TinyArray.Storage: Equatable where Element: Equatable {
@inlinable
static func == (lhs: Self, rhs: Self) -> Bool {
switch (lhs, rhs) {
case (.one(let lhs), .one(let rhs)):
return lhs == rhs
case (.arbitrary(let lhs), .arbitrary(let rhs)):
// we don't use lhs.elementsEqual(rhs) so we can hit the fast path from Array
// if both arrays share the same underlying storage: https://github.com/apple/swift/blob/b42019005988b2d13398025883e285a81d323efa/stdlib/public/core/Array.swift#L1775
return lhs == rhs
case (.one(let element), .arbitrary(let array)),
(.arbitrary(let array), .one(let element)):
guard array.count == 1 else {
return false
}
return element == array[0]
}
}
}
extension _TinyArray.Storage: Hashable where Element: Hashable {
@inlinable
func hash(into hasher: inout Hasher) {
// same strategy as Array: https://github.com/apple/swift/blob/b42019005988b2d13398025883e285a81d323efa/stdlib/public/core/Array.swift#L1801
hasher.combine(count)
for element in self {
hasher.combine(element)
}
}
}
extension _TinyArray.Storage: Sendable where Element: Sendable {}
extension _TinyArray.Storage: RandomAccessCollection {
@inlinable
subscript(position: Int) -> Element {
get {
switch self {
case .one(let element):
guard position == 0 else {
fatalError("index \(position) out of bounds")
}
return element
case .arbitrary(let elements):
return elements[position]
}
}
set {
switch self {
case .one:
guard position == 0 else {
fatalError("index \(position) out of bounds")
}
self = .one(newValue)
case .arbitrary(var elements):
elements[position] = newValue
self = .arbitrary(elements)
}
}
}
@inlinable
var startIndex: Int {
0
}
@inlinable
var endIndex: Int {
switch self {
case .one: return 1
case .arbitrary(let elements): return elements.endIndex
}
}
}
extension _TinyArray.Storage {
@inlinable
init(_ elements: some Sequence<Element>) {
self = .arbitrary([])
self.append(contentsOf: elements)
}
@inlinable
init(_ newElements: some Sequence<Result<Element, some Error>>) throws {
var iterator = newElements.makeIterator()
guard let firstElement = try iterator.next()?.get() else {
self = .arbitrary([])
return
}
guard let secondElement = try iterator.next()?.get() else {
// newElements just contains a single element
// and we hit the fast path
self = .one(firstElement)
return
}
var elements: [Element] = []
elements.reserveCapacity(newElements.underestimatedCount)
elements.append(firstElement)
elements.append(secondElement)
while let nextElement = try iterator.next()?.get() {
elements.append(nextElement)
}
self = .arbitrary(elements)
}
@inlinable
init() {
self = .arbitrary([])
}
@inlinable
mutating func append(_ newElement: Element) {
self.append(contentsOf: CollectionOfOne(newElement))
}
@inlinable
mutating func append(contentsOf newElements: some Sequence<Element>) {
switch self {
case .one(let firstElement):
var iterator = newElements.makeIterator()
guard let secondElement = iterator.next() else {
// newElements is empty, nothing to do
return
}
var elements: [Element] = []
elements.reserveCapacity(1 + newElements.underestimatedCount)
elements.append(firstElement)
elements.append(secondElement)
elements.appendRemainingElements(from: &iterator)
self = .arbitrary(elements)
case .arbitrary(var elements):
if elements.isEmpty {
// if `self` is currently empty and `newElements` just contains a single
// element, we skip allocating an array and set `self` to `.one(firstElement)`
var iterator = newElements.makeIterator()
guard let firstElement = iterator.next() else {
// newElements is empty, nothing to do
return
}
guard let secondElement = iterator.next() else {
// newElements just contains a single element
// and we hit the fast path
self = .one(firstElement)
return
}
elements.reserveCapacity(elements.count + newElements.underestimatedCount)
elements.append(firstElement)
elements.append(secondElement)
elements.appendRemainingElements(from: &iterator)
self = .arbitrary(elements)
} else {
elements.append(contentsOf: newElements)
self = .arbitrary(elements)
}
}
}
@discardableResult
@inlinable
mutating func remove(at index: Int) -> Element {
switch self {
case .one(let oldElement):
guard index == 0 else {
fatalError("index \(index) out of bounds")
}
self = .arbitrary([])
return oldElement
case .arbitrary(var elements):
defer {
self = .arbitrary(elements)
}
return elements.remove(at: index)
}
}
@inlinable
mutating func removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows {
switch self {
case .one(let oldElement):
if try shouldBeRemoved(oldElement) {
self = .arbitrary([])
}
case .arbitrary(var elements):
defer {
self = .arbitrary(elements)
}
return try elements.removeAll(where: shouldBeRemoved)
}
}
@inlinable
mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows {
switch self {
case .one:
// a collection of just one element is always sorted, nothing to do
break
case .arbitrary(var elements):
defer {
self = .arbitrary(elements)
}
try elements.sort(by: areInIncreasingOrder)
}
}
@inlinable
func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> _TinyArray<SegmentOfResult.Element> where SegmentOfResult : Sequence {
switch self {
case .one(let element):
let sequence:SegmentOfResult = try transform(element)
let result:_TinyArray<SegmentOfResult.Element> = _TinyArray<SegmentOfResult.Element>(sequence)
return result
case .arbitrary(let elements):
let seq:Array<SegmentOfResult.Element> = try elements.flatMap({ try transform($0) })
let result:_TinyArray<SegmentOfResult.Element> = _TinyArray<SegmentOfResult.Element>(seq)
return result
}
}
@inlinable
func flatMap<NEWTYPE>(_ transform: (Element) throws -> _TinyArray<NEWTYPE>) rethrows -> _TinyArray<NEWTYPE> where Element == _TinyArray<NEWTYPE> {
switch self {
case .one(let element):
let test:_TinyArray<NEWTYPE> = element
let sequence:_TinyArray<NEWTYPE> = try transform(element)
return sequence
case .arbitrary(let elements):
let seq:Array<NEWTYPE> = try elements.flatMap({ try transform($0) })
let result:_TinyArray<NEWTYPE> = _TinyArray<NEWTYPE>(seq)
return result
}
}
@inlinable
func flatMap<NEWTYPE>() -> _TinyArray<NEWTYPE> where Element == _TinyArray<NEWTYPE> {
switch self {
case .one(let element):
return element
case .arbitrary(let elements):
let seq:Array<NEWTYPE> = elements.flatMap({ $0 })
let result:_TinyArray<NEWTYPE> = _TinyArray<NEWTYPE>(seq)
return result
}
}
}
extension Array {
@inlinable
func flatMap<NEWTYPE>() -> Array<NEWTYPE> where Element == Array<NEWTYPE> {
if (self.count == 1) {
return self.first!
} else {
let arr = self.flatMap { $0 }
return arr
}
}
@inlinable
mutating func appendRemainingElements(from iterator: inout some IteratorProtocol<Element>) {
while let nextElement = iterator.next() {
append(nextElement)
}
}
@inlinable
func flatMapToTiny<NEWTYPE>() -> _TinyArray<NEWTYPE> where Element == _TinyArray<NEWTYPE> {
if (self.count == 1) {
return self.first!
} else {
let arr = self.flatMap({ $0 })
return _TinyArray(arr)
}
}
}