Swift - Add granularity in Array (2 by 2, 5 by 5, etc..) - ios

I'm currently working with the "Charts" pod.
My app shows a bar chart of athletes results, with:
X Axis: number of reps / time / rounds / weight
Y Axis: number of athletes
I would like to gather the number of reps in different groups.
Something that would be like: 10 < x < 20, 20 < x < 30, etc... Rather than the real total of reps / time / whatever.
Something like that:
Depending on the difference between the Max() and Min() value, I want to change the granularity.
This is what I have right now:
let resultTime = [2458, 3500, 3600] // Fake data -> 41min, 58min, 60 min
let dict = Dictionary(grouping: resultTime, by: { $0 / 60 }).map { ($0.key, $0.value.count) }
This way, I can have a dictionary grouped by minute. I got this:
[(58, 1), (40, 1), (60, 1)]
I would like to group these data 2 by 2, to have something like that instead:
[(60, 2), (40, 1)]
Is there a possibly to play with the granularity with groupBy?

As #Alexander said, the solution is to round the by function
You can do that with this little function:
func roundWithCoef(_ x : Double, coef: Int) -> Int {
return coef * Int(round(x / Double(coef)))
}
let dict = Dictionary(grouping: resultTime, by: { roundWithCoef(Double($0 / 60), coef: 4) }).map { ($0.key, $0.value.count) }
This way you can play with the coef number

Related

Generate all numbers for 0 to 1 using a loop

This question might seem a bit silly..:) But I think I'm missing something somewhere..so bit confused...
I wanted to generate all numbers from 0 to 1. In other words, if I do 1/2, I get 0.5. Then 0.5/2 = 0.25. Then 0.25/2 = 0.125. This will go on until 0.00000001 (A total of 26 divisions)
But I want to generate all numbers in an increasing order from 0.00000001 to 1.
I tried doing something like so...
let first = 0.00000001
let last = 1.0
let interval = first * 2
let sequence = stride(from: first, to: last, by: interval)
for element in sequence {
print(element)
}
But it's not working. It seemed it just prints infinitely...
How can I properly use a for loop and print from 0.00000001 to 1 in a limited number of iterations..? Or any other loops to be used in this case..?
You can't use stride. stride produces an arithmetic sequence with a difference of interval, which is 0.00000002:
0.00000001
0.00000003
0.00000005
0.00000007
...
You want a geometric sequence between 0 and 1.
You could use sequence instead, which generates an infinite sequence:
let first = 0.00000001
let last = 1.0
for item in sequence(first: first, next: { $0 * 2 }).prefix(while: { $0 < last }) {
print(item)
}
{ $0 * 2 } is the function that generates the next element, and prefix(while:) is used to get first elements that satisfy the < last condition.
Here is another way you could approach it. Use stride to count down the powers of 2 from 26 to 0 and divide 1.0 by that power of 2 and display only the first 8 decimal places:
for n in stride(from: 26, through: 0, by: -1) {
print(String(format: "%.8f", 1.0 / pow(2.0, Double(n))))
}
or equivalently (removing the 1/n by using negative exponents):
for n in -26...0 {
print(String(format: "%.8f", pow(2.0, Double(n))))
}
Output:
0.00000001
0.00000003
0.00000006
0.00000012
0.00000024
0.00000048
0.00000095
0.00000191
0.00000381
0.00000763
0.00001526
0.00003052
0.00006104
0.00012207
0.00024414
0.00048828
0.00097656
0.00195312
0.00390625
0.00781250
0.01562500
0.03125000
0.06250000
0.12500000
0.25000000
0.50000000
1.00000000

Round up to a 10s in Dart

