我们如何创建一个在Swift中对Number类型求和的通用Array Extension?

时间:2014-06-06 19:38:12

标签: swift

Swift允许您创建一个数组扩展,它将Integer与:

相加
extension Array {
    func sum() -> Int {
        return self.map { $0 as Int }.reduce(0) { $0 + $1 }
    }
}

现在可以用{和} {}来总结Int[]

[1,2,3].sum() //6

但是我们怎样才能制作支持对Double[]等其他数字类型求和的通用版本?

[1.1,2.1,3.1].sum() //fails

这个问题不是如何对数字进行总结,而是如何创建通用数组扩展来执行此操作。


越来越近了

如果能帮助任何人更接近解决方案,这是我能够获得的最接近的数据:

您可以创建一个可以满足我们需要的协议,即:

protocol Addable {
    func +(lhs: Self, rhs: Self) -> Self
    init()
}

然后扩展我们想要支持的每种类型,符合上述协议:

extension Int : Addable {
}

extension Double : Addable {
}

然后使用该约束添加扩展名:

extension Array {
    func sum<T : Addable>(min:T) -> T
    {
        return self.map { $0 as T }.reduce(min) { $0 + $1 }
    }
}

现在可用于支持协议的数字,即:

[1,2,3].sum(0) //6
[1.1,2.1,3.1].sum(0.0) //6.3

不幸的是,我无法在不提供论据的情况下使其工作,即:

func sum<T : Addable>(x:T...) -> T?
{
    return self.map { $0 as T }.reduce(T()) { $0 + $1 }
}

修改后的方法仍然适用于1个参数:

[1,2,3].sum(0) //6

但在无参数调用时无法解析方法,即:

[1,2,3].sum() //Could not find member 'sum'

在方法签名中添加Integer也无法帮助解决方法:

func sum<T where T : Integer, T: Addable>() -> T?
{
    return self.map { $0 as T }.reduce(T()) { $0 + $1 }
}

但希望这有助于其他人更接近解决方案。


一些进展

从@GabrielePetronella回答,如果我们在调用网站上明确指定类型,我们可以调用上面的方法,如:

let i:Int = [1,2,3].sum()
let d:Double = [1.1,2.2,3.3].sum()

8 个答案:

答案 0 :(得分:8)

我认为我找到了一种合理的方法,从scalaz借鉴了一些想法并从你提议的实施开始。 基本上我们想要的是拥有代表幺半群的类型类。

换句话说,我们需要:

  • 关联函数
  • 身份值(即零)

这是一个建议的解决方案,它围绕快速类型系统限制

首先,我们友好的Addable类型类

protocol Addable {
    class func add(lhs: Self, _ rhs: Self) -> Self
    class func zero() -> Self
}

现在让我们让Int实现它。

extension Int: Addable {
    static func add(lhs: Int, _ rhs: Int) -> Int {
        return lhs + rhs
    }

    static func zero() -> Int {
        return 0
    }
}

到目前为止一切顺利。现在我们已经拥有了构建通用`sum函数所需的所有部分:

extension Array {
    func sum<T : Addable>() -> T {
        return self.map { $0 as T }.reduce(T.zero()) { T.add($0, $1) }
    }
}

让我们测试一下

let result: Int = [1,2,3].sum() // 6, yay!

由于类型系统的限制,您需要显式地转换结果类型,因为编译器无法自己确定Addable解析为Int

所以你不能这样做:

let result = [1,2,3].sum()

我认为这种做法是一个可以忍受的缺点。

当然,这是完全通用的,它可以在任何类中使用,适用于任何类型的幺半群。 我之所以没有使用默认的+运算符,而是定义add函数,是因为这允许任何类型实现{{1类型类。如果你使用Addable,那么没有定义+运算符的类型,那么你需要在全局范围内实现这样的运算符,我不喜欢。

无论如何,如果您需要同时+Int'可倍增',那么它是如何工作的,因为String是为*定义的,但不是对于`String。

Int

现在protocol Multipliable { func *(lhs: Self, rhs: Self) -> Self class func m_zero() -> Self } func *(lhs: String, rhs: String) -> String { return rhs + lhs } extension String: Multipliable { static func m_zero() -> String { return "" } } extension Int: Multipliable { static func m_zero() -> Int { return 1 } } extension Array { func mult<T: Multipliable>() -> T { return self.map { $0 as T }.reduce(T.m_zero()) { $0 * $1 } } } let y: String = ["hello", " ", "world"].mult() 数组可以使用方法String来执行反向连接(只是一个愚蠢的例子),并且实现使用mult运算符,为{{1}新定义}},而*继续使用其通常的String运算符,我们只需要为monoid定义一个零。

