Skip to content

Commit

Permalink
feat(core): fold implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
dzmitry-antonenka-sap committed Sep 4, 2023
1 parent 89f263c commit 506bf90
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 35 deletions.
27 changes: 25 additions & 2 deletions Sources/SwiftListTreeDataSource/ListTreeDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,19 @@ open class TreeItem<Item: Hashable>: Hashable, Identifiable {
}
return counter
}


public func fold<Result>(_ leaf: (Item) -> Result, cons: (Item, [Result]) -> Result) -> Result {
switch self.subitems {
case []:
return leaf(self.value)
case let nodes:
return cons(
self.value,
nodes.map { $0.fold(leaf, cons: cons) }
)
}
}

public func allParents(of item: TreeItem<Item>) -> [TreeItem<Item>] {
var parents: [TreeItem<Item>] = []
var currentItem: TreeItem<Item> = item
Expand Down Expand Up @@ -89,7 +101,18 @@ open class ListTreeDataSource<ItemIdentifierType> where ItemIdentifierType : Has
func setShownFlatItems(_ items: [TreeItemType]) {
self.shownFlatItems = items
}


/// Folds created hierarchical store.
/// - Parameters:
/// - leaf: The leaf case
/// - cons: The cons case
/// - Returns: The folded hierarchical store.
public func fold<Result>(_ leaf: (ItemIdentifierType) -> Result, cons: (ItemIdentifierType, [Result]) -> Result) -> [Result] {
backingStore.map { node in
node.fold(leaf, cons: cons)
}
}

/// Adds the array of `items` to specified `parent`.
/// - Parameters:
/// - items: The array of items to add.
Expand Down
4 changes: 3 additions & 1 deletion Tests/Shared/Helpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import Foundation
public func addItems(_ items: [OutlineItem], to snapshot: ListTreeDataSource<OutlineItem>) {
addItems(items, itemChildren: { $0.subitems }, to: snapshot)
}

public func addItems(_ items: [NodeTestItem], to snapshot: ListTreeDataSource<NodeTestItem>) {
addItems(items, itemChildren: { $0.subitems }, to: snapshot)
}
public func depthFirstFlattened(items: [OutlineItem]) -> [OutlineItem] {
return depthFirstFlattened(items: items, itemChildren: { $0.subitems })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// Created by Dzmitry Antonenka on 18.04.21.
//

import TestsShared
import Foundation

struct DataSet {
Expand All @@ -25,7 +24,21 @@ struct DataSet {
// Total elements in tree: (Int) $R0 = 7_174_452, creation time ~17.5 sec on MBP 2019, core i7
return menuItems(targetNestLevel: 14, currentLevel: 0, itemsInSection: 3)
}()


static var mockDataTiny: [OutlineItem] = {
var items: [OutlineItem] = [
OutlineItem(title: "Level 0, item1", subitems: [
OutlineItem(title: "Level 1, item1"),
OutlineItem(title: "Level 1, item2", subitems: [
OutlineItem(title: "Level 2, item1"),
OutlineItem(title: "Level 2, item2")
])
]),
OutlineItem(title: "Level 0, item2")
]
return items
}()

static var mockDataSmall: [OutlineItem] = {
var items: [OutlineItem] = [
OutlineItem(title: "Compositional Layout", subitems: [
Expand Down Expand Up @@ -92,16 +105,17 @@ struct DataSet {
}
}


enum MockData {
public enum MockData {
case tiny
case small
case large88K
case large350K
case doubleLarge797K
case extraLarge7_2M
var items: [OutlineItem] {

public var items: [OutlineItem] {
switch self {
case .tiny: return DataSet.mockDataTiny
case .small: return DataSet.mockDataSmall
case .large88K: return DataSet.mockData88K
case .large350K: return DataSet.mockData350K
Expand All @@ -111,15 +125,16 @@ enum MockData {
}
}

class DataManager {
static let shared = DataManager()
public class DataManager {
public static let shared = DataManager()

var mockData: MockData { self.mockDataSmall }
public var mockData: MockData { self.mockDataSmall }

// specialized data sets
lazy var mockDataSmall: MockData = .small
lazy var mockDataLarge88K: MockData = .large88K
lazy var mockDataLarge350K: MockData = .large350K
lazy var mockDataDoubleLarge797K: MockData = .doubleLarge797K
lazy var mockDataExtraLarge7_2M: MockData = .extraLarge7_2M
public lazy var mockDataTiny: MockData = .tiny
public lazy var mockDataSmall: MockData = .small
public lazy var mockDataLarge88K: MockData = .large88K
public lazy var mockDataLarge350K: MockData = .large350K
public lazy var mockDataDoubleLarge797K: MockData = .doubleLarge797K
public lazy var mockDataExtraLarge7_2M: MockData = .extraLarge7_2M
}
44 changes: 40 additions & 4 deletions Tests/Shared/Model.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import Foundation

public class OutlineItem: Hashable {
public let identifier: UUID
public let title: String
public var subitems: [OutlineItem]

public init(title: String,
subitems: [OutlineItem] = []) {
public init(
identifier: UUID = UUID(),
title: String,
subitems: [OutlineItem] = []
) {
self.identifier = identifier
self.title = title
self.subitems = subitems
}

public init(
other: OutlineItem
) {
self.identifier = other.identifier
self.title = other.title
self.subitems = other.subitems
}

public func hash(into hasher: inout Hasher) {
// don't add subitems recursively for performance reasons.
hasher.combine(identifier)
}
public static func == (lhs: OutlineItem, rhs: OutlineItem) -> Bool {
return lhs.identifier == rhs.identifier
// don't add subitems recursively for performance reasons.
lhs.identifier == rhs.identifier
}
private let identifier = UUID()
}

extension OutlineItem: CustomStringConvertible {
Expand All @@ -24,3 +39,24 @@ extension OutlineItem: CustomStringConvertible {
extension OutlineItem: CustomDebugStringConvertible {
public var debugDescription: String { "\(title)" }
}

public struct NodeTestItem: Hashable {
public let identifier: UUID
public let title: String
public var subitems: [NodeTestItem]
public init(identifier: UUID, title: String, subitems: [NodeTestItem]) {
self.identifier = identifier
self.title = title
self.subitems = subitems
}
}
extension NodeTestItem {
public init(_ item: NodeTestItem) {
self.init(identifier: item.identifier, title: item.title, subitems: item.subitems)
}
public init(outline: OutlineItem) {
identifier = outline.identifier
title = outline.title
subitems = outline.subitems.map { NodeTestItem(outline: $0) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,9 @@ class DebugDescriptionUtilsTests: XCTestCase {
// MARK: - Helpers

func tinyHardcodedDataset() -> [OutlineItem] {
return [
OutlineItem(title: "Level 0, item1", subitems: [
OutlineItem(title: "Level 1, item1"),
OutlineItem(title: "Level 1, item2", subitems: [
OutlineItem(title: "Level 2, item1"),
OutlineItem(title: "Level 2, item2")
])
]),
OutlineItem(title: "Level 0, item2")
]
DataManager.shared.mockDataTiny.items
}

func verifyExpandedLevelsDescriptionsMatchesTopLevelForFlattenedItems() {
let backingStoreExpandedLevelsDescription = debugDescriptionExpandedLevels(sut.backingStore)
let flattenedItemsTopLevelsDescription = debugDescriptionTopLevel(sut.items)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,32 @@ class ListTreeDataSourceTests: XCTestCase {
func setUpSut() {
sut = ListTreeDataSource<OutlineItem>()
}

// MARK: - Append/Insert/Delete tests

func test_foldRecreate_withTinyDataSet_shouldMatchIdentity() {
let sut = ListTreeDataSource<NodeTestItem>()

let dataSet = DataManager.shared.mockDataTiny.items.map(NodeTestItem.init(outline:))
addItems(dataSet, to: sut)
sut.reload()

let folded = sut.fold(NodeTestItem.init) { item, subitems in
NodeTestItem(identifier: item.identifier, title: item.title, subitems: subitems)
}
XCTAssertEqual(dataSet, folded)
}

func test_foldId_withTinyDataSet_shouldMatchIdentity() {
let sut = ListTreeDataSource<NodeTestItem>()

let dataSet = DataManager.shared.mockDataTiny.items.map(NodeTestItem.init(outline:))
addItems(dataSet, to: sut)
sut.reload()

let folded = sut.fold(id(_:)) { root, _ in root }
XCTAssertEqual(dataSet, folded)
}

// MARK: - Append/Insert/Delete/Move tests

func test_append_withOneElementToNilParent_shouldAppendAsHead() throws {
sut = ListTreeDataSource<OutlineItem>() // start from clean state
Expand Down Expand Up @@ -459,4 +483,4 @@ class ListTreeDataSourceTests: XCTestCase {
}
}


func id<A>(_ a: A) -> A { a }

0 comments on commit 506bf90

Please sign in to comment.