美文网首页
Swift | Building a Custom Collec

Swift | Building a Custom Collec

作者: 清無 | 来源:发表于2022-02-17 18:22 被阅读0次

In this tutorial, you’re going to build a multiset, otherwise known as a bag, from scratch.

Along the way, you’ll learn how to:

  • Adopt these protocols: Hashable, Sequence, Collection, CustomStringConvertible, ExpressibleByArrayLiteral and ExpressibleByDictionaryLiteral.
  • Create custom initializations for your collections.
  • Improve your custom collections with custom methods.

Getting Started

A Bag is like a Set in that it does not store repeated values. The difference is this: A Bag keeps a running count of any repeated values while a Set does not.

Set vs Bag

Initializing Collections

To avoid this, Swift supplies two protocols that enable initialization with sequence literals. Literals give you a shorthand way to write data without explicitly creating an object.

// 1
extension Bag: ExpressibleByArrayLiteral {
  init(arrayLiteral elements: Element...) {
    self.init(elements)
  }
}

// 2
extension Bag: ExpressibleByDictionaryLiteral {
  init(dictionaryLiteral elements: (Element, Int)...) {
    self.init(elements.map { (key: $0.0, value: $0.1) })
  }
}
  • ExpressibleByArrayLiteral is used to create a Bag from an array style literal. Here you use the initializer you created earlier and pass in the elements collection.
  • ExpressibleByDictionaryLiteral does the same but for dictionary style literals. The map converts elements to the named-tuple the initializer expects.

Enforcing Non-destructive Iteration

One caveat: Sequence does not require conforming types to be non-destructive. This means that after iteration, there’s no guarantee that future iterations will start from the beginning. That’s a huge issue if you plan on iterating over your data more than once.

To enforce non-destructive iteration, your object needs to conform to the Collection protocol.

Collection inherits from Indexable and Sequence.

Collection

Final Codes

import Foundation

struct Bag<Thing: Hashable> {
    fileprivate var contents: [Thing: Int] = [:]
    
    var thingsKinds: Int {
        return contents.count
    }
    
    var thingsCount: Int {
        return contents.values.reduce(0){ $0 + $1 }
    }
    
    init(){}
    
    init<S: Sequence>(_ list: S) where S.Iterator.Element == Thing {
        list.forEach{ put($0) }
    }
    
    init<S: Sequence>(_ tuple: S) where S.Iterator.Element == (key: Thing, value: Int) {
        tuple.forEach{ put($0.key, num: $0.value) }
    }
    
    mutating func put(_ thing: Thing, num: Int = 1) {
        precondition(num > 0, "Should put at least 1 thing in bag!")
        
        if let currentNum = contents[thing] {
            contents[thing] = currentNum + num
        }
        else {
            contents[thing] = num
        }
    }
    
    mutating func take(_ thing: Thing, num: Int = 1) {
        precondition(num > 0, "Should take at least 1 thing from bag!")
        
        guard
            let currentCount = contents[thing],
            currentCount >= num else {
                return
            }
        if currentCount > num {
            contents[thing] = currentCount - num
        }
        else {
            contents.removeValue(forKey: thing)
        }
    }
}

extension Bag: CustomStringConvertible {
    var description: String {
        return String(describing: contents)
    }
}

extension Bag: ExpressibleByDictionaryLiteral {
    init(dictionaryLiteral elements: (Thing, Int)...) {
        self.init(elements.map{ (key: $0.0, value: $0.1) })
    }
}

extension Bag: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: Thing...) {
        self.init(elements)
    }
}


struct BagIndex<Element: Hashable> {
    fileprivate let index: DictionaryIndex<Element, Int>
    
    fileprivate init(
        _ dictionaryIndex: DictionaryIndex<Element, Int>) {
            self.index = dictionaryIndex
        }
}

extension BagIndex: Comparable {
    static func == (lhs: BagIndex, rhs: BagIndex) -> Bool {
        return lhs.index == rhs.index
    }
    
    static func < (lhs: BagIndex, rhs: BagIndex) -> Bool {
        return lhs.index < rhs.index
    }
}

extension Bag: Sequence {
    typealias Iterator = AnyIterator<(thing: Thing, num: Int)>
    
    func makeIterator() -> Iterator {
        var iterator = contents.makeIterator()
        
        return AnyIterator {
            return iterator.next()
        }
    }
}

extension Bag: Collection {
    typealias Index = BagIndex<Thing>
    
    var startIndex: BagIndex<Thing> {
        return .init(contents.startIndex)
    }
    
    var endIndex: BagIndex<Thing> {
        return .init(contents.endIndex)
    }
    
    subscript (position: Index) -> Iterator.Element {
        precondition((startIndex ..< endIndex).contains(position),
                     "out of bounds")
        // 3
        let dictionaryElement = contents[position.index]
        return (thing: dictionaryElement.key,
                num: dictionaryElement.value)
    }
    
    func index(after i: Index) -> Index {
        // 4
        return Index(contents.index(after: i.index))
    }
}


var bag = Bag<String>()
bag.put("Book")
bag.put("Food", num: 2)

let bag1: Bag = ["A", "B"]
bag1

let bag2: Bag = ["A": 1, "B": 2]
bag2

bag.forEach{
    print($0)
}

相关文章

网友评论

      本文标题:Swift | Building a Custom Collec

      本文链接:https://www.haomeiwen.com/subject/zfwrlrtx.html