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

Расширение построенного общего типа в Swift

Можно ли расширить общий класс для специализированного/построенного общего типа? Я хотел бы расширить Int массивы методом для вычисления суммы его элементов.

например.

extension Array<Int> {

    func sum() -> Int {
        return reduce(0) { $0 + $1 }
    }

}
4b9b3361

Ответ 1

Это может быть достигнуто с использованием расширений протокола (см. Язык быстрого программирования: протоколы для получения дополнительной информации). В Swift 3:

Суммируя только Int, вы можете сделать:

extension Sequence where Iterator.Element == Int {
    var sum: Int {
        return reduce(0, +)
    }
}

Использование:

let nums = [1, 2, 3, 4]
print(nums.sum) // Prints: "10"

Или, для чего-то более общего, вы могли бы предложить @Wes Campaigne и создать протокол Addable:

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

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

Затем добавьте Sequence, чтобы добавить последовательности элементов Addable:

extension Sequence where Iterator.Element: Addable {
    var sum: Iterator.Element {
        return reduce(Iterator.Element(), +)
    }
}

Использование:

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

let strings = ["a", "b", "c"]
print(strings.sum) // Prints: "abc"

Ответ 2

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

protocol Addable {
    func +(lhs: Self, rhs: Self) -> Self
    class var identity: Self { get }
}

extension Int : Addable {
    static var identity: Int { get { return 0 } }
}

extension String : Addable {
    static var identity: String { get { return "" } }
}

extension Array {
    func sum<U : Addable>() -> U? {
        let s: U? = U.identity
        return self.sum(s)
    }

    func sum<U : Addable>(start: U?) -> U? {
        return reduce(start) { lhs, rhs in
            switch (lhs, rhs) {
            case (.Some(let left), let right as U):
                return left + right
            default:
                return nil
            }
        }
    }
}

В частности: с помощью этого решения тип inferencing не будет работать с методом no-parameter sum(), поэтому вам нужно либо аннотировать ожидаемый тип возвращаемого значения, либо дать ему начальное значение (из которого он может вывести тип).

Обратите внимание, что это возвращает значение Необязательного типа: если по какой-либо причине сумма ожидаемого типа не может быть вычислена из массива, она возвращает nil.

Чтобы проиллюстрировать:

let int_array = Array(1...10)

let x: Int? = int_array.sum() // result: {Some 55}
let x2 = int_array.sum(0) // result: {Some 55}
let x3 = int_array.sum() // Compiler error because it can't infer type


let string_array = ["a", "b", "c"]

let y: String? = string_array.sum() // result: {Some "abc"}
let y2 = string_array.sum("") // result: {Some "abc"}

let y3: Int? = string_array.sum() // result: nil  (can't cast String to Int)
let y4 = string_array.sum(0) // result: nil  (can't cast String to Int)


let double_array = [1.3, 4.2, 2.1]

let z = double_array.sum(0.0) // Compiler error because we haven't extended Double to be Addable

Ответ 3

Похоже, вы не можете. Самое близкое, что мы можем получить, это функция

func sum(a:Array<Int>) -> Int {
    return a.reduce(0) {$0 + $1}
}

Swift позволит вам добавить расширение в класс Array, но не специально для специализированной версии класса.

error: <REPL>:108:1: error: non-nominal type 'Array<Int>' cannot be extended

Вы можете расширить класс Array.

extension Array {

    func sum() -> Int {
        return reduce(0) { $0 + $1 }
    }
}

Теперь проблема с оператором +

error: <REPL>:102:16: error: could not find an overload for '+' that accepts the supplied arguments
        return reduce(0) { $0 + $1 }

Это несколько ожидаемо, так как мы не можем быть уверены, что оператор + будет перегружен для всех возможных типов, которые могут быть использованы в массиве.

Таким образом, мы могли бы попытаться ограничить операцию только некоторыми классами. Что-то вроде

class Dummy {
}

extension Array {
    func someFunc<T:Dummy>() -> Int {
       return 0
    }
}

var l = [Dummy()]
var r = l.someFunc() // Expect 0

Концептуально это должно работать (в настоящее время кажется, что есть ошибка, ошибка Xcode при оценке игровой площадки с использованием этого кода). В конечном счете, это работает, мы не можем использовать этот трюк, так как тип Int не является классом.

extension Array {
    func sum<T:Int>() -> T {
        return reduce(0) { $0 + $1 }
    }
}

error: <REPL>:101:14: error: inheritance from non-protocol, non-class type 'Int'
    func sum<T:Int>() -> T {

Я также рассмотрел расширение класса Array протоколом, но снова Int, не являющийся классом, делает невозможным. Если числовые типы были классами, было бы неплохо, если бы у нас мог быть протокол для определения того, что класс может быть добавлен так же, как Comparable или Equatable, но я понимаю, что протокол не может определить общую функцию, которая потребуется для создания a Addable.

Edit:

Как указано в других ответах, вы можете заставить его работать для Int, явно проверяя и отбрасывая Int в закрытии. Думаю, я пропустил это, будет расследовать. Но было бы неплохо, если бы у нас был общий способ работы с числовыми типами.

Ответ 4

Можно вернуть реальное значение суммы после того, как вы протестировали int-type в sum(). Поэтому я решил бы проблему следующим образом:

import Cocoa

extension Array {
    func sum() -> Int {
        if !(self[0] is Int) { return 0; }
        var sum = 0;
        for value in self { sum += value as Int }
        return sum;
    }
}

let array = [1,2,3,4,5]
array.sum() // =15

let otherArray = ["StringValue"]
otherArray.sum() // =0

Ответ 5

Александр

Вот как вы можете это сделать:

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

Работает как шарм, проверенный на детской площадке. Однако вы можете столкнуться с проблемами, если вы вызываете эту функцию на разных типах массивов.

Ответ 6

вы также можете сделать это

extension Array {
    func sum () -> Int? {
        guard self.count > 0  && self.first is Int  else {
            return nil
        }
        var s = 0
        forEach {
            s += $0 as! Int
        }
        return s
    }
}