Is there good way to make Array index limited to some fixed set of values, like some enum case's?

As a way to do defensive programming, I want my array to only allow index to be some known values, not any Int value, which can be anything...something like this:

enum MyIndex: Int, CaseIterable {
    case one, two, three, four, five
}

var array = Array<CGFloat?>(repeating: nil, count: MyIndex.allCases.count)     // how to make this array's index only accept MyIndex?

let value = array[MyIndex.one]    // prefer not to use MyIndex.one.rawValue

Do you just want to use a Dictionary<MyIndex, CGFloat> instead?

enum MyIndex {
    case one, two, three, four, five
}

var dictionary: [MyIndex: CGFloat] = [:]
let value: CGFloat? = dictionary[.one]
1 Like

Does Dictionary perform as good as Array in memory and speed? If it does, then most likely yes.

A raw dictionary is slower than a raw array because it needs to hash the indices. But it is probably about the same as an array that is constantly converting indices.

If you want to measure the difference, you could extend Array like the following and compare the two:

extension Array {
  subscript<I>(position: I) -> Element where I: RawRepresentable, I.RawValue: Int {
    get {
      return self[position.rawValue]
    }
    set {
      self[position.rawValue] = newValue
    }
  }
}

enum MyIndex: Int, CaseIterable {
    case one, two, three, four, five
}

var array: [CGFloat?] = [CGFloat?](repeating: nil, count: MyIndex.allCases.count)
let value: CGFloat? = array[MyIndex.one]

But since the basic Int subscript still exists alongside your new one, you’d need to hide it by encapsulating the entire thing inside another type for this second strategy to actually help you on the defensive programming front.

1 Like

You can encapsulate the array in a new type, and make that type expose a subscript operator that only accepts a particular index type. This index could itself be a wrapper for an Int, but written so that only "valid" indexes are ever possible. Consider this simple TicTacToe implementation:

enum TicTacToe {
    public enum Square { case o, x, blank }
    
    public struct Index {
        let i: Int
        
        init?(_ i: Int) {
            guard (0...2).contains(i) else { return nil }
            self.i = i
        }
    }
    
    public struct Board {
        private var squares: [[Square]] = Array(repeating: Array(repeating: .blank, count: 3), count: 3)
        
        public subscript(row row: Index, column column: Index) -> Square {
            get { self.squares[row.i][column.i] }
            set { self.squares[row.i][column.i] = newValue }
        }
    }
}

extension TicTacToe.Board: CustomStringConvertible {
    var description: String {
        let line = String(repeating: "–", count: 11)
        return squares.map(describe(row:)).joined(separator: "\n" + line + "\n")
    }
    
    private func describe(row: [TicTacToe.Square]) -> String {
        return row.map {
            switch $0 {
                case .o: return " O "
                case .x: return " X "
                case .blank: return "   "
            }
        }.joined(separator: "|")
    }
}

var board = TicTacToe.Board()
board[row: TicTacToe.Index(0)!, column: TicTacToe.Index(1)!] = .o
board[row: TicTacToe.Index(1)!, column: TicTacToe.Index(2)!] = .x
print(board)

/*
   | O |   
–––––––––––
   |   | X 
–––––––––––
   |   |   
*/
1 Like

Ah ha! I learn many good ideas from you code. Thanks much for showing me this! Much appreciated.