简体   繁体   English

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

[英]How can we create a generic Array Extension that sums Number types in Swift?

Swift lets you create an Array extension that sums Integer's with: Swift允许您创建一个数组扩展,它将Integer与:

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

Which can now be used to sum Int[] like: 现在可以用来对Int[]求和:

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

But how can we make a generic version that supports summing other Number types like Double[] as well? 但是,我们如何制作一个支持对Double[]等其他数字类型求和的通用版本呢?

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

This question is NOT how to sum numbers , but how to create a generic Array Extension to do it. 这个问题不是如何对数字求和 ,而是如何创建一个通用的数组扩展来实现它。


Getting Closer 越来越近

This is the closest I've been able to get if it helps anyone get closer to the solution: 如果能帮助任何人更接近解决方案,这是我能够获得的最接近的:

You can create a protocol that can fulfills what we need to do, ie: 您可以创建一个可以满足我们需要的协议,即:

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

Then extend each of the types we want to support that conforms to the above protocol: 然后扩展我们想要支持的每种类型,以符合上述协议:

extension Int : Addable {
}

extension Double : Addable {
}

And then add an extension with that constraint: 然后添加具有该约束的扩展:

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

Which can now be used against numbers that we've extended to support the protocol, ie: 现在可以用来对付我们扩展为支持协议的数字,即:

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

Unfortunately I haven't been able to get it working without having to supply an argument, ie: 不幸的是,我无法在不提供参数的情况下使其工作,即:

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

The modified method still works with 1 argument: 修改后的方法仍然适用于1个参数:

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

But is unable to resolve the method when calling it with no arguments, ie: 但是在没有参数的情况下调用它时无法解析方法,即:

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

Adding Integer to the method signature also doesn't help method resolution: Integer添加到方法签名也无法帮助解决方法:

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

But hopefully this will help others come closer to the solution. 但希望这将有助于其他人更接近解决方案。


Some Progress 一些进展

From @GabrielePetronella answer, it looks like we can call the above method if we explicitly specify the type on the call-site like: 从@GabrielePetronella回答,如果我们在调用网站上明确指定类型,我们可以调用上面的方法,如:

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

I think I found a reasonable way of doing it, borrowing some ideas from scalaz and starting from your proposed implementation. 我想我找到了一种合理的方法,从scalaz中借鉴一些想法并从你提议的实现开始。 Basically what we want is to have typeclasses that represents monoids. 基本上我们想要的是具有代表幺半群的类型类。

In other words, we need: 换句话说,我们需要:

  • an associative function 关联函数
  • an identity value (ie a zero) 身份值(即零)

Here's a proposed solution, which works around the swift type system limitations 这是一个建议的解决方案,它围绕快速类型的系统限制

First of all, our friendly Addable typeclass 首先,我们友好的Addable类型类

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

Now let's make Int implement it. 现在让我们让Int实现它。

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

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

So far so good. 到现在为止还挺好。 Now we have all the pieces we need to build a generic `sum function: 现在我们已经拥有了构建通用`sum函数所需的所有部分:

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

Let's test it 我们来试试吧

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

Due to limitations of the type system, you need to explicitly cast the result type, since the compiler is not able to figure by itself that Addable resolves to Int . 由于类型系统的限制,您需要显式地转换结果类型,因为编译器无法自行确定Addable解析为Int

So you cannot just do: 所以你不能只做:

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

I think it's a bearable drawback of this approach. 我认为这种方法是一个可以忍受的缺点。

Of course, this is completely generic and it can be used on any class, for any kind of monoid. 当然,这是完全通用的,它可以用于任何类,任何类型的monoid。 The reason why I'm not using the default + operator, but I'm instead defining an add function, is that this allows any type to implement the Addable typeclass. 我之所以没有使用默认的+运算符,而是我定义了一个add函数,是因为它允许任何类型实现Addable类型类。 If you use + , then a type which has no + operator defined, then you need to implement such operator in the global scope, which I kind of dislike. 如果你使用+ ,那么没有定义+运算符的类型,那么你需要在全局范围内实现这样的运算符,我有点不喜欢。

Anyway, here's how it would work if you need for instance to make both Int and String 'multipliable', given that * is defined for Int but not for `String. 无论如何,如果你需要让IntString '可倍增',它是如何工作的,因为*是为Int定义的,而不是为`String定义的。

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()

Now array of String can use the method mult to perform a reverse concatenation (just a silly example), and the implementation uses the * operator, newly defined for String , whereas Int keeps using its usual * operator and we only need to define a zero for the monoid. 现在, String数组可以使用方法mult来执行反向连接(只是一个愚蠢的例子),并且实现使用为String新定义的*运算符,而Int保持使用其通常的*运算符,我们只需要定义零对于幺半群。

For code cleanness, I much prefer having the whole typeclass implementation to live in the extension scope, but I guess it's a matter of taste. 对于代码清洁,我更喜欢让整个类型类实现生活在extension范围内,但我想这是一个品味问题。

As of Swift 2 it's possible to do this using protocol extensions. 从Swift 2开始,可以使用协议扩展来完成此操作。 (See The Swift Programming Language: Protocols for more information). (有关更多信息,请参阅Swift编程语言:协议 )。

First of all, the Addable protocol: 首先, Addable协议:

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

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

Next, extend SequenceType to add sequences of Addable elements: 接着,扩展SequenceType添加的序列Addable元素:

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

Usage: 用法:

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"

In Swift 2, you can solve it like this: 在Swift 2中,你可以像这样解决它:

Define the monoid for addition as protocol 定义monoid作为协议添加

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

In addition to other solutions, this explicitly defines the zero element using the standard initializer. 除了其他解决方案之外,这还使用标准初始化程序明确定义了零元素。

Then declare Int and Double as Addable: 然后将Int和Double声明为Addable:

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

Now you can define a sum() method for all Arrays storing Addable elements: 现在,您可以为存储Addable元素的所有Arrays定义sum()方法:

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

Here's a silly implementation: 这是一个愚蠢的实现:

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})
    }
}

It's silly because you have to say arr.sum(arr) . 这很愚蠢因为你不得不说arr.sum(arr) In other words, it isn't encapsulated; 换句话说,它不是封装的; it's a "free" function sum that just happens to be hiding inside Array. 它是一个“自由”函数sum恰好隐藏在Array中。 Thus I failed to solve the problem you're really trying to solve. 因此,我未能解决您真正想要解决的问题。

  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 地图,过滤,减少等等

From my understanding of the swift grammar, a type identifier cannot be used with generic parameters, only a generic argument. 根据我对swift语法的理解, 类型标识符不能与泛型参数一起使用,只能用于泛型参数。 Hence, the extension declaration can only be used with a concrete type. 因此, 扩展声明只能用于具体类型。

It's doable based on prior answers in Swift 1.x with minimal effort: 基于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 }
    }
}

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

Swift 2, as used elsewhere, plans major improvements, including exception handling, promises and better generic metaprogramming. 其他地方使用的Swift 2计划进行重大改进,包括异常处理,承诺和更好的通用元编程。

Help for anyone else struggling to apply the extension to all Numeric values without it looking messy: 帮助其他人努力将扩展应用于所有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
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM