【问题标题】:Swift Array Extension to replace value of index n by the sum of the n-previous valuesSwift Array Extension 用前 n 个值的总和替换索引 n 的值
【发布时间】:2019-03-07 21:22:48
【问题描述】:

我正在尝试为 Array Types 编写一个扩展,将索引 n 中的前 n 个索引相加。

let myArray = [1, 2, 3, 4, 5]
let mySumArray = myArray.sumNIndex()
print(mySumArray)
// returns [1,3,6,10,15]

我尝试了各种方法,但在某些时候都失败了。 例如,下面的示例会触发编译错误 “不能使用类型为 '(Int, _)' 的参数列表调用 'reduce'”:

extension Array {
    mutating func indexSum() {
        var tempArray = [Any]()
        for index in 1...self.count - 1 {
        self[index] += self[.prefix(index + 2).reduce(0, +)]
        }
    }
}

这另一个尝试触发另一个编译错误: "二元运算符 '+=' 不能应用于两个 'Element' 操作数"

extension Array {
    mutating func indexSum() {
        var tempArray = [Any]()
        for index in 1...self.count - 1 {
        self[index] += self[index - 1]
        }
    }
}

欢迎任何想法! 非常感谢您的帮助!

编辑:非常感谢@Martin 和@Carpsen,他们以两种不同的方式解决了这个问题

@Martin 使用 map 方法:

extension Array where Element: Numeric {
    func cumulativeSum() -> [Element] {
        var currentSum: Element = 0
        return map {
            currentSum += $0
            return currentSum
        }
    }
}

@Carpsen 使用 reduce 方法:

extension Array where Element: Numeric {
    func indexSum() -> [Element] {
        return self.reduce(into: [Element]()) {(acc, element) in
            return acc + [(acc.last ?? 0) + element]
        })
    }
}

【问题讨论】:

    标签: arrays swift extension-methods reduce


    【解决方案1】:

    主要问题是没有为元素定义加法运算符+ 任意数组。您需要限制扩展方法,例如到 Numeric 元素的数组。

    也不需要使用Any

    这是一种可能的非变异方法实现:

    extension Array where Element: Numeric {
        func cumulativeSum() -> [Element] {
            var currentSum: Element = 0
            return map {
                currentSum += $0
                return currentSum
            }
        }
    }
    

    例子:

    let intArray = [1, 2, 3, 4, 5]
    print(intArray.cumulativeSum()) // [1, 3, 6, 10, 15]
    
    let floatArray = [1.0, 2.5, 3.25]
    print(floatArray.cumulativeSum()) [1.0, 3.5, 6.75]
    

    以类似的方式,我们可以“累积连接”一个 字符串数组。 enumerated() 现在用于提供当前元素 索引与元素一起,用于决定是否 是否插入分隔符:

    extension Array where Element == String {
        func cumulativeJoin(separator: String) -> [Element] {
            var currentJoin = ""
            return enumerated().map { (offset, elem) in
                if offset > 0 { currentJoin.append(separator) }
                currentJoin.append(elem)
                return currentJoin
            }
        }
    }
    

    例子:

    let stringArray = ["a", "b", "c"]
    print(stringArray.cumulativeJoin()) // ["a", "ab", "abc"]
    print(stringArray.cumulativeJoin(separator: ":")) // ["a", "a:b", "a:b:c"]
    

    【讨论】:

    • 感谢您抽出宝贵的时间了解这个 Martin。你的话很有道理,我认为我必须使用协议。但是,有两件事:(1)代码确实编译但返回相同的数组 [1,2,3,4,5] 和(2)我希望代码也适用于字符串,因此数字协议可能限制太多。再次感谢!!!
    • 对于 let intArray = [1, 2, 3, 4, 5]print(intArray.cumulativeSum()) 返回 [1,2,3,4,5] 而不是 [1,2,6,10,15]
    • @Chris:哎呀。现已修复。
    • 非常感谢@Martin!它就像一个魅力!我想我理解这个想法:由于 map 方法,你用索引值增加 currentSum 属性,但我不太明白一方面是连续的returns 以及为什么你不写 self.map 代替另一方面的地图。最后但并非最不重要的一点是,我希望它也可以与字符串一起使用。
    • @Chris: map() 默认为self.map()(所有实例方法也是如此)。 map() 依次调用每个数组元素的闭包,并返回包含结果的数组。 – ["a","b"].cumulativeSum() 应该返回什么?
    【解决方案2】:

    试试这个:

    let myArray = [1, 2, 3, 4, 5]
    
    myArray.reduce([Int](), {accumulator, element in
        return accumulator + [(accumulator.last ?? 0) + element]
    })
    //[1, 3, 6, 10, 15]
    

    reduce 的作用是:

    • 从一个空数组开始
    • 对于myArray 中的每个元素,它会使用accumulator 中的最后一个元素计算其sum
    • 返回前一个数组加上最后一个sum

    这是一个更简单但更长的版本:

    let myArray = [1, 2, 3, 4, 5]
    
    let newArray = myArray.reduce([Int](), {accumulator, element in
        var tempo = accumulator
        let lastElementFromTheAccumulator = accumulator.last ?? 0
        let currentSum = element + lastElementFromTheAccumulator
        tempo.append(currentSum)
        return tempo
    })
    
    print(newArray)  //[1, 3, 6, 10, 15]
    

    Martin R 在 cmets 中建议的更有效的解决方案是使用 reduce(into:)

    myArray.reduce(into: [Int]()) { (accumulator, element) in
        accumulator += [(accumulator.last ?? 0) + element]
    }
    //[1, 3, 6, 10, 15]
    

    你可以把它作为一个扩展:

    extension Array where Element: Numeric {
        func indexSum() -> [Element] {
            return self.reduce([Element](), {acc, element in
                return acc + [(acc.last ?? 0) + element]
            })
        }
    }
    
    myArray.indexSum()  //[1, 3, 6, 10, 15]
    

    这里有一个也适用于字符串的解决方案:

    extension Array where Element == String {
        func indexSum() -> [String] {
            return self.reduce(into: [String]()) {(accumulator, element) in
                accumulator += [(accumulator.last ?? "") + element]
            }
        }
    }
    
    ["a", "b", "c", "d"].indexSum() //["a", "ab", "abc", "abcd"]
    

    如果你想在初始数组元素的元素之间有一个分隔符,你可以使用这个扩展:

    extension Array where Element == String {
        func indexSum(withSparator: String) -> [String] {
            return self.reduce(into: [String]()) {(accumulator, element) in
                var previousString = ""
                if let last = accumulator.last {
                    previousString = last + " "
                }
                accumulator += [previousString +  element]
            }
        }
    }
    
    ["a", "b", "c", "d"].indexSum(withSparator: " ") //["a", "a b", "a b c", "a b c d"]
    

    【讨论】:

    • 感谢卡普森!它有效,但你介意进一步解释一下,以便我能更好地理解你是如何编写/找到这个闭包的吗?
    • 非常感谢@Carpsen!我刚从 Swift 开始,所以我读了较长的版本(谢谢!!)但我仍然很难从较长的版本跳到较短的版本。我需要弄清楚你如何返回一个数组而不是简短版本中的值。此外,您知道如何使其与字符串一起使用吗?非常非常感谢!
    • 这会创建很多中间数组。你至少应该使用reduce(into:)
    • @Chris accumulator 是一个数组,所以 accumulator + [(accumulator.last ?? 0) + element] 是一个数组二。您可以通过使用 + 添加两个数组来获得更长的数组
    • @WayneBurkett:公平点。我将重新表述它:reduce(into:) 可用于减少(!)中间数组的数量(这就是它的用途:github.com/apple/swift-evolution/blob/master/proposals/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-20
    相关资源
    最近更新 更多