NSNumberFormatter to display custom labels for 10^n (10000 -> 10k) - iphone

I need to display numbers on a plot axis. The values could change but I want to avoid too long numbers that will ruin the readability of the graph.
My thought was to group every 3 characters and substitute them with K, M and so on (or a custom character).
So:
1 -> 1,
999 -> 999,
1.000 -> 1k,
1.200 -> 1.2k,
1.280 -> 1.2k,
12.800 -> 12.8k,
999.999 -> 999.9k,
1.000.000 -> 1M,
...
Note that probably I'll only need to format round numbers (1, 10, 1000, 1500, 2000, 10000, 20000, 30000, 100000, ...).
Is that possibile with NSNumberFormatter? I saw that it has a setFormat method but I don't know how much customizable it is.
I'm using NSNumberFormatter cause the graph object I use wants it to set label format and I want to avoid changing my data to set the label.

You can use this code:
let formatter = NSNumberFormatter()
formatter.multiplier = 0.001
formatter.positiveFormat = "#,###k"
formatter.zeroSymbol = "0"
return formatter
It helped me to convert the currency values:
2000 -> 2k
10000 -> 10k

No. The closest you can get is Scientific Notation. Have a look here for how to create a format for that. You could obviously quite easily do the k, M etc substitution yourself though.

Related

Set/sequence summation operator?

I have a set, S = { 1, 2, 3, 4, 5 }.
If I wanted to sum this in standard logic it's just ∑S (no MathJax on SO so I can't format this nicely).
What's the VDM equivalent? I don't see anything in the numerics/sets section of the language reference.
There isn't a standard library function to do this (though perhaps there should be). You would sum a set with a simple recursive function:
sum: set of nat +> nat
sum(s) ==
if s = {}
then 0
else let e in set s in
e + sum(s \ {e})
measure card s;
The "let" selects an arbitrary element from the set, and then add that to the sum of the remainder. The measure says that the recursion always deals with smaller sets.
This should work:
sum(S)
But you could find this very easily.

iOS-Charts Library: x-axis labels without backing data not showing

I am using version 3.1.1 of the popular charts library for iOS. I have run into an issue with x-axis labeling that I can't seem to find the answer for online:
Let's say I want to have a chart with one x-axis label for every day of the week (namely: S, M, T, W, T, F, S). Lots of forums I've read suggest taking the approach of setting a custom value formatter on the x-axis as suggested here: https://github.com/danielgindi/Charts/issues/1340
This works for calculating labels on days for which I have data. The issue I'm running into with this approach is that if I don't have data for a specific day, then the label for that day won't get generated.
For example, if I were to use a custom value formatter that looked like this:
public class CustomChartFormatter: NSObject, IAxisValueFormatter {
var days: = ["S", "M", "T", "W", "T", "F", "S"]
public func stringForValue(value: Double, axis: AxisBase?) -> String {
return days[Int(value)]
}
}
and my backing data looked like this: [(0, 15.5), (1, 20.1), (6, 11.1)] where 0, 1, and 6 are representations of days, and 15.5, 20.1, and 11.1 are the data points on those days, then when stringForValue is called, some of the days will never get labels generated for them.
Since value is always based on that backing data, it will never be equal to 2, 3, 4, or 5 in this scenario. As such, labels for "T", "W", "T", and "F" are never generated.
Does anyone know how to force the library to generate 7 labels, one for each day of the week, regardless of what my backing data is? Thank you kindly.
Ok so thanks to #wingzero 's comment, I have been able to get this working. There are a few things required to do so. For simplicity's sake, I am going to explain how to get the "days of the week" labels working as I originally asked. If you follow these steps, however, you should be able to tweak them to format your chart however you like (for example, with months of the year).
1) Make sure that your chart's x-axis minimum and maximum values are set. In this case, you'd want to say: chartView.xAxis.axisMinimum = 0.0 and chartView.axisMaximum = 6.0. This is important for step 2.
2) As Wingzero alluded to, create a subclass of XAxisRenderer that allows us to grab the minimum and maximum values set in step one and determine what values should be passed to our IAxisValueFormatter subclass in step three. In this case:
class XAxisWeekRenderer: XAxisRenderer {
override func computeAxis(min: Double, max: Double, inverted: Bool) {
axis?.entries = [0, 1, 2, 3, 4, 5, 6]
}
}
Make sure to pass this renderer to your chart like this: chartView.xAxisRenderer = XAxisWeekRenderer()
3) Create a subclass of IAxisValueFormatter that takes the values we passed to the chart in step two ([0, 1, 2, 3, 4, 5, 6]) and gets corresponding label names. This is what I did in my original question here. To recap:
public class CustomChartFormatter: NSObject, IAxisValueFormatter {
var days: = ["S", "M", "T", "W", "T", "F", "S"]
public func stringForValue(value: Double, axis: AxisBase?) -> String {
return days[Int(value)]
}
}
4) Set the labelCount on your graph to be equal to the number of labels you want. In this case, it would be 7. I show how to do this, along with the rest of the steps, below the last step here.
5) Force the labels to be enabled
6) Force granularity on the chart to be enabled and set granularity to 1. From what I understand, setting the granularity to 1 means that if the data your chart passes to stringForValue is not in round numbers, the chart will essentially round said data or treat it like it is rounded. This is important since if you passed in 0.5, it's possible that your stringForValue might not produce the right strings for your labels.
7) Set the value formatter on the xAxis to be the custom formatter you created in step 3.
Steps 4-7 (plus setting the formatter created in step 3) are shown below:
chartView.xAxis.labelCount = 7
chartView.xAxis.forceLabelsEnabled = true
chartView.xAxis.granularityEnabled = true
chartView.xAxis.granularity = 1
chartView.xAxis.valueFormatter = CustomChartFormatter()
First, have you debugged return days[Int(value)] on your side? From your screenshot, it seems obvious that your value after int cast looses the precision. e.g. 2.1 and 2.7 will be 2, which always shows you T. You have to look at your value first.
If you are sure you only get 7 xaxis labels all the time, a tricky way is to force computeAxisValues to have [0,1,2,3,4,5,6] all the time.
Meaning, you make sure your data x range is [1,7] (or [0,6]), and in #objc open func computeAxisValues(min: Double, max: Double), you should be able to see min is 1 and max is 7.
Then you override this method to set axis.entries = [Double]() to be [0,1,2,3,4,5,6], without any calculation. This should gives you the correct mapping.
However, before doing this, I suggest you take some time to debug this method first, to understand why you didn't get the expected values.

