Мой недавний пост в блоге, Zip, Map и Generics , рассматривал новую функцию Swift zip () для объединения двух массивов. zip возвращает SequenceType с таким количеством элементов, как его кратчайшая входная последовательность. Что если мы захотим создать пользовательскую функцию zip, которая будет возвращать массив той же длины, что и самый длинный ввод, и дополнит самое короткое значением nil ?
Функция будет работать примерно так:
    let arrayOne = [1, 2, 3, 4]
    let arrayTwo: [String?] = ["AAA", "BBB"]
    let result = longZip(arrayOne, arrayTwo) // expect [(1, "AAA"), (2, "BBB"), (3, nil), (4, nil)]
Например, если входные массивы являются необязательными строками, [String] , наш сжатый результат должен будет возвращать необязательные значения, [String?] , Чтобы учесть это заполнение. Поэтому при повторном использовании дженериков подпись longZip будет выглядеть так:
    func longZip(arrayOne:[T], arrayTwo: [U]) -> [(T?, U?)]
На этот раз давайте возьмем тестовый подход к разработке . Прежде чем писать какой-либо код, мы напишем тест. Я создал LongZipTests.swift, который содержит два тестовых массива:
    let arrayOne = [1, 2, 55, 90]
    let arrayTwo = ["AAA", "BBB"]
Мои тесты гарантируют, что количество выходных данных равно 4, затем зациклите выходной поток, убедившись, что выходные элементы соответствуют элементам ввода или равны нулю, если нет ввода:
    func testLongZip_v1()
    {
        let resultOne = longZip_v1(arrayOne, arrayTwo)
        commonAssertions(resultOne)
    }
    func commonAssertions(results: [(Int?, String?)])
    {
        XCTAssert(results.count == 4, "Count")
        
        for (idx: Int, result:(Int?, String?))  in enumerate(results)
        {
            if idx < arrayOne.count
            {
                XCTAssertEqual(result.0!, arrayOne[idx], "Array One Value")
            }
            else
            {
                XCTAssertNil(result.0, "Array One nil")
            }
            
            if idx < arrayTwo.count
            {
                XCTAssertEqual(result.1!, arrayTwo[idx], "Array Two Value")
            }
            else
            {
                XCTAssertNil(result.1, "Array Two nil")
            }
        }
    }
Первая реализация longZip () довольно проста: используйте max (), чтобы найти наибольшее количество, переберите оба массива с помощью цикла for и заполните возвращаемый объект элементами из этих массивов или с помощью nil, если мы превысили счет :
    func longZip_v1(arrayOne:[T], arrayTwo: [U]) -> [(T?, U?)]
    {
        let n = max(arrayOne.count, arrayTwo.count)
        
        var returnObjects = [(T?, U?)]()
        
        for var i = 0; i < n; i++
        {
            let returnObject: (T?, U?) = (i < arrayOne.count ? arrayOne[i] : nil, i < arrayTwo.count ? arrayTwo[i] : nil)
            
            returnObjects.append(returnObject)
        }
        
        return returnObjects
    }
Нажатие command-u выполняет тесты, которые, как правило, работают лучше, если ваши пальцы скрещены:
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testLongZip_v1]' started.
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testLongZip_v1]' passed (0.000 seconds).
Вы можете использовать debugDescription (), чтобы быть вдвойне уверенным:
    [(Optional(1), Optional("AAA")), (Optional(2), Optional("BBB")), (Optional(55), nil), (Optional(90), nil)]
Теперь у нас есть рабочая версия, пора разбирать код. Вторая версия использует map () для преобразования из необязательного в необязательное и extension () для добавления нулей, где это необходимо:
    func longZip_v2(arrayOne:[T], arrayTwo: [U]) -> [(T?, U?)]
    {
        var arrayOneExtended = arrayOne.map({$0 as T?})
        var arrayTwoExtended = arrayTwo.map({$0 as U?})
        
        arrayOneExtended.extend([T?](count: max(0, arrayTwo.count - arrayOne.count), repeatedValue: nil))
        arrayTwoExtended.extend([U?](count: max(0, arrayOne.count - arrayTwo.count), repeatedValue: nil))
        
        return Array(zip(arrayOneExtended, arrayTwoExtended))
    }
… добавлен новый тестовый пример:
    func testLongZip_v2()
    {
        let resultOne = longZip_v2(arrayOne, arrayTwo)
        
        commonAssertions(resultOne)
    }
Пальцы скрещены на обеих руках …
    func testLongZip_v2()
    {
        let resultOne = longZip_v2(arrayOne, arrayTwo)
        
        commonAssertions(resultOne)
    }
Уф!
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testLongZip_v2]' started.
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testLongZip_v2]' passed (0.000 seconds).
Вторая версия была хорошей, но есть дублированный код и переменные, где я всегда предпочитаю константы. К сожалению, возвращение map () является неизменным, поэтому, несмотря на все мои усилия, я не смог связать карту и продолжить вместе. Однако, переместив этот общий код в отдельную функцию extendWithNil () , я каким-то образом пошел на его устранение.
extendWithNil () принимает массив и целое число для желаемой новой длины и возвращает массив опций типа входного массива:
    func extendWithNil(array: [T], newCount: Int) -> [T?]
Опять же, прежде чем писать код, давайте напишем тест, который проверяет количество и то, что вновь добавленный элемент равен nil:
    func testExtendWithNil()
    {
        let array = ["AAA", "BBB"]
        let result = extendWithNil(array, 3)
        
        XCTAssert(resultOne.count == 3, "Count")
        XCTAssertNil(result[2], "Nil Added")
    }
Внутренности extendWithNil () взяты из версии два из longZip () :
    func extendWithNil(array: [T], newCount: Int) -> [T?]
    {
        var returnArray = array.map({$0 as T?})
        
        returnArray.extend([T?](count: max(0, newCount - array.count), repeatedValue: nil))
        
        return returnArray
    }
и, наконец, третья версия longZip () использует exteWithNil () для довольно аккуратной реализации:
    func longZip_v3(arrayOne:[T], arrayTwo: [U]) -> [(T?, U?)]
    {
        let newCount = max(arrayOne.count, arrayTwo.count)
        
        let arrayOneExtended = extendWithNil(arrayOne, newCount)
        let arrayTwoExtended = extendWithNil(arrayTwo, newCount)
        
        return Array(zip(arrayOneExtended, arrayTwoExtended))
    }
Перед празднованием с коробкой Сотовых Пальцев , заключительный тест:
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testExtendWithNil]' started.
[Optional("AAA"), Optional("BBB"), nil]
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testExtendWithNil]' passed (0.001 seconds).
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testLongZip_v3]' started.
    Test Case '-[FlexMonkeyExamplesTests.FlexMonkeyExamplesTests testLongZip_v3]' passed (0.001 seconds).
Я создал новый репозиторий для этих маленьких технических примеров, и вы можете найти весь исходный код этого поста здесь .