Как динамически выбрать свойство структуры для передачи в качестве параметра в функции в swift 4?

#ios #swift #struct

#iOS #swift #структура

Вопрос:

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

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

 func checkContentsForMatching(forIndex: Int) {
    var cards = [SetCard]()
    for index in game.indicesOfChosenCards.indices {
        cards.append(cardTitles[testCards[game.indicesofChosenCards[index]]]!)


    }

    if (cards[0].color == cards[1].color) amp;amp; (cards[1].color == cards[2].color) amp;amp; (cards[0].color == cards[2].color) {
        game.playingCards[game.indicesOfChosenCards[0]].color = true
        game.playingCards[game.indicesOfChosenCards[1]].color = true
        game.playingCards[game.indicesOfChosenCards[2]].color = true

    }
    else if cards[0].color == cards[1].color {
        game.playingCards[game.indicesOfChosenCards[0]].color = true
        game.playingCards[game.indicesOfChosenCards[1]].color = true

    }
    else if cards[1].color == cards[2].color {
        game.playingCards[game.indicesOfChosenCards[1]].color = true
        game.playingCards[game.indicesOfChosenCards[2]].color = true

    }
    else if cards[0].color == cards[2].color {
        game.playingCards[game.indicesOfChosenCards[0]].color = true
        game.playingCards[game.indicesOfChosenCards[2]].color = true
    }

    if (cards[0].shape == cards[1].shape) amp;amp; (cards[1].shape == cards[2].shape) amp;amp; (cards[0].shape == cards[2].shape) {
        game.playingCards[game.indicesOfChosenCards[0]].shape = true
        game.playingCards[game.indicesOfChosenCards[1]].shape = true
        game.playingCards[game.indicesOfChosenCards[2]].shape = true
    }
    else if cards[0].shape == cards[1].shape {
        game.playingCards[game.indicesOfChosenCards[0]].shape = true
        game.playingCards[game.indicesOfChosenCards[1]].shape = true
    }
    else if cards[1].shape == cards[2].shape {
        game.playingCards[game.indicesOfChosenCards[1]].shape = true
        game.playingCards[game.indicesOfChosenCards[2]].shape = true
    }
    else if cards[0].shape == cards[2].shape {
        game.playingCards[game.indicesOfChosenCards[0]].shape = true
        game.playingCards[game.indicesOfChosenCards[2]].shape = true
    }
  

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

1. Является ли фактическая проблема реализацией наборов? Если да, возможно, вам следует спросить об этом, и я покажу вам, как это реализовать. В нынешнем виде это проблема x-y.

2. Да, это реализация наборов, которая была заданием программирования из CS193P. Я уже это реализовал. Я хотел сжать свой код в 300 строк, если не меньше.

3. Да, моя реализация довольно компактна (конечно, намного компактнее вашей). Существует довольно элегантный способ сделать это.

Ответ №1:

То, что вы ищете, — это быстрый ключевой путь.

В моей реализации Sets (Zotz!) Это то, что я делаю. Сначала я использую перечисления с исходными значениями Int для четырех атрибутов card. Это позволяет мне представлять любой атрибут как общий тип (Int). Я выражаю эти необработанные значения как вычисляемые свойства и предоставляю список всех четырех вычисляемых свойств в виде массива ключевых путей (обратите внимание, это Swift 5):

 public struct Card: Codable, CustomStringConvertible {
    
    let itsColor : Color
    let itsNumber : Number
    let itsShape : Shape
    let itsFill : Fill
    
    var itsColorRaw : Int { return itsColor.rawValue }
    var itsNumberRaw : Int { return itsNumber.rawValue }
    var itsShapeRaw : Int { return itsShape.rawValue }
    var itsFillRaw : Int { return itsFill.rawValue }
    public static let attributes : [KeyPath<Card, Int>] = [
        itsColorRaw, itsNumberRaw, itsShapeRaw, itsFillRaw
    ]
    // ...
}
  

Хорошо, теперь легко выразить правило, определяющее, является ли данная тройка карт допустимым совпадением:

 private func isCorrectCards(_ cards:[Card]) -> Bool {
    func evaluateOneAttribute(_ nn:[Int]) -> Bool {
        let allSame = (nn[0] == nn[1]) amp;amp; (nn[1] == nn[2])
        let allDiff = (nn[0] != nn[1]) amp;amp; (nn[1] != nn[2]) amp;amp; (nn[2] != nn[0])
        return allSame || allDiff
    }
    return Card.attributes.allSatisfy { att in
        evaluateOneAttribute(cards.map{$0[keyPath:att]})
    }
}
  

Еще более элегантная версия, предложенная @vacawama:

 private func isCorrect(cards:[Card]) -> Bool {
    return Card.attributes.allSatisfy { att in
        Set(cards.map{$0[keyPath:att]}).count != 2
    }
}
  

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

1. Ну, вы вдохновили меня обновить мой код, чтобы использовать keyPath и allSatisfy . Мой пересмотренный код стал еще короче и понятнее, чем был раньше!

2. Я занимаюсь кодированием уже три месяца, и это моя первая сборка приложения за три или четыре дня. Спасибо за предложение keypath и allsatisfy . Это, несомненно, сделает мой код более компактным.

3. Проверка набора с использованием набора Swift: Set(nn).count != 2

4. @vacawama Вау, круто! Конечно, потому что это 1, если все они одинаковые, и 3, если все они разные. Я могу выбросить evaluateOneAttribute и сделать все это как однострочный внутри allSatisfy блока.

5. Что ж, теперь мы показываем оба!

Ответ №2:

Не уверен, что вы пытаетесь сделать, но если вы пытаетесь обобщить if -s и else -s, я бы сделал что-то вроде этого:

 var allColorsMatch = cards[0].color == cards[1].color) amp;amp; (cards[1].color == cards[2].color) amp;amp; (cards[0].color == cards[2].color

var zeroAndOneMatch = cards[0].color == cards[1].color
var oneAndTwoMatch = cards[1].color == cards[2].color
var zeroAndTwoMatch = cards[0].color == cards[2].color

game.playingCards[game.indicesOfChosenCards[0]].color = allColorsMatch || zeroAndOneMatch || zeroAndTwoMatch
game.playingCards[game.indicesOfChosenCards[1]].color = allColorsMatch || zeroAndOneMatch || oneAndTwoMatch
game.playingCards[game.indicesOfChosenCards[2]].color = allColorsMatch || zeroAndTwoMatch || oneAndTwoMatch
  

Вот и все, теперь вам не нужны if -s или else -s для color , вы можете заменить color if-else блоки приведенным выше кодом.

Теперь вы можете спросить, как бы вы сделали то же самое для shape . shape подобный цвет также может быть true или false , насколько я вижу. Итак, вы можете захотеть обернуть приведенный выше код в функцию с тремя параметрами и выполнить те же операции.

Ответ №3:

Вы можете использовать функции более высокого порядка Swift для фильтрации вашего массива cards вместо использования цикла for in для запуска.

Вероятно, вам следует создать отдельные объекты модели, чтобы избежать необходимости выполнять вызовы вложенных индексов, такие как cardTitles[testCards[game.indicesOfChosenCards[index]]]! .

Кроме того, почему ваше свойство color в game.playingCards[game.indicesOfChosenCards[i]].color логическом значении? Кажется немного запутанным, что свойство с именем color возвращает значение true или false.

Попробуйте разбить свою функцию на несколько функций. Вы пытаетесь сделать слишком много в этом.