Created
January 24, 2026 11:19
-
-
Save treboc/dcd76b0f2adaf7e2740b0273889361d4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import Foundation | |
| import SwiftUI | |
| main() | |
| func main() { | |
| let items = (0..<10_000).map { | |
| InternalWidgetModelWrapper(id: $0, size: Size.allCases.randomElement()!) | |
| } | |
| measure(functionName: "buildRows_removeSubrange") { | |
| _ = buildRows_removeSubrange(from: items) | |
| } | |
| measure(functionName: "buildRows_indexSet") { | |
| _ = buildRows_indexSet(from: items) | |
| } | |
| } | |
| enum Size: CaseIterable { | |
| case small | |
| case medium | |
| } | |
| protocol Sizable { | |
| var size: Size { get } | |
| } | |
| struct InternalWidgetModelWrapper: Identifiable, Sizable { | |
| let id: Int | |
| let size: Size | |
| } | |
| struct LayoutRowItem<T: Sizable & Identifiable>: Identifiable { | |
| let id = UUID() | |
| let first: T | |
| var second: T? = nil | |
| } | |
| /// A method that reorders a given array of items, where the items conform to `Sizable`, | |
| /// to make them usable in a grid like structure. | |
| /// - Parameter items: The array of items to be restructured. | |
| /// - Returns: An array of rows. | |
| func buildRows<T: Sizable>( | |
| from items: [T] | |
| ) -> [LayoutRowItem<T>] { | |
| var rows: [LayoutRowItem<T>] = [] | |
| var pendingItems = items | |
| while pendingItems.isEmpty == false { | |
| // Get the first item of the list and remove it from the list directly. | |
| let item = pendingItems.removeFirst() | |
| switch item.size { | |
| // Only one medium item can fit in a row, so add it. | |
| case .medium: | |
| rows.append(LayoutRowItem(first: item)) | |
| case .small: | |
| // If there's another small item that can be placed to the right, do that. | |
| if let secondItemIndex = pendingItems.firstIndex(where: { $0.size == .small }) { | |
| let second = pendingItems.remove(at: secondItemIndex) | |
| rows.append(LayoutRowItem(first: item, second: second)) | |
| // If then there's no other medium item coming, it's the last small item, place it alone. | |
| } else if !pendingItems.contains(where: { $0.size == .medium }) { | |
| rows.append(LayoutRowItem(first: item)) | |
| // If the previous cases did not match at all, insert it back in the list, | |
| // but after the item that's now the first in the list. | |
| } else { | |
| pendingItems.insert(item, at: 1) | |
| } | |
| } | |
| } | |
| return rows | |
| } | |
| func buildRows_indexSet<T: Sizable>( | |
| from items: [T] | |
| ) -> [LayoutRowItem<T>] { | |
| var rows: [LayoutRowItem<T>] = [] | |
| var pendingItems = items | |
| while pendingItems.isEmpty == false { | |
| let index = 0 | |
| let item = pendingItems[index] | |
| // Check if there is a next item, if not it's the last. Append and break. | |
| guard index + 1 < pendingItems.endIndex else { | |
| rows.append(LayoutRowItem(first: item)) | |
| pendingItems.remove(at: index) | |
| break | |
| } | |
| if item.size == .medium { | |
| // Medium items always go alone | |
| rows.append(LayoutRowItem(first: item)) | |
| pendingItems.remove(at: index) | |
| } else { | |
| if let secondItemIndex = pendingItems[1...].firstIndex(where: { $0.size == .small }) { | |
| rows.append(LayoutRowItem(first: item, second: pendingItems[secondItemIndex])) | |
| pendingItems.remove(atOffsets: IndexSet([0, 1])) | |
| } else if !pendingItems.contains(where: { $0.size == .medium }) { | |
| rows.append(LayoutRowItem(first: item)) | |
| pendingItems.remove(at: index) | |
| } else if let nextMediumItemIndex = pendingItems.firstIndex(where: { $0.size == .medium }) { | |
| rows.append(LayoutRowItem(first: pendingItems[nextMediumItemIndex])) | |
| pendingItems.remove(at: nextMediumItemIndex) | |
| } | |
| } | |
| } | |
| return rows | |
| } | |
| func buildRows_removeSubrange<T: Sizable>( | |
| from items: [T] | |
| ) -> [LayoutRowItem<T>] { | |
| var rows: [LayoutRowItem<T>] = [] | |
| var pendingItems = items | |
| while pendingItems.isEmpty == false { | |
| let index = 0 | |
| let item = pendingItems[index] | |
| // Check if there is a next item, if not it's the last. Append and break. | |
| guard index + 1 < pendingItems.endIndex else { | |
| rows.append(LayoutRowItem(first: item)) | |
| pendingItems.remove(at: index) | |
| break | |
| } | |
| if item.size == .medium { | |
| // Medium items always go alone | |
| rows.append(LayoutRowItem(first: item)) | |
| pendingItems.remove(at: index) | |
| } else { | |
| if let secondItemIndex = pendingItems[1...].firstIndex(where: { $0.size == .small }) { | |
| rows.append(LayoutRowItem(first: item, second: pendingItems[secondItemIndex])) | |
| pendingItems.removeSubrange(0...1) | |
| } else if !pendingItems.contains(where: { $0.size == .medium }) { | |
| rows.append(LayoutRowItem(first: item)) | |
| pendingItems.remove(at: index) | |
| } else if let nextMediumItemIndex = pendingItems.firstIndex(where: { $0.size == .medium }) { | |
| rows.append(LayoutRowItem(first: pendingItems[nextMediumItemIndex])) | |
| pendingItems.remove(at: nextMediumItemIndex) | |
| } | |
| } | |
| } | |
| return rows | |
| } | |
| @discardableResult | |
| func measure<R>(functionName: String = #function, body: () throws -> R) rethrows -> R { | |
| #if DEBUG // Don't waste time logging unless we're in debug | |
| // I'm using CoreFoundation's clock as an example, | |
| // but you can use `clock_gettime(CLOCK_MONOTONIC, ...)` or whatever | |
| let startTime = CFAbsoluteTimeGetCurrent() | |
| defer { | |
| let endTime = CFAbsoluteTimeGetCurrent() | |
| let duration = endTime - startTime | |
| NSLog("\(functionName): \(duration) seconds") | |
| } | |
| #endif | |
| return try body() | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment