Статьи

Swift 1.2: давайте поговорим о наборах, детка!


Одной из многих новых интересных функций 
Swift 1.2
 является введение нового типа 
Set
. Даже если вы никогда раньше не сталкивались с типом, вы, возможно, вручную закодировали функциональность набора, когда захотите, например, сравнить два массива, чтобы найти сходство между ними. Или вы могли использовать аналогичную функциональность, когда использовали объединения и объединения в SQL.


Наборы содержат неупорядоченные отдельные наборы объектов и позволяют реализовать теорию множеств по 
диаграммам Венна  . 

Давайте начнем с создания двух наборов целых чисел из массивов.
Один содержит нечетные числа, а другой — четные числа до десяти включительно:
    let odd = Set([1, 3, 3, 3, 5, 7, 9]) // {5, 7, 3, 1, 9}

    let even = Set([2, 2, 2, 2, 2, 2, 4, 6, 8, 8, 10]) // {6, 10, 2, 4, 8}

Первое, что вы заметите, это то, что наборы содержат различные значения: несмотря на то, что мой массив четных чисел содержит шесть экземпляров ‘2’, набор содержит только одно.

Чтобы увидеть, есть ли какое-либо совпадение между нечетным и четным, 
isDisjoint ()  возвращает 
логическое значение,  указывающее, что в обоих наборах нет членов:
    let oddDisjointWithEven = odd.isDisjointWith(even) // true

Чтобы объединить эти наборы вместе, мы будем использовать метод  union (),  чтобы дать нам новый   экземпляр Set с именем  allNumbers :

    let allNumbers = odd.union(even) // {10, 2, 9, 4, 5, 7, 6, 3, 1, 8}

Чтобы еще немного поработать, я создам свой собственный набор с именем  xyzzyNumbers :

    let xyzzyNumbers = Set([1, 2, 3, 4]) // {2, 3, 1, 4}

… Этот новый набор содержит некоторые нечетные числа, поэтому я ожидаю, что  isDisjoint ()  снова приведет к ложным числам:

    let xyzzyDisjointWithOdd = xyzzyNumbers.isDisjointWith(odd) // false

Чтобы получить нечетные числа в  xyzzy  ниже десяти, я могу использовать метод  intersect ()  :

    let xyzzyOddNumbers = xyzzyNumbers.intersect(odd) // {3, 1}

… и с четным:

    let xyzzyEvenNumbers = xyzzyNumbers.intersect(even) // {2, 4}

Метод is Subset ()  возвращает true, если один набор является подмножеством другого. xyzzy  не является подмножеством  нечетных,  потому что оно содержит четные числа, однако это подмножество всех  чисел :

    let xyzzyIsSubsetOfOdd = xyzzyNumbers.isSubsetOf(odd) // false
    let xyzzyIsSubSetOfAll = xyzzyNumbers.isSubsetOf(allNumbers) // true

… и это означает, что  allNumbers  — это расширенный набор  xyzzy :

let allIsSupersetOfXyzzy = allNumbers.isSupersetOf(xyzzyNumbers) // true

Я также могу вычесть один набор из другого. Здесь я удаляю все числа из  xyzzy  из моих нечетных чисел:

    let oddNumbersSubtractXyzzy = odd.subtract(xyzzyNumbers) // {5, 7, 9}

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

    let colors = Set(["red", "orange", "purple"]) // {"orange", "purple", "red"}

    let fruit = Set(["apple", "banana", "orange"]) // {"banana", "apple", "orange"}

    let fruityColors = colors.intersect(fruit) // {"orange"}

    let nonFruityColors = colors.exclusiveOr(fruit) // {"banana", "purple", "red", "apple"}

Но типы нельзя смешивать, поэтому это не скомпилирует:

    let evenColors = colors.intersect(even)

Приложение — Создание наборов из ваших собственных типов данных

Если вы хотите создать наборы своего собственного типа данных, вы должны убедиться, что ваша структура или класс соответствуют   протоколу Hashable , который расширяет   протокол Equatable . Это означает, что вам нужны ваши данные для реализации функции равенства (‘==’) и возможности возвращать хешированное значение.
Я создал простую  структуру Существо  с несколькими свойствами, которые описывают существо:

    let name: String
    let swims: Bool
    let flies: Bool
    let walks: Bool
    let legCount: Int

Для того, чтобы соответствовать  HashableСущество «s  значение хэш  возвращает целочисленное значение хэш -функции его  имя  свойства ( строка  уже  Hashable , так что построенный в) и добавляет увеличение полномочий десяти для трех булевых свойств и , наконец , добавляет число ног:

    var hashValue: Int
    {
        get
        {
            return name.hashValue + (swims ? 10 : 0) + (flies ? 100 : 0) + (walks ? 1000 : 0) + legCount
        }
    }

Чтобы соответствовать  Equatable,  мы реализуем функцию равенства (спасибо  Davide De Francesch i за то, что он указал на значение хеш-функции, это не хороший способ приравнять два объекта!):

    func == (lhs: Creature, rhs: Creature) -> Bool
    {
        return lhs.name == rhs.name && lhs.swims == rhs.swims && lhs.flies == rhs.flies && lhs.walks == rhs.walks && lhs.legCount == rhs.legCount

    }

The final Creature definition looks like:

    struct Creature: Hashable
    {
        init(name: String, swims: Bool, flies: Bool, walks: Bool, legCount: Int)
        {
            self.name = name
            self.swims = swims
            self.flies = flies
            self.walks = walks
            self.legCount = legCount
        }
    
        let name: String
        let swims: Bool
        let flies: Bool
        let walks: Bool
        let legCount: Int
    
        var hashValue: Int
        {
            get
            {
                return name.hashValue + (swims ? 10 : 0) + (flies ? 100 : 0) + (walks ? 1000 : 0) + legCount
            }
        }
    }

We can now create an array of a handful of different creatures:
…and then create some sets of creatures with different special skills:

    let ant = Creature(name: "Ant", swims: false, flies: false, walks: true, legCount: 6)
    let bumbleBee = Creature(name: "Bumble Bee", swims: false, flies: true, walks: false, legCount: 6)
    let cat = Creature(name: "Cat", swims: false, flies: false, walks: true, legCount: 4)
    let flyingFish = Creature(name: "Flying Fish", swims: true, flies: true, walks: false, legCount: 0)
    let human = Creature(name: "Human", swims: true, flies: false, walks: true, legCount: 2)
    let penguin = Creature(name: "Penguin", swims: true, flies: false, walks: true, legCount: 2)
    let swift = Creature(name: "Swift", swims: false, flies: true, walks: true, legCount: 2)

    let creaturesArray = [ant, ant, ant, bumbleBee, cat, flyingFish, human, penguin, swift]

    let swimmers = Set(creaturesArray.filter({ $0.swims }))
    let walkers = Set(creaturesArray.filter({ $0.walks }))
    let fliers = Set(creaturesArray.filter({ $0.flies }))

Using the Set syntax, we can find the creatures that can fly and walk:

    let flyingWalkers = fliers.intersect(walkers)
…being a 
Set, although there were three little ants in the source array, there’s only one in the set.

We can find creatures that can walk but can’t fly or swim by chaining two set methods together:

    let walkingNonSwimmersNonFliers = walkers.subtract(swimmers).subtract(fliers)

…or we can find the creatures that can either fly or swim:

    let fliersAndSwimmers = fliers.union(swimmers)

Again, although the flying fish can do both, because we’re dealing with sets, it only appears once.