If you prefer `map` or `filter` or something like that, you can re-write your code like this:
struct ValueDistributor2 {
var dataArray: [Double]
var numberOfBins: Int
var minValue = 0.0
var maxValue = 0.0
var bins: [(Range<Double>, Int)] = []
init(dataArray: [Double], numberOfBins: Int) {
self.dataArray = dataArray
self.numberOfBins = numberOfBins
minValue = dataArray.min() ?? 0
maxValue = dataArray.max() ?? 0
let binSize = (maxValue - minValue) / Double(numberOfBins)
assert(binSize != 0.0) //Ignore special case
func binIndex(_ number: Double) -> Int {
let index = Int(floor((number-minValue)/binSize))
return index
}
func range(ofBin index: Int) -> Range<Double> {
let lowerBound = minValue + binSize * Double(index)
let upperBound = minValue + binSize * Double(index+1)
return lowerBound..<upperBound
}
let counts = dataArray.reduce(into: [:]) {$0[binIndex($1), default: 0] += 1}
bins = counts.sorted(by: {$0.key < $1.key}).map{(range(ofBin: $0.key), $0.value)}
}
}
If you do not like that the last range may always contain the max value, you can write something like this:
struct GeneralizedRange<T>: Hashable, CustomStringConvertible
where T: Hashable, T: Comparable {
var lowerBound: T
var upperBound: T
var isClosed: Bool
init(_ range: Range<T>) {
self.lowerBound = range.lowerBound
self.upperBound = range.upperBound
self.isClosed = false
}
init(_ range: ClosedRange<T>) {
self.lowerBound = range.lowerBound
self.upperBound = range.upperBound
self.isClosed = true
}
var description: String {
if isClosed {
return "\(lowerBound)...\(upperBound)"
} else {
return "\(lowerBound)..<\(upperBound)"
}
}
func contains(_ value: T) -> Bool {
if isClosed {
return lowerBound<=value && value<=upperBound
} else {
return lowerBound<=value && value<upperBound
}
}
static func ~= (lhs: GeneralizedRange<T>, rhs: T) -> Bool {
return lhs.contains(rhs)
}
}
struct ValueDistributor3 {
var dataArray: [Double]
var numberOfBins: Int
var minValue = 0.0
var maxValue = 0.0
var bins: [(GeneralizedRange<Double>, Int)] = []
init(dataArray: [Double], numberOfBins: Int) {
self.dataArray = dataArray
self.numberOfBins = numberOfBins
minValue = dataArray.min() ?? 0
maxValue = dataArray.max() ?? 0
let binSize = (maxValue - minValue) / Double(numberOfBins)
assert(binSize != 0.0) //Ignore special case
func binIndex(_ number: Double) -> Int {
let index = Int(floor((number-minValue)/binSize))
return index >= numberOfBins ? numberOfBins-1 : index
}
func range(ofBin index: Int) -> GeneralizedRange<Double> {
let lowerBound = minValue + binSize * Double(index)
let upperBound = minValue + binSize * Double(index+1)
if index == numberOfBins-1 {
return GeneralizedRange(lowerBound...upperBound)
} else {
return GeneralizedRange(lowerBound..<upperBound)
}
}
let counts = dataArray.reduce(into: [:]) {$0[binIndex($1), default: 0] += 1}
bins = counts.sorted(by: {$0.key < $1.key}).map{(range(ofBin: $0.key), $0.value)}
}
}
Testing code and Output example:
var dataArray: [Double] = []
for _ in 0..<20 {
dataArray.append(Double(Int.random(in: 0...100)))
}
print(dataArray)
let vd = ValueDistributor(dataArray: dataArray, numberOfBins: 10)
print(vd.bins)
let vd2 = ValueDistributor2(dataArray: dataArray, numberOfBins: 10)
print(vd2.bins, vd.bins.map{$0.1} == vd2.bins.map{$0.1})
let vd3 = ValueDistributor3(dataArray: dataArray, numberOfBins: 10)
print(vd3.bins, vd.bins.map{$0.1} == vd3.bins.map{$0.1})
[25.0, 66.0, 6.0, 0.0, 81.0, 71.0, 10.0, 84.0, 92.0, 56.0, 64.0, 71.0, 73.0, 66.0, 100.0, 19.0, 70.0, 51.0, 59.0, 19.0]
[(Range(0.0..<10.0), 2), (Range(10.0..<20.0), 3), (Range(20.0..<30.0), 1), (Range(50.0..<60.0), 3), (Range(60.0..<70.0), 3), (Range(70.0..<80.0), 4), (Range(80.0..<90.0), 2), (Range(90.0..<100.0), 1), (Range(100.0..<110.0), 1)]
[(Range(0.0..<10.0), 2), (Range(10.0..<20.0), 3), (Range(20.0..<30.0), 1), (Range(50.0..<60.0), 3), (Range(60.0..<70.0), 3), (Range(70.0..<80.0), 4), (Range(80.0..<90.0), 2), (Range(90.0..<100.0), 1), (Range(100.0..<110.0), 1)] true
[(0.0..<10.0, 2), (10.0..<20.0, 3), (20.0..<30.0, 1), (50.0..<60.0, 3), (60.0..<70.0, 3), (70.0..<80.0, 4), (80.0..<90.0, 2), (90.0...100.0, 2)] false
Please remember, there may be some cases calculation errors affect differently in each implementation.