2

(Using my usual playing card example)
I am trying to make a generic CardCollection that both a Deck and a Hand would inherit from. Both Decks and Hands would need to be sorted or shuffled, but there would be some differences such as initialisation and whether the method for removing a Card for use elsewhere is Deal (for a Deck), Play, or Discard (for Hands).

class CardCollection: <Some protocol or another that Arrays use> {
    var collective = [Card]()
    // CardCollection-specific functions

    // pass-through functions
    func append(newCard: Card) {
        collective.append(newCard)
    }
}


class Deck: CardCollection {
    // deck-specific functions
}
class Hand: CardCollection {
    // hand-specific functions
}

The way I'm currently implementing it (see above) is with a Class that contains an Array of Cards, but I can't use my classes like they were Arrays without writing tons of pass-through functions to get my classes to conform to all the protocols as an Array.

What I need is a way that lets me do things like for card in deck (as if deck were simply an Array<Card>) without writing tons and tons of wrapper functions just to get the CardCollection to conform to all the necessary protocols.

How do I make a CardCollection that functions like it's just an Array<Card> without making pass-through functions on every function used by the protocols that Array uses?

1 Answer 1

4

You can define a CardCollection protocol which inherits from RangeReplaceableCollectionType, and a protocol extension with default implementations to forward all access methods to the underlying collective array:

struct Card { 
    // Simplified for demonstration purposes:
    let rank : Int
    let suit : Int
}

protocol CardCollection : RangeReplaceableCollectionType {
    var collective : [Card] { get set }
}

extension CardCollection  {

    var startIndex : Int { return collective.startIndex }
    var endIndex : Int { return collective.endIndex }

    subscript(position : Int) -> Card {
        get {
            return collective[position]

        }
        set(newElement) {
            collective[position] = newElement
        }
    }

    mutating func replaceRange<C : CollectionType where C.Generator.Element == Card>(subRange: Range<Int>, with newElements: C) {
        collective.replaceRange(subRange, with: newElements)
    }
}

Then

struct Deck: CardCollection {
    var collective = [Card]()

}

struct Hand: CardCollection {
    var collective = [Card]()

}

both conform to RangeReplaceableCollectionType and can be treated like an array:

var deck = Deck()
deck.append(Card(rank: 1, suit: 1))
deck[0] = Card(rank: 2, suit: 3)

for card in deck {
    print(card)
}

var hand = Hand()
hand.append(deck.first!)

If Deck/Hand are classes instead of structs then they need to be final or have a required init() method, compare Why use required Initializers in Swift classes?.


Slightly more general, you can define an ElementCollection protocol (independently of the Card type) which behaves like an array (by conforming to RangeReplaceableCollectionType) and forwards the access to an underlying elements array:

protocol ElementCollection : RangeReplaceableCollectionType {
    typealias Element
    var elements : [Element] { get set }
}

extension ElementCollection  {

    var startIndex : Int { return elements.startIndex }
    var endIndex : Int { return elements.endIndex }

    subscript(position : Int) -> Element {
        get {
            return elements[position]

        }
        set(newElement) {
            elements[position] = newElement
        }
    }

    mutating func replaceRange<C : CollectionType where C.Generator.Element == Element>(subRange: Range<Int>, with newElements: C) {
        elements.replaceRange(subRange, with: newElements)
    }
}

Which is then used as

struct Card { 
    // Simplified for demonstration purposes:
    let rank : Int
    let suit : Int
}

struct Deck: ElementCollection {
    var elements = [Card]()

}

struct Hand: ElementCollection {
    var elements = [Card]()

}
Sign up to request clarification or add additional context in comments.

3 Comments

Just to clarify: I still need to provide forwards for all the methods listed on the documentation page for Range ReplacableCollectionType in my protocol extension, right?
@cjm: No, only those which don't have a default implementation. The above example only implements forwards for startIndex, endIndex, subscript (from the Indexable protocol) and replaceRange. The default implementation for the other methods like first, append or generate (used in for .. in) then use these methods.
Then I must've missed something that doesn't have a default implementation in there (most likely the protocol extension) somewhere, since it's complaining that Type 'Deck' does not conform to protocol 'RangeReplaceableCollectionType'. NEVERMIND (RE the above): I was being silly and forgot to expand the issues in the Issue Navigator for details

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.