Swift 3 casting or NumberFormatter

Is there any advantage to using a NumberFormatter over simply casting?
For example 1 and 2 below both seem to do the same job, but casting seems to be easier to read so why would the NumberFormatter often be used in code samples I've found?
var displayValue: Double? {
get {
if let text = display.text,
// 1 let value = NumberFormatter().number(from: text)?.doubleValue
// 2 let value = Double(text)
}
Huge difference. If you want something to handle:
Thousand-digit separator and decimal places: one million is commonly written as 1,000,000 in the US, 1.000.000 in France, and 10,00,000 in India.
Currency symbol: $100 or £100 or €100
Negative numbers in the accounting style, for example -100 is written as (100) (and often in red too)
Percentage: 5% → 0.05
Word spelling: "one hundred six" → 106
etc... then use NumberFormatter.

Is there a range data structure in Scala?

I'm looking for a way to handle ranges in Scala.
What I need to do is:
given a set of ranges and a range(A) return the range(B) where range(A) intersect range (B) is not empty
given a set of ranges and a range(A) remove/add range(A) from/to the set of ranges.
given range(A) and range(B) create a range(C) = [min(A,B), max(A,B)]
I saw something similar in java - http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/RangeSet.html
Though subRangeSet returns only the intersect values and not the range in the set (or list of ranges) that it intersects with.
RangeSet rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(0, 10));
rangeSet.add(Range.closed(30, 40));
Range range = Range.closed(12, 32);
System.out.println(rangeSet.subRangeSet(range)); //[30,32] (I need [30,40])
System.out.println(range.span(Range.closed(30, 40))); //[12,40]
There is an Interval[A] type in the spire math library. This allows working with ranges of arbitrary types that define an Order. Boundaries can be inclusive, exclusive or omitted. So e.g. (-∞, 0.0] or [0.0, 1.0) would be possible intervals of doubles.
Here is a library intervalset for working with sets of non-overlapping intervals (IntervalSeq or IntervalTrie) as well as maps of intervals to arbitrary values (IntervalMap).
Here is a related question that describes how to use IntervalSeq with DateTime.
Note that if the type you want to use is 64bit or less (basically any primitive), IntervalTrie is extremely fast. See the Benchmarks.
As Tzach Zohar has mentioned in the comment, if all you need is range of Int - go for scala.collection.immutable.Range:
val rangeSet = Set(0 to 10, 30 to 40)
val r = 12 to 32
rangeSet.filter(range => range.contains(r.start) || range.contains(r.end))
If you need it for another underlying type - implement it by yourself, it's easy for your usecase.

Round currency closest to five

I'd like to round my values to the closest of 5 cent for example:
5.31 -> 5.30
5.35 -> 5.35
5.33 -> 5.35
5.38 -> 5.40
Currently I'm doing it by getting the decimal values using:
let numbers = 5.33
let decimal = (numbers - rint(numbers)) * 100
let rounded = rint(numbers) + (5 * round(decimal / 5)) / 100
// This results in 5.35
I was wondering if there's a better method with fewer steps because sometimes numbers - rint(numbers) is giving me a weird result like:
let numbers = 12.12
let decimal = (numbers - rint(numbers)) * 100
// This results in 11.9999999999999
Turns out..it's really simple
let x: Float = 1.03 //or whatever value, you can also use the Double type
let y = round(x * 20) / 20
It's really better to stay away from floating-point for this kind of thing, but you can probably improve the accuracy a little with this:
import Foundation
func roundToFive(n: Double) -> Double {
let f = floor(n)
return f + round((n-f) * 20) / 20
}
roundToFive(12.12) // 12.1
I will use round function and NSNumberFormatter also but slightly different algorithm
I was thinking about using % but I changed it to /
let formatter = NSNumberFormatter()
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
//5.30
formatter.stringFromNumber(round(5.31/0.05)*0.05)
//5.35
formatter.stringFromNumber(round(5.35/0.05)*0.05)
//5.35
formatter.stringFromNumber(round(5.33/0.05)*0.05)
//5.40
formatter.stringFromNumber(round(5.38/0.05)*0.05)
//12.15
formatter.stringFromNumber(round(12.13/0.05)*0.05)
Depending on how you are storing your currency data, I would recommend using a dictionary or an array to look up the original cents value, and return a pre-computed result. There's no reason to do the calculations at all, since you know that 0 <= cents < 100.
If your currency is a string input, just chop off the last couple of digits and do a dictionary lookup.
round_cents = [ ... "12":"10", "13":"15", ... ]
If your currency is a floating point value, well, you have already discovered the joys of trying to do that. You should change it.
If your currency is a data type, or a fixed point integer, just get the cents part out and do an array lookup.
...
round_cents[12] = 10
round_cents[13] = 15
...
In either case, you would then do:
new_cents = round_cents[old_cents]
and be done with it.