Подтвердить что ты не робот

Как мы можем создать универсальное расширение массива, которое суммирует типы номеров в Swift?

Swift позволяет создать расширение массива, которое суммирует Integer с:

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

Что теперь можно использовать для суммирования Int[] как:

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

Но как мы можем создать общую версию, которая поддерживает суммирование других типов Number, таких как 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,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()
4b9b3361

Ответ 1

С Swift 2 это можно сделать с помощью расширений протокола. (Подробнее см. Язык быстрого программирования: Протоколы).

Прежде всего, протокол 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

Думаю, я нашел разумный способ сделать это, заимствуя некоторые идеи от scalaz и начиная с предлагаемой реализации. В принципе, мы хотим, чтобы были классные классы, представляющие моноиды.

Другими словами, нам нужно:

  • ассоциативная функция
  • значение идентичности (т.е. нуль)

Здесь предлагается предлагаемое решение, которое работает вокруг ограничений системы быстрого типа

Прежде всего, наш дружественный Addable typeclass

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

Теперь пусть make 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, заключается в том, что это позволяет любому типу Addable typeclass. Если вы используете +, то тип, который не имеет оператора +, определен, тогда вам нужно реализовать такой оператор в глобальной области видимости, что мне не нравится.

В любом случае, как это работает, если вам нужно, например, сделать как Int, так и String 'multipiable', учитывая, что * определен для 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()

Теперь массив String может использовать метод mult для выполнения обратной конкатенации (просто глупый пример), а в реализации используется оператор *, новый для String, тогда как Int хранит используя свой обычный оператор *, и нам нужно всего лишь определить нуль для моноида.

Для чистоты кода я предпочитаю, чтобы вся реализация в стиле typeclass работала в области extension, но я думаю, что это вопрос вкуса.

Ответ 3

В Swift 2 вы можете решить это следующим образом:

Определить моноид для добавления в качестве протокола

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

В дополнение к другим решениям, это явно определяет нулевой элемент, используя стандартный инициализатор.

Затем объявите Int и Double как Addable:

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

Теперь вы можете определить метод sum() для всех массивов, сохраняющих Добавляемые элементы:

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

Ответ 4

Здесь глупая реализация:

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). Другими словами, он не инкапсулирован; это "свободная" функция sum, которая просто скрывается внутри массива. Таким образом, я не смог решить проблему, которую вы действительно пытаетесь решить.

Ответ 6

Из моего понимания быстрой грамматики идентификатор типа не может использоваться с общими параметрами, а только общий аргумент. Следовательно, объявление расширения может использоваться только с конкретным типом.

Ответ 7

Это можно сделать на основе предыдущих ответов в 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, как используется в других местах, планирует значительные улучшения, включая обработку исключений, promises и лучшее общее метапрограммирование.

Ответ 8

Помогите всем, кто пытается применить расширение ко всем 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
        }
    }
}