Почему метод map возвращает пустой массив в моей функции accumulate на Swift?

Вопрос или проблема

В книге ObcIO, ADVANCED SWIFT, приводится следующий пример:


var numbers = [1,2,3,4,5]

    extension Array {
        func accumulate(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] {
            var running = initialResult
            return map { next in
                running = nextPartialResult(running, next)
                print("что угодно")
                print("running = \(running)")
                return running
            }
        }
    }
    
    
    let runningTotals = numbers.accumulate(0) { $0 + $1 }
    print(" accumulate \(runningTotals)")

…но код в замыкании никогда не выполняется.

Существует версия, которая использует reduce и работает:

extension Array {
    func accumulate(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] {
        var running = initialResult
        return self.reduce(into: []) { result, next in
            running = nextPartialResult(running, next)
            result.append(running)
        }
    }
}

и версия, которая просто использует цикл for, и она работает:

extension Array {
    func accumulate(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] {
        var result: [Result] = []
        var running = initialResult
        for element in self {
            running = nextPartialResult(running, element)
            result.append(running)
        }
        return result
    }
}

…но мне действительно хотелось бы знать, что я делаю не так с map.

Ответ или решение

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

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

Итак, вот подробности:

  1. Когда вы используете map, каждый вызов замыкания получает элемент массива и возвращает значение, не имея доступа к предыдущему состоянию running, так как оно не обновляется в контексте замыкания map.

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

Вот несколько способов, которые работают правильно, как вы уже заметили в ваших примерах с reduce и for:

Пример с reduce

extension Array {
    func accumulate<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] {
        var running = initialResult
        return self.reduce(into: []) { result, next in
            running = nextPartialResult(running, next)
            result.append(running)
        }
    }
}

Пример с for циклом

extension Array {
    func accumulate<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] {
        var result: [Result] = []
        var running = initialResult
        for element in self {
            running = nextPartialResult(running, element)
            result.append(running)
        }
        return result
    }
}

Заключение

Таким образом, использование map для этой задачи неудачно, поскольку оно не предназначено для последовательного накопления значений, требуемых для вашей функции accumulate. Вместо этого выбирайте reduce, если хотите обрабатывать элементы массива с сохранением состояния между итерациями, или используйте цикл for, чтобы явно управлять состоянием переменной running.

Оцените материал
Добавить комментарий

Капча загружается...