Подтвердить что ты не робот

В Swift я могу использовать кортеж в качестве ключа в словаре?

Мне интересно, могу ли я как-то использовать пару x, y в качестве ключа к моему словарю

let activeSquares = Dictionary <(x: Int, y: Int), SKShapeNode>()

Но я получаю ошибку:

Cannot convert the expression type '<<error type>>' to type '$T1'

и ошибка:

Type '(x: Int, y: Int)?' does not conform to protocol 'Hashable'

Итак, как мы можем это сделать?

4b9b3361

Ответ 1

Определение для Dictionary равно struct Dictionary<KeyType : Hashable, ValueType> : ..., то есть тип ключа должен соответствовать протоколу Hashable. Но руководство по языку говорит нам, что протоколы могут быть приняты классами, структурами и перечислениями, т.е. Не по кортежам. Поэтому кортежи не могут использоваться как клавиши Dictionary.

Обходным решением будет определение типа хешируемой структуры, содержащего два входа (или все, что вы хотите поместить в свой кортеж).

Ответ 2

Как указано в ответе выше, это невозможно. Но вы можете обернуть кортеж в общую структуру с помощью протокола Hashable в качестве обходного пути:

struct Two<T:Hashable,U:Hashable> : Hashable {
  let values : (T, U)

  var hashValue : Int {
      get {
          let (a,b) = values
          return a.hashValue &* 31 &+ b.hashValue
      }
  }
}

// comparison function for conforming to Equatable protocol
func ==<T:Hashable,U:Hashable>(lhs: Two<T,U>, rhs: Two<T,U>) -> Bool {
  return lhs.values == rhs.values
}

// usage:
let pair = Two(values:("C","D"))
var pairMap = Dictionary<Two<String,String>,String>()
pairMap[pair] = "A"

Ответ 3

Я создал этот код в приложении:

struct Point2D: Hashable{
    var x : CGFloat = 0.0
    var y : CGFloat = 0.0

    var hashValue: Int {
        return "(\(x),\(y))".hashValue
    }

    static func == (lhs: Point2D, rhs: Point2D) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}

struct Point3D: Hashable{
    var x : CGFloat = 0.0
    var y : CGFloat = 0.0
    var z : CGFloat = 0.0

    var hashValue: Int {
        return "(\(x),\(y),\(z))".hashValue
    }

    static func == (lhs: Point3D, rhs: Point3D) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z
    }

}

var map : [Point2D : Point3D] = [:]
map.updateValue(Point3D(x: 10.0, y: 20.0,z:0), forKey: Point2D(x: 10.0, 
y: 20.0))
let p = map[Point2D(x: 10.0, y: 20.0)]!

Ответ 4

Если вы не против немного неэффективности, вы можете легко преобразовать свой кортеж в строку, а затем использовать его для словарного ключа...

var dict = Dictionary<String, SKShapeNode>() 

let tup = (3,4)
let key:String = "\(tup)"
dict[key] = ...

Ответ 5

Я предлагаю реализовать структуру и использовать решение, подобное boost::hash_combine.

Вот что я использую:

struct Point2: Hashable {

    var x:Double
    var y:Double

    public var hashValue: Int {
        var seed = UInt(0)
        hash_combine(seed: &seed, value: UInt(bitPattern: x.hashValue))
        hash_combine(seed: &seed, value: UInt(bitPattern: y.hashValue))
        return Int(bitPattern: seed)
    }

    static func ==(lhs: Point2, rhs: Point2) -> Bool {
        return lhs.x == rhs.x && lhs.y == rhs.y
    }
}

func hash_combine(seed: inout UInt, value: UInt) {
    let tmp = value &+ 0x9e3779b97f4a7c15 &+ (seed << 6) &+ (seed >> 2)
    seed ^= tmp
}

Это намного быстрее, чем использование строки для хэш-значения.

Если вы хотите узнать больше о магическом номере.

Ответ 6

К сожалению, с Swift 4.2 стандартная библиотека по-прежнему не обеспечивает условного соответствия Hashable для кортежей, и это не считается допустимым кодом компилятором:

extension (T1, T2): Hashable where T1: Hashable, T2: Hashable {
  // potential generic 'Hashable' implementation here..
}

Кроме того, структуры, классы и перечисления с кортежами в качестве полей не Hashable автоматически получать Hashable.

В то время как другие ответы предполагали использование массивов вместо кортежей, это могло бы привести к неэффективности. Кортеж - очень простая структура, которую можно легко оптимизировать из-за того, что количество и типы элементов известны во время компиляции. Экземпляр Array почти всегда предопределяет более непрерывную память для размещения потенциальных элементов, которые необходимо добавить. Кроме того, использование типа Array заставляет вас либо делать типы кортежей одинаковыми, либо использовать стирание типа. То есть, если вас не волнует неэффективность (Int, Int) можно сохранить в [Int], но (String, Int) потребуется что-то вроде [Any].

Обходной путь, который я нашел, основан на том факте, что Hashable делает синтез автоматически для полей, хранящихся отдельно, поэтому этот код работает даже без добавления Hashable и Equatable реализаций, таких как Marek Gregor:

struct Pair<T: Hashable, U: Hashable>: Hashable {
  let first: T
  let second: U
}

Ответ 7

Или просто используйте Массивы. Я пытался сделать следующий код:

let parsed:Dictionary<(Duration, Duration), [ValveSpan]> = Dictionary(grouping: cut) { span in (span.begin, span.end) }

Который привел меня на этот пост. После прочтения этих и разочарования (потому что, если они могут синтезировать Equatable и Hashable, просто приняв протокол, ничего не делая, они должны иметь возможность сделать это для кортежей, нет?), Я вдруг понял, просто используйте Arrays. Не знаю, насколько это эффективно, но это изменение работает очень хорошо:

let parsed:Dictionary<[Duration], [ValveSpan]> = Dictionary(grouping: cut) { span in [span.begin, span.end] }

Мой более общий вопрос становится "так почему же не являются кортежами, когда строятся первые классы, такие как массивы? Python отключил его (утка и бег)".