Как создать тип, который может содержать такие значения, как UInt, но также и дополнительное значение 00?

#swift

#swift

Вопрос:

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

У баскетболистов есть номера футболок. В некоторых европейских странах (как в случае с этим приложением) числа варьируются от 0 до 99 включительно. Но также доступно дополнительное значение 00, и это не то же самое, что 0.

Я могу использовать String для этого, но тогда String может хранить другие значения, и мне придется написать дополнительный код проверки.

На данный момент я использую UInt для хранения чисел.

Есть ли лучший способ хранить такие значения в виде типа Swift?

Комментарии:

1. Увеличьте диапазон от 0 до 100 и пусть 100 будет этим специальным числом. Затем добавьте некоторую логику при его отображении, чтобы 100 отображалось как 00

2. Было бы очень полезно, если бы мы могли видеть этот специальный «тип», который вы создаете. Как вы вообще ограничиваете числа диапазоном 0 … 99?

3. @matt на данный момент это UInt без какой-либо проверки верхней границы, поскольку это не так важно, я отредактировал вопрос

4. Итак, «создать тип» в заголовке вашего вопроса и в теле вопроса на самом деле не соответствует действительности? Это нормально, просто пытаюсь прояснить. Если вы хотите использовать обычный UInt, это нормально. Вы можете просто расширить его, чтобы «перевести» себя в строку так, как предлагает Йоаким Даниельсон.

5. @PhillipMills да, это правило в НБА, но, например, у ФИБА 4 … 15, а в некоторых европейских лигах вы можете использовать 00, 0 … 99

Ответ №1:

Это отличный вариант использования для пользовательского типа. Это определенно не UInt или какое-либо целое число. Является ли #23 # 24 = # 47? Это тарабарщина. Что такое # 99 * # 2? Опять же, тарабарщина. Это не целое число.

Просто чтобы сделать еще один шаг вперед, в то время как «00» является юридическим номером Джерси, «01» — нет. Это не просто то же самое, что и «1». Это недопустимое число. На самом деле, если это NCAA, большинство чисел не являются законными (см. Раздел 5 статья 5.b.2).

Вместо этого JerseyNumber является его собственным типом. У него есть свои собственные юридические операции (с технической точки зрения, у него своя собственная алгебра).

Наиболее разумным примитивным типом для JerseyNumber является String , поскольку вы хотите иметь возможность отличать 00 от 0. Я бы, вероятно, реализовал его таким образом, что даст вам Equatable, Hashable, CustomStringConvertible и более удобный Codable:

 struct JerseyNumber: Hashable {
    private var string: String

    static let legalNumbers = ["0", "1", "2", "3", "4", "5", "00", "10", "11", "12", "13", "14", "15", "20", "21", "22", "23", "24", "25", "30", "31", "32", "33", "34", "35", "40", "41", "42", "43", "44", "45", "50", "51", "52", "53", "54", "55"]

    init?(_ string: String) {
        guard JerseyNumber.legalNumbers.contains(string) else { return nil }
        self.string = string
    }
}

extension JerseyNumber: CustomStringConvertible {
    var description: String { string }
}

extension JerseyNumber: Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let value = try container.decode(String.self)
        guard let number = JerseyNumber(value) else {
            throw DecodingError.dataCorruptedError(in: container,
                                                   debugDescription: "Could not decode JerseyNumber: (value)")
        }
        self = number
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(string)
    }
}
  

Очевидно, что если это не NCAA, или если вам нужны другие правила для ваших чисел, вы можете переработать init , чтобы обеспечить это или не применять любым способом, который имеет смысл для вашей проблемы. Вы можете, например, проанализировать строку, а не иметь большой список допустимых значений. Это всего лишь пример. Но основной смысл заключается в том, чтобы использовать для этого пользовательский тип, а не UInt.

Комментарии:

1.Это то решение, о котором я думал. Мысли: может быть, какая-то инъекция зависимостей (я ненавижу этот термин) может быть использована для реализации европейского vs. Правила нумерации US. Также я думаю, вам следует показать, как генерировать legalNumbers правило by , т. Е. Используя код, который выражает, каковы правила, а не явно произвольный, подверженный ошибкам список значений вручную.

