Making an array type

I'm trying to create a type similar to Array<U>, except that it's indexed by a type T other than Int. To reduce the complexity of the (envisioned) implementation, I planned to use an underlying ("sparse" array) [U?] variable, and to require T to be RawRepresentible so that its raw values can be used to index the underlying array. This should work well with the intended use case, where T is an 'enum E: Int' type whose cases are sequentially assigned, more or less.


However, I can't find a way of writing the type so that it satisfies CollectionType. I don't know how to fix the compiler errors in the following attempted implementation, and it's made triply hard by the fact that this code pretty much crashes SourceKit and the compiler itself (with a segmentation fault: 11). I'm open to any suggestions about how to make this compile, or alternate techniques to achieve approximately the same effect.


public struct EnumeratedArrayGenerator<T: RawRepresentable, U>: GeneratorType {
  public mutating func next () -> U? {
       while index < array.count {
            if let element = array [index] { return element }
       }
       return nil
  }
  private var array: [U?]
  private var index = 0
  private init (array: [U?]) {
       self.array = array
  }
}
public struct EnumeratedArray<T: RawRepresentable, U where T.RawValue == Int>: SequenceType, CollectionType {
  private var array: [U?] = []
  public subscript (position: T) -> U {
       return array [position.rawValue]!
  }
  public var isEmpty: Bool {
       return count == 0
  }
  public var count: Int {
       return array.filter { $0 != nil }.count
  }
  public func generate () -> EnumeratedArrayGenerator<T, U> {
       return EnumeratedArrayGenerator<T, U> (array: array)
  }
}
Answered by frogcjn in 19756022
public var startIndex: Fit<T> {
       return Fit<T> (rawValue: 0, count: array.count)
  }
  public var endIndex: Fit<T> {
       return Fit (rawValue: nil, count: array.count)
  }


and because your index is the same count as your native array, not the same count of your collectionType, you should use array.count instead of count.

You need these things:

protocol CollectionType : SequenceType {
    typealias Index : ForwardIndexType
    var startIndex: Self.Index { get }
    var endIndex: Self.Index { get }
    subscript (position: Self.Index) -> Self.Generator.Element { get }
}


These four requirements are infered by SequenceType and extensions of CollectionType.

(Index type can be infered by other, you do not need explicitly anounce the Index type.)

  • You donot need to create a generator, because the extension of CollectionType automaticlly create a IndexingGenerator for you.
  • You just need tell the system what is the startIndex, and the endIndex, and give the element represented by each index.
  • You can use Int as index, but you can also create a custom index conform to ForwardIndexType.

Due to your request

You can use a normal array to enumerate none nil element


It is not neccessary to create your own array.


let array = [1,nil,3] as [Int?]
for case let element? in array {
     print(element)
}

The idea is that clients of this type aren't aware that there is an array with nil elements — the idea is that it appears to clients to be indexed by (say) an enum. The nil elements were just a detail of the proposed implementation.


However, the whole approach doesn't really work, because there's nothing valid to return from 'endIndex' if the index type is an enum. It's undesirable to try to fix that by implementing the generator position as an Int, because that produces an Int-based subscript method and the whole point of the exercise is to prevent the "array" from being indexed by the wrong kind of index.


Actually, the syntax I'm looking for is really Dictionary syntax, except that the result of subscripting is optional, and that feels like a code smell in the client:


enum Direction { case North, South, East, West }
let compassPoints: Dictionary<Direction,String>  = [.North: "north", .South: "south", .East: "east", .West: "west"]
let compassPoint = compassPoints [direction]!


The dictionary lookup in line 3 can never really fail, so it's annoying (and misleading to readers of the code) to have to write the "!". Basically, I'm looking for a way to avoid that.

public struct EnumeratedArrayGenerator<T, U  where T: RawRepresentable, T: ForwardIndexType, T.RawValue == Int> : GeneratorType {
    private var array: [U?]
    private init (array: [U?]) {
        self.array = array
    }
    public mutating func next () -> U? {
        while array.count > 0 {
            if let element = array.removeAtIndex(0){
                return element
            }
        }
        return nil
    }
}
public struct EnumeratedArray<T, U where T: RawRepresentable, T: ForwardIndexType, T.RawValue == Int> : CollectionType, ArrayLiteralConvertible{

    private var array: [U?]

    public init(_ elements: [U?]){
        array = elements
    }

    public init(arrayLiteral elements: U?...){
        self = EnumeratedArray(elements)
    }

