Как создать словарь, который может содержать что-либо в ключе? или весь возможный тип, который он способен удерживать

Я хочу создать Dictionary, который не ограничивает тип ключа (например, NSDictionary)

Итак, я попробовал

var dict = Dictionary<Any, Int>()

и

var dict = Dictionary<AnyObject, Int>()

в результате

error: type 'Any' does not conform to protocol 'Hashable'
var dict = Dictionary<Any, Int>()
           ^
<REPL>:5:12: error: cannot convert the expression type '<<error type>>' to type '$T1'
var dict = Dictionary<Any, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~

ОК, я буду использовать Hashable

var dict = Dictionary<Hashable, Int>()

но

error: type 'Hashable' does not conform to protocol 'Equatable'
var dict = Dictionary<Hashable, Int>()
           ^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
  func ==(lhs: Self, rhs: Self) -> Bool
       ^
Swift.Hashable:1:10: note: type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
         ^
<REPL>:5:12: error: cannot convert the expression type '<<error type>>' to type '$T1'
var dict = Dictionary<Hashable, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~~~~~~

Итак Hashable унаследовано от Equatable, но оно не соответствует Equatable??? Я не понимаю...

В любом случае, продолжайте пробовать

typealias KeyType = protocol<Hashable, Equatable> // KeyType is both Hashable and Equatable
var dict = Dictionary<KeyType, Int>() // now you happy?

не повезло

error: type 'KeyType' does not conform to protocol 'Equatable'
var dict = Dictionary<KeyType, Int>()
           ^
Swift.Equatable:2:8: note: '==' requirement refers to 'Self' type
  func ==(lhs: Self, rhs: Self) -> Bool
       ^
Swift.Hashable:1:10: note: type 'KeyType' does not conform to inherited protocol 'Equatable.Protocol'
protocol Hashable : Equatable
         ^
<REPL>:6:12: error: cannot convert the expression type '<<error type>>' to type '$T1'
var dict = Dictionary<KeyType, Int>()
           ^~~~~~~~~~~~~~~~~~~~~~~~~~

Я так потерялся, как я могу сделать компилятор счастливым с моим кодом?


Я хочу использовать словарь, например

var dict = Dictionary<Any, Int>()
dict[1] = 2
dict["key"] = 3
dict[SomeEnum.SomeValue] = 4

Я знаю, что могу использовать Dictionary<NSObject, Int>, но это не совсем то, что я хочу.

Ответ 1

Обновление Swift 3

Теперь вы можете использовать AnyHashable, который является стираемым хешируемым значением, созданным именно для таких сценариев:

var dict = Dictionary<AnyHashable, Int>()

Ответ 2

Я считаю, что с Swift 1.2 вы можете использовать для этого структуру ObjectIdentifier. Он реализует Hashable (и, следовательно, Equatable), а также Comparable. Вы можете использовать его для переноса любого экземпляра класса. Я предполагаю, что реализация использует обернутый объект, лежащий в основе адреса для hashValue, а также внутри оператора ==.

Ответ 3

Я взял на себя смелость перекрестной публикации/ссылки на этот вопрос на отдельном посте на форумах Apple Dev, и этот вопрос ответил здесь,

Изменить

Этот ответ из приведенной выше ссылки работает в 6.1 и выше:

struct AnyKey: Hashable {
    private let underlying: Any
    private let hashValueFunc: () -> Int
    private let equalityFunc: (Any) -> Bool

    init<T: Hashable>(_ key: T) {
        underlying = key
        // Capture the key hashability and equatability using closures.
        // The Key shares the hash of the underlying value.
        hashValueFunc = { key.hashValue }

        // The Key is equal to a Key of the same underlying type,
        // whose underlying value is "==" to ours.
        equalityFunc = {
            if let other = $0 as? T {
                return key == other
            }
            return false
        }
    }

    var hashValue: Int { return hashValueFunc() }
}

func ==(x: AnyKey, y: AnyKey) -> Bool {
    return x.equalityFunc(y.underlying)
}

Ответ 4

Словарь struct Dictionary<Key : Hashable, Value>... Это означает, что Value может быть любым, что вам нужно, и Key может быть любым типом, который вам нужен, но Key должен соответствовать протоколу Hashable.

Вы не можете создать Dictionary<Any, Int>() или Dictionary<AnyObject, Int>(), потому что Any и AnyObject не могут гарантировать, что такой ключ соответствует Hashable

Вы не можете создать Dictionary<Hashable, Int>(), потому что Hashable не тип, это просто протокол, который описывает необходимый тип.

Итак, Hashable унаследован от Equatable, но он не соответствует Equatable??? Я не понимаю...

Но вы ошибаетесь в терминологии. Исходная ошибка

