3

So, I’ve been looking for a solution to this problem for about 2 days. I’ve rummaged the Swift documentation, tried many different google / stack overflow searches, and just straight up randomly wrote code to no avail. I think part of the problem is that I don’t know how to word it... so sorry is this is confusing, but hopefully the code I attached clears it up.

Anyways, I want to know if it’s possible to have a private field in an enum such that each enum value has its own value for this field. I am not asking about extending another class, and I’m not asking about associated values. Unfortunately, I have yet to come across anything online saying whether this is allowed in Swift, and so, how to do it. To highlight my question, I’m trying to recreate the following Java code in Swift:

// Java

public enum Direction {

  LEFT(0, -1), RIGHT(0, 1), UP(-1, 0), DOWN(1, 0);

  private final int rowChange, colChange;

  Direction(int rowChange, int colChange) {
    this.rowChange = rowChange;
    this.colChange = colChange;
  }

  public int rowChange() {
    return this.rowChange;
  }

  public int colChange() {
    return this.colChange;
  }

}

This is what I have at the moment, which works, but I would rather have it have stored values rather than switching through each possible case.

// Swift

public enum Direction {

  case left, right, up, down

  public func rowChange() -> Int {
    switch self {
    case .left:
      return 0
    case .right:
      return 0
    case .up:
      return -1
    case .down:
      return 1
    }
  }

  public func colChange() -> Int {
    switch self {
    case .left:
      return -1
    case .right:
      return 1
    case .up:
      return 0
    case .down:
      return 0
    }
  }

}
  • Unrelated note: You should use `var column: Int { ... }` instead of `func colChange() -> Int { ... }`. Since it's a O(1) operation, the de-facto way of writing it in Swift is to use properties, and not functions. – Claus Jørgensen Jun 02 '19 at 21:16
  • Possible duplicate of [Why do enums have computed properties but not stored properties in Swift?](https://stackoverflow.com/questions/32278305/why-do-enums-have-computed-properties-but-not-stored-properties-in-swift) – Guilherme Matuella Jun 02 '19 at 21:54
  • 1
    Thank you everyone for the feedback - I’m still relatively new to Swift, so I appreciate the help. I’ll be switching them to computed properties... It’s a little weird that Swift doesn’t allow something I use quite often in Java, but I guess the switch works just as fine. – user11591069 Jun 02 '19 at 22:17

4 Answers4

3

You can conform to RawRepresentable and use (Int, Int) as Self.RawValue:

enum Direction : RawRepresentable {
    case left, right, up, down

    var rawValue : (Int, Int) {
        switch self {
        case .left: return (0, -1)
        case .right: return (0, 1)
        case .up: return (-1, 0)
        case .down: return (1, 0)
        }
    }

    init?(rawValue: (Int, Int)) {
        switch rawValue {
        case (0, -1): self = .left
        case (0, 1): self = .right
        case (-1, 0): self = .up
        case (1, 0): self = .down
        case (_, _): return nil
        }
    }

    var rowChange : Int { return self.rawValue.0 }
    var colChange : Int { return self.rawValue.1 }
}

Then you could use it like this:

print(Direction.left.rawValue) // --> (0, -1)
print(Direction.left.rowChange) // --> 0
print(Direction.left.colChange) // --> -1
LoVo
  • 1,856
  • 19
  • 21
2

Switching on self is the standard way. You can have a single value assigned to a enum by letting it inherit from Int (or String is another common case). But for two values like this, the switch would be the normal way.

Maybe consider using a struct instead? Something like this:

struct Direction: Equatable {

    let row: Int
    let column: Int

    static let left = Direction(row: 0, column: -1)
    static let right = Direction(row: 0, column: 1)
    static let up = Direction(row: -1, column: 0)
    static let down = Direction(row: 1, column: 0)

    private init() { 
        // Just to disable empty initialisation, not actually used.
        self.row = 0
        self.column = 0
    }

    private init(row: Int, column: Int) {
        self.row = row
        self.column = column
    }
}

And it can be accessed like this:

let direction = Direction.left
let row = direction.row
let column = direction.column

Also, if the goal is to use pattern matching, it's possible when the struct inherits from Equatable allowing you to write something like:

switch direction {
case .left:
    break
case .right:
    break
case .up:
    break
case .down:
    break
default:
    break
}
Claus Jørgensen
  • 25,882
  • 9
  • 87
  • 150
  • 1
    The problem with the `struct` approach is that you can create other directions with other values. You should at least make the `init` private so only the 4 statics are visible. And make the two properties public read-only. – rmaddy Jun 02 '19 at 21:30
  • @rmaddy good point about the private init, but the column/row properties are already read-only, so I'm not sure what you mean. Anyway, I'm merely offering OP an alternative to the switch statement. – Claus Jørgensen Jun 02 '19 at 21:34
  • Never mind on the two properties. I briefly forgot they are not writable due to being `let`. – rmaddy Jun 02 '19 at 21:38
  • Hm, very interesting - I hadn’t even considered the struct approach. Thank you for your input! – user11591069 Jun 02 '19 at 22:14
2

Your solution is pretty much the right idea, I would just avoid the primitive obsession and return a (dX: Int, dY: Int) tuple, or maybe even something like a PointOffset struct. I would also use a computed property, instead of a parameterless method.

public enum Direction {

    case left, right, up, down

    public var offset: (dX: Int, dY: Int) {
        switch self {
        case .left:  return (dX: -1,  dY:  0)
        case .right: return (dX: +1,  dY:  0)
        case .up:    return (dX:  0,  dY: +1)
        case .down:  return (dX:  0,  dY: -1)
        }
    }
}

If you ever envision that you'll need directions that aren't single units of distance, then I would recommend switching from an enum to a struct, and doing something like this:

public struct PointOffset {
    public var dX, dY: Int

    static func left (by distance: Int) { return self.init(dX: -distance, dY: 0) }
    static func right(by distance: Int) { return self.init(dX: +distance, dY: 0) }
    static func up   (by distance: Int) { return self.init(dX: 0, dY: +distance) }
    static func down (by distance: Int) { return self.init(dX: 0, dY: -distance) }
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
1

Using a switch on self will guarantee you are returning a valid value for each case in your enum, and it will protect you if you ever add a new value to the enum because then your code won't compile until you add a case for the new value. Don't use a func, use a property:

public enum Direction {

    case left, right, up, down

    var rowChange: Int {
        switch self {
        case .left: return 0
        case .right: return 0
        case .up: return -1
        case .down: return 1
        }
    }

    var colChange: Int {
        switch self {
        case .left: return -1
        case .right: return 1
        case .up: return 0
        case .down: return 0
        }
    }

}

Example of calling it:

let list: [Direction] = [.left, .right, .up, .down]
for e in list {
    print("ROW changed = \(e.rowChange)")
    print("COL changed = \(e.colChange)")
}
N.W
  • 672
  • 2
  • 8
  • 19