Быстрые цифры и коллекции монотипий - PullRequest
2 голосов
/ 26 сентября 2019

коллег по обмену.Недавно я познакомился с swift, выполнив несколько тривиальных упражнений, и столкнулся с тем, где нужно вычислять статистику.Цель состояла в том, чтобы вычислить среднее (сумма всех элементов, деленная на количество) для массива чисел.Я решил воспользоваться наиболее известным способом решения этой проблемы и определил расширение для коллекции с помощью функции, которая возвращает числовое значение:

//this is specific case with Integers. Seems bold enought
extension Array where Element == Int {
    func mean() -> Double {
        var result = 0.0
        for i in self {
            result += Double(i)
        }
        return result / Double(self.count)
    }
}

[1,2,3,4,5,6,7,8,9].mean() //outputs 5.0

Приведенный выше код работает без проблем.Но я хочу иметь возможность применять mean() к любой последовательности чисел (числа с плавающей запятой, двойные числа и т. Д.), Поэтому попытайтесь:

extension Collection where Element: Numeric {
    func mean() -> some Numeric {
        var result = Element.init(exactly: 0)!
        for i in self {
            result += i
        }
        return result / Element.init(exactly: self.count)! 
    }
}

Это позволит получить результат из любого числового массива:

var array1: [Float] = ...
var array2: [UInt] = ... 
var array3: [Int8] = ... //nutty case

Но компиляция завершается неудачно с сообщением, в котором говорится, что, поскольку значения в этой коллекции могут быть другого конкретного типа, невозможно вызвать оператор деления из-за строгой типизации swift.Таким образом, единственная возможность реализовать необходимую функциональность - это расширить все конкретные типы, работающие с числами (Int, UInt, UInt8, ...), что является надежной вещью (их около 20).Учитывая большие возможности абстракции swift с расширениями, возможно ли:

extension Collection where Adopter: HomogeneousCollection { ... }

Кроме того, некоторые типы не могут быть большими числами: Int8 может содержать до 256 чисел, поэтому набор [Int8]может дать правильный результат, только если имеется менее 128 элементов, в противном случае произойдет деление на nil.

Итак, я спрашиваю, как лучше всего объявить функцию для вычисления среднего для однородного последовательного набора?Кроме того, как бы вы решили для случая, когда коллекция пуста?

1 Ответ

0 голосов
/ 26 сентября 2019

Я нашел подходящее решение.

extension Collection where Element: Numeric {
    /// Returns the total sum of all elements in the array
    var total: Element { return reduce(0, +) }
}

extension Collection where Element: BinaryInteger {
    /// Returns the average of all elements in the array
    var average: Double? {
        return isEmpty ? nil : Double(total) / Double(count)
    }
}

extension Collection where Element: BinaryFloatingPoint {
    /// Returns the average of all elements in the array
    var average: Element? {
        return isEmpty ? nil : total / Element(count)
    }
}

(1...9).average //Range<Int> -> 5.0

[1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9].average //[Double] -> 5.5

Хотя я хотел, чтобы результат был того же типа, что и коллекция, он принадлежит [Type] -> Type, на самом деле это не всегда было необходимо, и мне достаточно Double * [Type] -> Double.Также оказывается, что Swift заставляет коллекцию быть гомогенной в этом случае по какой-то причине.Сначала я был обеспокоен тем, что приведенный выше код не будет работать, поскольку коллекция (массив) инициализируется для хранения непрозрачного типа, но компиляция сообщает о массе: «BinaryInteger» может использоваться только как общее ограничение, поэтому конкретные типы здесь сохраняются..

//this compiles
protocol SomeProtocol {}
struct SomeStruct: SomeProtocol {}
struct AnotherStruct: SomeProtocol {}

var myObject: [SomeProtocol] = [SomeStruct(), AnotherStruct()]

//but this doesnt, though all elements conform to BinaryInteger
let arr: [BinaryInteger] = [0 as Int, 1 as Int8, 2 as Int16]
...