type 'Hashable' does not conform to inherited protocol 'Equatable.Protocol' Это означает, что Xcode предполагает "Hashable" как некоторый тип, но такого типа нет. И Xcode рассматривает его как некий пустой тип, который явно не соответствует ни одному протоколу (в этом случае он не соответствует унаследованному протоколу Equatable)

Что-то подобное происходит с KeyType.

Объявление псевдонима типа вводит именованный псевдоним существующего типа в вашу программу.

Вы видите existing type. protocol<Hashable, Equatable> не является типом протокола, поэтому Xcode снова сообщает вам, что type 'KeyType' does not conform to protocol 'Equatable'

Вы можете просто использовать Dictionary<NSObject, Int>, потому что NSObject соответствует протоколу Hashable.

Swift - сильный язык ввода, и вы не можете делать такие вещи, как creating Dictionary that can hold anything in Key. На самом деле словарь уже поддерживает любое может содержать что-либо в Key, которое соответствует Hashable. Но так как вы должны указать конкретный класс, вы не можете сделать это для собственных классов Swift, потому что в Swift нет такого мастер-класса, как в Objective-C, который соответствует воздуху, может соответствовать (с помощью расширений) до Hashable

Конечно, вы можете использовать некоторую оболочку, такую ​​как chrisco. Но я действительно не могу представить, зачем вам это нужно. Замечательно, что у вас есть сильная печать в Swift, поэтому вам не нужно беспокоиться о типе кастинга, как вы это делали в Objective-C

Ответ 5

Hashable - это просто протокол, поэтому вы не можете указывать его напрямую как тип для значения Key. То, что вам действительно нужно, это способ выражения "любого типа T, так что T реализует Hashable. Это обрабатывается ограничениями типа в Swift:

func makeDict<T: Hashable>(arr: T[]) {
  let x = Dictionary<T, Int>()
}

Этот код компилируется.

AFAIK, вы можете использовать ограничения типов только для общих функций и классов.

Ответ 6

Это точно не отвечает на вопрос, но помогло мне.

Общий ответ - реализовать Hashable для всех ваших типов, однако для протоколов может быть сложно, поскольку Hashable расширяет Equatable и Equatable uses Self, который налагает серьезные ограничения на то, для чего может использоваться протокол.

Вместо этого выполните Printable, а затем выполните:

var dict = [String: Int]
dict[key.description] = 3

Реализация описания должна быть примерно такой:

var description : String {
    return "<TypeName>[\(<Field1>), \(<Field2>), ...]"
}

Не лучший ответ, но лучшее, что у меня есть до сих пор: (

Ответ 7

Это не отвечает на вопрос ОП, но имеет несколько взаимосвязь и, возможно, может быть полезным для некоторых ситуаций. Предположим, что вы действительно хотите это сделать:

   public var classTypeToClassNumber = [Any.Type : Int]()

Но Swift говорит вам: "Тип" Any.Type "не соответствует протоколу Hashable".

Большинство приведенных выше ответов касаются использования экземпляров объектов в качестве словарного ключа, а не типа объекта. (Что справедливо, о том, о чем спрашивал ОП.) Это был ответ Говарда Ловата, который привел меня к полезному решению.

public class ClassNumberVsClassType {

   public var classTypeToClassNumber = [String : Int]()

   public init() {

      classTypeToClassNumber[String(describing: ClassWithStringKey.self)] = 367622
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList3.self)] = 367629
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList2.self)] = 367626
      classTypeToClassNumber[String(describing: ClassWithGuidKey.self)] = 367623
      classTypeToClassNumber[String(describing: SimpleStruct.self)] = 367619
      classTypeToClassNumber[String(describing: TestData.self)] = 367627
      classTypeToClassNumber[String(describing: ETestEnum.self)] = 367617
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList0.self)] = 367624
      classTypeToClassNumber[String(describing: ClassBasedOnKeyedItemList1.self)] = 367625
      classTypeToClassNumber[String(describing: SimpleClass.self)] = 367620
      classTypeToClassNumber[String(describing: DerivedClass.self)] = 367621
   }

   public func findClassNumber(_ theType : Any.Type) -> Int {
      var s = String(describing: theType)
      if s.hasSuffix(".Type") {
         s = s.substring(to: s.index(s.endIndex, offsetBy: -5))  // Remove ".Type"
      }
      let classNumber = _classTypeToClassNumber[s]
      return classNumber != nil ? classNumber! : -1
   }
}

EDIT:

Если задействованные классы определены в разных модулях и могут иметь конфликтующие имена классов, если вы пренебрегаете именем модуля, тогда замените "String (reflecting:" для "String (описывая:" ), как при создании словаря, так и когда выполняя поиск.