Вопрос или проблема
В книге 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
, как бы оно ни менялось.
Итак, вот подробности:
-
Когда вы используете
map
, каждый вызов замыкания получает элемент массива и возвращает значение, не имея доступа к предыдущему состояниюrunning
, так как оно не обновляется в контексте замыканияmap
. -
Вместо этого, когда вы используете
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
.