2. В случае правил NCAA я бы сказал, что этот подход наименее подвержен ошибкам, поскольку он копируется непосредственно из правил (которые перечисляют точные допустимые числа). Вероятна ошибка при разработке алгоритма, который соответствует этим числам, а не просто перечисляет правило, как указано. Но, конечно, в других средах может быть полезно создать общую форму, например struct JerseyNumber<Rule: RuleSet> , где RuleSet был предоставлен протокол static func isValid(string: String) -> Bool .

3. Большой компромисс в том, чтобы сделать это более настраиваемым, заключается в том, что это затрудняет сериализацию. Если вы знаете, что в вашей программе существует только одна система правил, вы можете кодировать числа как просто строку. Но если могут быть другие правила, вам может потребоваться закодировать их тип при сериализации. Я бы подождал, пока в этом не возникнет реальная необходимость, прежде чем внедрять его.

4. Это выглядит хорошо @RobNapier, спасибо. Я также вижу, как его можно легко адаптировать к другим правилам нумерации, следуя предложению @matt создать legalNumbers и сделать его более тестируемым. Я отмечу ваш ответ как принятый.

5. Почему не набор вместо массива? Похоже, порядок не имеет значения.

Ответ №2:

Вы могли бы использовать перечисление, например:

 enum JerseyNumber {
   case jersey(UInt)
   case special //You could call this doubleZero
}
  

Это позволит вам сохранить номера Джерси и этот специальный номер 00.

Комментарии:

1. Как это решает проблему ограничения диапазона UInt?

2. Говорит ли это, что числа должны быть ограничены? В вопросе упоминается диапазон, но на самом деле вопрос заключается в том, как сохранить эти значения.

3. @matt ограничение диапазона Uint не было проблемой, о которой я беспокоился, в основном это касалось способности различать 0 и 00

Ответ №3:

Вы могли бы определить строковый тип enum и явно перечислить допустимые значения:

 enum JerseyNumber: String {
    case jersey00 = "00"
    case jersey0 = "0"
    case jersey1 = "1"
    case jersey2 = "2"
    case jersey3 = "3"
    case jersey4 = "4"
    case jersey5 = "5"
    //...
    case jersey99 = "99"
    case jersey100 = "100"
}
  

Затем, чтобы получить строковое значение JerseyNumber:

 print (JerseyNumber.jersey00.rawValue)
  

(Выводит «00»)

И создать JerseyNumber из строки:

 if let aJersey = JerseyNumber(rawValue: "5") {
    print(aJersey)
}
  

(выводит «jersey5» {Имя перечисления, а не его значение rawValue} )

Комментарии:

1. На данный момент я действительно не понимаю, почему это лучше, чем проверяющая оболочка для строки.

2. Это позволяет вам проверять, разрешено ли заданное значение, это позволяет вам использовать исчерпывающие инструкции switch . Почему бы не опубликовать пример проверяющей оболочки в строке? (Не совсем уверен, что вы имеете в виду, поэтому я хотел бы это увидеть.)

3. Я думаю, что мой ответ — это то, о чем говорит @matt.

4. Понял. Я вроде понял, что вы говорите о пользовательском типе.

Ответ №4:

Вот несколько иное решение, использующее оболочку свойств

 @propertyWrapper struct JerseyNumber {
    var wrappedValue: Int?

    init(_ number: Int) {
        guard (0...100).contains(number) else {
            wrappedValue = nil
            return
        }
        wrappedValue = number
    }

    var projectedValue: String {
        switch wrappedValue {
        case nil: return "not set"
        case 100: return "00"
        default:
            return String(wrappedValue!)
        }
    }
}

    struct Player {
    let name: String
    @JerseyNumber var number: Int?

    var description: String {
        "(name) ($number)"
    }
}

var team = [
    Player(name: "A", number: JerseyNumber(10)),
    Player(name: "B", number: JerseyNumber(15)),
    Player(name: "C", number: JerseyNumber(3)),
    Player(name: "D", number: JerseyNumber(100)),
    Player(name: "E", number: JerseyNumber(1000)),
]

team.forEach { print($0.name, $0.$number) }