    public var startIndex: T {
        return T(rawValue: 0)!
    }
    public var endIndex: T {
        return T(rawValue: array.count)!
    }
    public subscript (position: T) -> U {
        return array[position.rawValue]!
    }
    public var count: Int {
        return array.filter { $0 != nil }.count
    }
    public func generate () -> EnumeratedArrayGenerator<T, U> {
        return EnumeratedArrayGenerator<T, U> (array: array)
    }
}
enum EnumIndex: Int, ForwardIndexType{
    case E0, E1, E2, End
    func successor() -> EnumIndex{
        return EnumIndex(rawValue: rawValue+1)!
    }
}
let a = [0,nil,2] as EnumeratedArray<EnumIndex, Int>

print(a[.E0]) // 0
// print(a[.E1]) // nil, don't uncomment
print(a[.E2]) // 2
print(a.count) // 2

let b = [nil] as EnumeratedArray<EnumIndex, Int>
print(b.isEmpty) // true
public extension Dictionary{
    public func valueOf(key: Key) -> Value {
        return self[key]!
    }
}


This will made the ! disappear. But you need to use .valueOf instead of subscripting.

Yes, I got to both those places. 🙂


a. Adding an extra ".End" case means that every switch statement has to have an explicit case for it, or a default.


b. Using a 'valueOf' method doesn't seem much of an improvement over "!".


I'm currently trying to figure out a generic type (on T with a RawRepresentable/Int restriction as before) whose value is a T? (so it uses nil as the "extra" case) and conforms to ForwardIndexType:


public struct Fit<T: RawRepresentable where T.RawValue == Int> {
  let value: T?
  init (value: Int?) {
       if let value = value {
            self.value = T (rawValue: value) }
       else {
            self.value = nil }
  }
}


but I haven't yet found a way to plug it into EnumeratedArray's Index type without getting compile errors. So far I have this:


public struct EnumeratedArray<T: RawRepresentable, U where T.RawValue == Int>: SequenceType, CollectionType, DictionaryLiteralConvertible {
private var array: [U?] = []
  public subscript (position: Fit<T>) -> U {
  get { return array [position.value!.rawValue]! }
  set { array [position.value!.rawValue] = newValue }
  }
  public var isEmpty: Bool {
  return count == 0
  }
  public var count: Int {
  return array.filter { $0 != nil }.count
  }
  public var startIndex: Fit<T> {
  return Fit<T> (value: 0)
  }
  public var endIndex: Fit<T> {
  return Fit (value: nil)
  }
  public func generate () -> EnumeratedArrayGenerator<T, U> {
  return EnumeratedArrayGenerator<T, U> (array: array)
  }
}


but I'm not sure how to fix the compile error.


(Also I think there should be a conformance error for Fit, since it has no successor method yet, but the error hasn't shown up. The successor method would use T's raw values to iterate through all possible T's, if my concept works.)


Edit: Doh! Forgot to add the ForwardIndexType conformance.

Since there are so many pains using custom index type, you just use Int as index type, and you do not need to create custom IndexType and GeneratorType.

If you want to a subscript of enum, you just add enum subscript aside of indexing subscript.


public struct EnumeratedArray<T, U where T: RawRepresentable, T.RawValue == Int> : CollectionType, ArrayLiteralConvertible{

    private var array: [U?]

    public init(_ elements: [U?]){
        array = elements
    }

    public init(arrayLiteral elements: U?...){
        self = EnumeratedArray(elements)
    }

    public var startIndex: Int {
        return 0
    }
    public var endIndex: Int {
        return array.count
    }
    public subscript (position: Int) -> U {
        return array[position]!
    }

    public subscript (position: T) -> U {
        return array[position.rawValue]!
    }

    public var count: Int {
        return array.filter { $0 != nil }.count
    }
}
enum Direction: Int { case North, South, East, West }
let compassPoints = ["north",  "south",  "east", "west"] as EnumeratedArray<Direction, String>
let compassPoint = compassPoints[.North]

Again, I'm trying to disallow the Int subscript, because in the actual application there's a danger of confusing the enum value with an Int ordering of the enum values that isn't related to their raw values. However, it might be acceptable just to put a 'fatalError()' in the Int subscript accessors, thereby "disallowing" them without using a custom index.


FWIW, the following implementation of a custom index was tested and seems to work:


public struct Fit<T: RawRepresentable where T.RawValue == Int, T: Equatable>: ForwardIndexType {
  let value: T?
  let count: Int
  init (rawValue: Int?, count: Int) {
       self.count = count
       if let rawValue = rawValue {
            self.value = Fit.resolvedT (rawValue, count: count) }
       else {
            self.value = nil
       }
  }
  private static func resolvedT (var rawValue: Int, count: Int) -> T? {
       while rawValue < count {
            if let t = T (rawValue: rawValue) {
                 return t
            }
            rawValue++
       }
       return nil
  }
  public func successor () -> Fit {
       if let rawValue = value?.rawValue {
            return Fit (rawValue: rawValue + 1, count: self.count)
       }
       else {
            return Fit (rawValue: nil, count: self.count)
       }
  }
}
public func ==<T> (lhs: Fit<T>, rhs: Fit<T>) -> Bool {
  return lhs.value == rhs.value
}
public struct EnumeratedArray<T: RawRepresentable, U where T.RawValue == Int, T: Equatable>: SequenceType, CollectionType, DictionaryLiteralConvertible {

  private var array: [U?] = []
  public subscript (position: Fit<T>) -> U {
       get { return array [position.value!.rawValue]! }
  }
  public subscript (index: T) -> U {
       get { return array [index.rawValue]! }
  }
  public init (dictionaryLiteral elements: (T, U)...) {
       var count = 0
       for (t, _) in elements {
            count = max (count, t.rawValue + 1)
       }
       array = [U?] (count: count, repeatedValue: nil)
       for (t, u) in elements {
            array [t.rawValue] = u
       }
  }
  public var isEmpty: Bool {
       return count == 0
  }
  public var count: Int {
       return array.filter { $0 != nil }.count
  }
  public var startIndex: Fit<T> {
       return Fit<T> (rawValue: 0, count: count)
  }
  public var endIndex: Fit<T> {
       return Fit (rawValue: nil, count: count)
  }
  public func generate () -> EnumeratedArrayGenerator<T, U> {
       return EnumeratedArrayGenerator<T, U> (array: array)
  }
}


Tested with:


  enum Direction: Int {
       case North, South, East, West
  }

  var labels: EnumeratedArray<Direction, String> = [.North: "north", .South: "south", .East: "east", .West: "west"]

  let n = labels [.North]
  print (n)

  for label in labels {
       print (label)
  }


Many thanks for your help!

"I'm trying to disallow the Int subscript"


This requirement seems not neccessary.

Accepted Answer
public var startIndex: Fit<T> {
       return Fit<T> (rawValue: 0, count: array.count)
  }
  public var endIndex: Fit<T> {
       return Fit (rawValue: nil, count: array.count)
  }


and because your index is the same count as your native array, not the same count of your collectionType, you should use array.count instead of count.

Have you considered doing something like this?


enum Direction: Int {
    case North, South, East, West
}


extension Direction {
   
    private static let d_labels: [Direction : String] = [.North : "north", .South: "south", .East: "east", .West: "west"]
   
    var compassPoint: String {return Direction.d_labels[Direction(rawValue: rawValue)!]!}
}


let heading = Direction.North
let label = heading.compassPoint

Unfortunately, the relationship between the indexes and the values isn't fixed.


Maybe it'll be clearer if I explain the Obj-C construct I'm converting. The old code has a large number of C-style arrays, some as ivars, some as local variables. They're indexed by integers, but in most cases the indexes are cases of a number of enums. Something like:


typedef enum: NSUInteger { CompassPointNorth = 1, CompassPointSouth, CompassPointEast, CompassPointWest, CompassPointCount } CompassPoint;

MyDirection* directions [CompassPoint];

for (i = 0; i < CompassPointCount; i++)

directions [i] = [[MyDirection alloc] init… ];


and so on.


I can use Swift arrays and enums instead, but then I have to use '.rawValue' everywhere to index the array, and there's a big danger of using the wrong enum in any given case, since converting to an Int basically discards type checking. I can use Swift dictionaries instead, with an enum-valued key, and I get strict type checking, but then I have to use "!" on every subscripting operation — and there's no "natural" order to enumerations.


So all of the exploration in the earlier part of this thread is an attempt to merge Array and Dictionary by brute force. But if there's another approach that provides all of the functionality (as I use the newly defined type, I keep finding more initializers and methods from Array or Dictionary that are needed in particular cases), then I'd be very glad to hear it.

Making an array type
 
 
Q