damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

NonCopyableLinkedList.swift (5893B)


      1 //
      2 //  NonCopyableLinkedList.swift
      3 //  damus
      4 //
      5 //  Created by Daniel D’Aquino on 2025-07-04.
      6 //
      7 
      8 /// A linked list to help with iteration of non-copyable elements
      9 ///
     10 /// This is needed to provide an array-like abstraction or iterators since swift arrays or iterator protocols require the element to be "copyable"
     11 struct NonCopyableLinkedList<T: ~Copyable>: ~Copyable {
     12     private var head: Node<T>? = nil
     13     private var tail: Node<T>? = nil
     14     private(set) var count: Int = 0
     15     
     16     /// Iterates over each item of the list, with enumeration support.
     17     func forEachItem<Y>(_ borrowingFunction: ((_ index: Int, _ item: borrowing T) throws -> LoopCommand<Y>)) rethrows -> Y?  {
     18         var indexCounter = 0
     19         
     20         var cursor: Node? = self.head
     21         
     22         outerLoop: while let nextItem = cursor {
     23             let loopIterationResult = try borrowingFunction(indexCounter, nextItem.value)
     24             indexCounter += 1
     25             cursor = nextItem.next
     26             switch loopIterationResult {
     27             case .loopBreak:
     28                 break outerLoop
     29             case .loopContinue:
     30                 continue outerLoop
     31             case .loopReturn(let result):
     32                 return result
     33             }
     34         }
     35         
     36         return nil
     37     }
     38     
     39     /// Iterates over each item of the list in reverse, with enumeration support.
     40     func forEachItemReversed<Y, E: Error>(_ borrowingFunction: ((_ index: Int, _ item: borrowing T) throws(E) -> LoopCommand<Y>)) throws(E) -> Y?  {
     41         var indexCounter = count - 1
     42         var cursor: Node? = self.tail
     43         
     44         outerLoop: while let nextItem = cursor {
     45             let loopIterationResult = try borrowingFunction(indexCounter, nextItem.value)
     46             indexCounter -= 1
     47             cursor = nextItem.previous
     48             switch loopIterationResult {
     49             case .loopBreak:
     50                 break outerLoop
     51             case .loopContinue:
     52                 continue outerLoop
     53             case .loopReturn(let result):
     54                 return result
     55             }
     56         }
     57         
     58         return nil
     59     }
     60     
     61     /// Iterates over each item of the list, with enumeration support, updating some value in each iteration and returning the final value at the end.
     62     func reduce<Y>(initialResult: Y, _ borrowingFunction: ((_ index: Int, _ partialResult: Y, _ item: borrowing T) throws -> LoopCommand<Y>)) throws -> Y {
     63         var indexCounter = 0
     64         var currentResult = initialResult
     65         
     66         var cursor: Node? = self.head
     67         
     68         outerLoop: while let nextItem = cursor {
     69             let loopIterationResult = try borrowingFunction(indexCounter, currentResult, nextItem.value)
     70             indexCounter += 1
     71             cursor = nextItem.next
     72             switch loopIterationResult {
     73             case .loopBreak:
     74                 break outerLoop
     75             case .loopContinue:
     76                 continue outerLoop
     77             case .loopReturn(let result):
     78                 currentResult = result
     79                 continue outerLoop
     80             }
     81         }
     82         
     83         return currentResult
     84     }
     85     
     86     /// Uses a specific item of the list based on a provided index.
     87     ///
     88     /// O(N/2) worst case scenario
     89     ///
     90     /// Returns `nil` if nothing was found
     91     func useItem<Y>(at index: Int, _ borrowingFunction: ((_ item: borrowing T) throws -> Y)) rethrows -> Y? {
     92         if index < 0 || index >= self.count {
     93             return nil
     94         }
     95         else if index < self.count / 2 {
     96             return try self.forEachItem({ i, item in
     97                 if i == index {
     98                     return .loopReturn(try borrowingFunction(item))
     99                 }
    100                 return .loopContinue
    101             })
    102         }
    103         else {
    104             return try self.forEachItemReversed({ i, item in
    105                 if i == index {
    106                     return .loopReturn(try borrowingFunction(item))
    107                 }
    108                 return .loopContinue
    109             })
    110         }
    111     }
    112     
    113     /// Adds an item to the tail end list
    114     mutating func add(item: consuming T) {
    115         guard self.head != nil, let currentTail = self.tail else {
    116             let firstNode = Node(value: item, next: nil, previous: nil)
    117             self.head = firstNode
    118             self.tail = firstNode
    119             self.count = 1
    120             return
    121         }
    122         let newTail = Node(value: item, next: nil, previous: currentTail)
    123         currentTail.next = newTail
    124         self.tail = newTail
    125         self.count += 1
    126     }
    127     
    128     /// A node of the linked list
    129     ///
    130     /// Should be `~Copyable` but that would require using a value type such as a struct or enum, and the Swift compiler does not support recursive enums with non-copyable objects for some reason. Example:
    131     /// ```swift
    132     /// enum List<Y: ~Copyable>: ~Copyable {
    133     ///     indirect case node(value: Y, next: NewList<Y>)  // <-- ERROR: Noncopyable enum 'List' cannot be marked indirect or have indirect cases yet
    134     ///     case empty
    135     /// }
    136     /// ```
    137     ///
    138     /// Therefore, we make it `private` to make sure we contain the exposure of this unsafe object to only this class. Outside users of the linked list can access objects via the iterator functions.
    139     private class Node<Item: ~Copyable> {
    140         let value: Item
    141         var next: Node?
    142         var previous: Node?
    143         
    144         init(value: consuming Item, next: consuming Node?, previous: consuming Node?) {
    145             self.value = value
    146             self.next = next
    147             self.previous = previous
    148         }
    149     }
    150 
    151     /// A loop command to allow closures to control the loop they are in.
    152     enum LoopCommand<Y> {
    153         /// Breaks out of the loop
    154         case loopBreak
    155         /// Continues to the next iteration of the loop
    156         case loopContinue
    157         /// Stops iterating and return a value
    158         case loopReturn(Y)
    159     }
    160 }