I'm doing pagination and I'm wondering how I can take a number such as 11 and round it to 20.
Other cases:
1 should round to 10
501 should round to 510
10 should round to 10
Basically I have cards and there are 10 cards per page and so if there are 11 cards there should be 2 pages.
To round a number up to a multiple of some factor, you can do:
/// Round [number] up to a multiple of [factor].
///
/// The [factor] must be greater than zero.
int roundUp(int number, int factor) {
if (factor < 1) throw RangeError.range(factor, 1, null, "factor");
number += factor - 1;
return number - (number % factor);
}
There is a corresponding roundDown which you can defined roundUp in terms of:
int roundDown(int number, int factor) {
if (factor < 1) throw RangeError.range(factor, 1, null, "factor");
return number - (number % factor);
}
int roundUp(int number, int factor) => roundDown(number + (factor - 1), factor);
This rounds towards plus/minus infinity. If you want to round towards/away from zero instead, you can use:
int roundTowardsZero(int number, int factor) {
if (factor < 1) throw RangeError.range(factor, 1, null, "factor");
return number - number.remainder(factor);
}
int roundAwayFromZero(int number, int factor) =>
roundTowardsZero(number + number.sign * (factor - 1), factor);
Because this approach uses only integers, it's relatively safe from precision loss, but it can overflow at the very end of the integer range (or if you use very, very large factors). If you want to be safe against that, we need to add a check for whether number + factor - 1 overflows. In most practical uses, that won't matter.
All you have to do is:
var test = 11;
print((test / 10).ceil() * 10); // 20
Dividing the number by 10 (in this case it's 11) will result in 1.1.
When you do (1.1).ceil(), you will get 2.
Now you multiply by 10 to get it to a power of 10.
var data = 12.62;
print(data.ceil());
Will output
13

Swift - Sort array element by range

I'm currently working with the "Charts" pod.
My app shows a bar chart of athletes results, with:
X Axis: number of reps / time / rounds / weight
Y Axis: number of athletes
I would like to gather the number of reps in different groups.
Something that would be like: 10 < x < 20, 20 < x < 30, etc...
Rather than the real total of reps.
Something like that:
What would be the best way to do so? I though about some approaches:
Round the number of reps to transform 19 and 15 to 10 and 10 for example (for the 10 < x < 20 category)
The problem with that method is that I don't know if I can do the same for the "time (seconds)
Create a new array with dictionnaries inside, something like:
[["10-20": 15, 17, 19], ["20-30": 21, 22, 22, 24], etc..]
But I don't know how to achieve that...
What would be the best way?
You can use Dictionary's init(grouping:by:) initializer to create such a dictionary:
let array = [15,17,19,22,24,24,27]
let dict = Dictionary(grouping: array, by: { $0 / 10 })
// dict is [2: [22, 24, 24, 27], 1: [15, 17, 19]]
If I understood you correctly, you probably have a bunch of Athletes and they have a reps property. You can group by $0.reps / 10 instead:
Dictionary(grouping: athletes, by: { $0.reps / 10 })
And then map the keys and values to this:
.map { ("\($0.key * 10) - \(($0.key + 1) * 10)", $0.value.count) }
// now you have this:
// [("20 - 30", 4), ("10 - 20", 3)]

swift: what does arc4andom_uniform() means

This might be a lame question and even have already answered in SO.i have even searched about this but could not understand in a proper way. what is happening here..??please help me out to understand this.
let size = Double(arc4random_uniform(5)) + 1
for index in 0..<ITEM_COUNT
{
let y = Double(arc4random_uniform(100)) + 50.0
let size = Double(arc4random_uniform(5)) + 1
entries.append(ChartEntry(x: Double(index) + 0.5, y: y, size: CGFloat(size)))
}
arc4random_uniform(x) returns a random value between 0 and x-1
Examples:
arc4random_uniform(2) -> returns 0 or 1 randomly
arc4random_uniform(2) == 0 returns true or false randomly
arc4random_uniform(6) + 1 returns a number between 1 and 6 (like a dice roll)
There are a multitude of reasons that arc4random_uniform(5) returns a number between 0 and 5, but the main one is that this is a basic functionality in programming, where numbers start at zero. An example of why this would be useful would be returning a random value from an array. Example:
func randomArrayValue(array: [Int]) -> Int {
let index = arc4random_uniform(array.count)
return array[index]
}
let arrayOfInt = [10,20,30]
print("Random Int: \(randomArrayValue(array: arrayOfInt))")
//"Random Int: 10"
//"Random Int: 20"
//"Random Int: 30"
For these three lines of code in your questions:
let y = Double(arc4random_uniform(100)) + 50.0
let size = Double(arc4random_uniform(5)) + 1
entries.append(ChartEntry(x: Double(index) + 0.5, y: y, size: CGFloat(size)))
y is a random variable between 50 and 149
size is a random variable between 1 and 5
you then add an item onto an array that goes onto a chart. The value being added specifies the x location (the index) and the y location (the random y value). Size is some code specific requirement, which we wouldn't be able to help with without seeing the functionality.

Generate random numbers with a given distribution

Check out this question:
Swift probability of random number being selected?
The top answer suggests to use a switch statement, which does the job. However, if I have a very large number of cases to consider, the code looks very inelegant; I have a giant switch statement with very similar code in each case repeated over and over again.
Is there a nicer, cleaner way to pick a random number with a certain probability when you have a large number of probabilities to consider? (like ~30)
This is a Swift implementation strongly influenced by the various
answers to Generate random numbers with a given (numerical) distribution.
For Swift 4.2/Xcode 10 and later (explanations inline):
func randomNumber(probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = probabilities.reduce(0, +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = Double.random(in: 0.0 ..< sum)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in probabilities.enumerated() {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
Examples:
let x = randomNumber(probabilities: [0.2, 0.3, 0.5])
returns 0 with probability 0.2, 1 with probability 0.3,
and 2 with probability 0.5.
let x = randomNumber(probabilities: [1.0, 2.0])
return 0 with probability 1/3 and 1 with probability 2/3.
For Swift 3/Xcode 8:
func randomNumber(probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = probabilities.reduce(0, +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = sum * Double(arc4random_uniform(UInt32.max)) / Double(UInt32.max)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in probabilities.enumerated() {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
For Swift 2/Xcode 7:
func randomNumber(probabilities probabilities: [Double]) -> Int {
// Sum of all probabilities (so that we don't have to require that the sum is 1.0):
let sum = probabilities.reduce(0, combine: +)
// Random number in the range 0.0 <= rnd < sum :
let rnd = sum * Double(arc4random_uniform(UInt32.max)) / Double(UInt32.max)
// Find the first interval of accumulated probabilities into which `rnd` falls:
var accum = 0.0
for (i, p) in probabilities.enumerate() {
accum += p
if rnd < accum {
return i
}
}
// This point might be reached due to floating point inaccuracies:
return (probabilities.count - 1)
}
Is there a nicer, cleaner way to pick a random number with a certain probability when you have a large number of probabilities to consider?
Sure. Write a function that generates a number based on a table of probabilities. That's essentially what the switch statement you've pointed to is: a table defined in code. You could do the same thing with data using a table that's defined as a list of probabilities and outcomes:
probability outcome
----------- -------
0.4 1
0.2 2
0.1 3
0.15 4
0.15 5
Now you can pick a number between 0 and 1 at random. Starting from the top of the list, add up probabilities until you've exceeded the number you picked, and use the corresponding outcome. For example, let's say the number you pick is 0.6527637. Start at the top: 0.4 is smaller, so keep going. 0.6 (0.4 + 0.2) is smaller, so keep going. 0.7 (0.6 + 0.1) is larger, so stop. The outcome is 3.
I've kept the table short here for the sake of clarity, but you can make it as long as you like, and you can define it in a data file so that you don't have to recompile when the list changes.
Note that there's nothing particularly specific to Swift about this method -- you could do the same thing in C or Swift or Lisp.
This seems like a good opportunity for a shameless plug to my small library, swiftstats:
https://github.com/r0fls/swiftstats
For example, this would generate 3 random variables from a normal distribution with mean 0 and variance 1:
import SwiftStats
let n = SwiftStats.Distributions.Normal(0, 1.0)
print(n.random())
Supported distributions include: normal, exponential, binomial, etc...
It also supports fitting sample data to a given distribution, using the Maximum Likelihood Estimator for the distribution.
See the project readme for more info.
You could do it with exponential or quadratic functions - have x be your random number, take y as the new random number. Then, you just have to jiggle the equation until it fits your use case. Say I had (x^2)/10 + (x/300). Put your random number in, (as some floating-point form), and then get the floor with Int() when it comes out. So, if my random number generator goes from 0 to 9, I have a 40% chance of getting 0, and a 30% chance of getting 1 - 3, a 20% chance of getting 4 - 6, and a 10% chance of an 8. You're basically trying to fake some kind of normal distribution.
Here's an idea of what it would look like in Swift:
func giveY (x: UInt32) -> Int {
let xD = Double(x)
return Int(xD * xD / 10 + xD / 300)
}
let ans = giveY (arc4random_uniform(10))
EDIT:
I wasn't very clear above - what I meant was you could replace the switch statement with some function that would return a set of numbers with a probability distribution that you could figure out with regression using wolfram or something. So, for the question you linked to, you could do something like this:
import Foundation
func returnLevelChange() -> Double {
return 0.06 * exp(0.4 * Double(arc4random_uniform(10))) - 0.1
}
newItemLevel = oldItemLevel * returnLevelChange()
So that function returns a double somewhere between -0.05 and 2.1. That would be your "x% worse/better than current item level" figure. But, since it's an exponential function, it won't return an even spread of numbers. The arc4random_uniform(10) returns an int from 0 - 9, and each of those would result in a double like this:
0: -0.04
1: -0.01
2: 0.03
3: 0.1
4: 0.2
5: 0.34
6: 0.56
7: 0.89
8: 1.37
9: 2.1
Since each of those ints from the arc4random_uniform has an equal chance of showing up, you get probabilities like this:
40% chance of -0.04 to 0.1 (~ -5% - 10%)
30% chance of 0.2 to 0.56 (~ 20% - 55%)
20% chance of 0.89 to 1.37 (~ 90% - 140%)
10% chance of 2.1 (~ 200%)
Which is something similar to the probabilities that other person had. Now, for your function, it's much more difficult, and the other answers are almost definitely more applicable and elegant. BUT you could still do it.
Arrange each of the letters in order of their probability - from largest to smallest. Then, get their cumulative sums, starting with 0, without the last. (so probabilities of 50%, 30%, 20% becomes 0, 0.5, 0.8). Then you multiply them up until they're integers with reasonable accuracy (0, 5, 8). Then, plot them - your cumulative probabilities are your x's, the things you want to select with a given probability (your letters) are your y's. (you obviously can't plot actual letters on the y axis, so you'd just plot their indices in some array). Then, you'd try find some regression there, and have that be your function. For instance, trying those numbers, I got
e^0.14x - 1
and this:
let letters: [Character] = ["a", "b", "c"]
func randLetter() -> Character {
return letters[Int(exp(0.14 * Double(arc4random_uniform(10))) - 1)]
}
returns "a" 50% of the time, "b" 30% of the time, and "c" 20% of the time. Obviously pretty cumbersome for more letters, and it would take a while to figure out the right regression, and if you wanted to change the weightings you're have to do it manually. BUT if you did find a nice equation that did fit your values, the actual function would only be a couple lines long, and fast.

Resources