Как мы можем создать универсальное расширение массива, которое суммирует типы номеров в 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()
Ответы
Ответ 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
, которая просто скрывается внутри массива. Таким образом, я не смог решить проблему, которую вы действительно пытаетесь решить.
Ответ 5
3> [1,2,3].reduce(0, +)
$R2: Int = 6
4> [1.1,2.1,3.1].reduce(0, +)
$R3: Double = 6.3000000000000007
Карта, Фильтр, Уменьшить и многое другое
Ответ 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
}
}
}