对于代码清洁,我更喜欢将整个类型类实现放在Int范围内,但我想这是一个品味问题。

答案 1 :(得分:7)

从Swift 2开始,可以使用协议扩展来完成此操作。 (有关更多信息,请参阅The Swift Programming Language: Protocols)。

首先,Addable协议:

protocol Addable: IntegerLiteralConvertible {
    func + (lhs: Self, rhs: Self) -> Self
}

extension Int   : Addable {}
extension Double: Addable {}
// ...

接下来,扩展SequenceType以添加Addable元素的序列:

extension SequenceType where Generator.Element: Addable {
    var sum: Generator.Element {
        return reduce(0, combine: +)
    }
}

用法:

let ints = [0, 1, 2, 3]
print(ints.sum) // Prints: "6"

let doubles = [0.0, 1.0, 2.0, 3.0]
print(doubles.sum) // Prints: "6.0"

答案 2 :(得分:4)

在Swift 2中,您可以这样解决:

将monoid定义为添加为协议

protocol Addable {
    init()
    func +(lhs: Self, rhs: Self) -> Self
    static var zero: Self { get }
}
extension Addable {
    static var zero: Self { return Self() }
}

除了其他解决方案之外,它还使用标准初始化程序明确定义了零元素。

然后将Int和Double声明为可添加:

extension Int: Addable {}
extension Double: Addable {}

现在,您可以为存储可添加元素的所有数组定义sum()方法:

extension Array where Element: Addable {
    func sum() -> Element {
        return self.reduce(Element.zero, combine: +)
    }
}

答案 3 :(得分:1)

这是一个愚蠢的实施:

extension Array {
    func sum(arr:Array<Int>) -> Int {
        return arr.reduce(0, {(e1:Int, e2:Int) -> Int in return e1 + e2})
    }
    func sum(arr:Array<Double>) -> Double {
        return arr.reduce(0, {(e1:Double, e2:Double) -> Double in return e1 + e2})
    }
}

这很愚蠢,因为你必须说arr.sum(arr)。换句话说,它不是封装的;它是一个免费的&#34;函数sum恰好隐藏在Array中。因此,我未能解决您真正试图解决的问题。

答案 4 :(得分:0)

  3> [1,2,3].reduce(0, +)
$R2: Int = 6

  4> [1.1,2.1,3.1].reduce(0, +)
$R3: Double = 6.3000000000000007

Map, Filter, Reduce and more

答案 5 :(得分:0)

根据我对swift语法的理解,type identifier不能与泛型参数一起使用,只能用于泛型参数。因此,extension declaration只能用于具体类型。

答案 6 :(得分:0)

基于Swift 1.x中的先前答案,可以轻松实现:

import Foundation

protocol Addable {
    func +(lhs: Self, rhs: Self) -> Self
    init(_: Int)
    init()
}

extension Int : Addable {}
extension Int8 : Addable {}
extension Int16 : Addable {}
extension Int32 : Addable {}
extension Int64 : Addable {}

extension UInt : Addable {}
extension UInt8 : Addable {}
extension UInt16 : Addable {}
extension UInt32 : Addable {}
extension UInt64 : Addable {}

extension Double : Addable {}
extension Float : Addable {}
extension Float80 : Addable {}

// NSNumber is a messy, fat class for ObjC to box non-NSObject values
// Bit is weird

extension Array {
    func sum<T : Addable>(min: T = T(0)) -> T {
        return map { $0 as! T }.reduce(min) { $0 + $1 }
    }
}

在这里:https://gist.github.com/46c1d4d1e9425f730b08

其他地方使用的Swift 2计划进行重大改进,包括异常处理,承诺和更好的通用元编程。

答案 7 :(得分:0)

帮助其他人努力将扩展名应用于所有Numeric的值,而又不会显得混乱:

extension Numeric where Self: Comparable {

    /// Limits a numerical value.
    ///
    /// - Parameter range: The range the value is limited to be in.
    /// - Returns: The numerical value clipped to the range.
    func limit(to range: ClosedRange<Self>) -> Self {
        if self < range.lowerBound {
            return range.lowerBound
        } else if self > range.upperBound {
            return range.upperBound
        } else {
            return self
        }
    }
}