How to sum NSNumber array in Swift? - swift

How to sum NSNumber array in Swift? I'm getting this error when I use .reduce. any other way to sum the NSNumber array?
Cannot invoke 'reduce' with an argument list of type '(Int, _)
var record = [Record]()
var incomeFilter: [Record] = []
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "¥"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
incomeFilter = record.filter { $0.recordtype!.contains("Income") && $0.createdAt! == recordItem.createdAt!}
let incomeSumArray = incomeFilter.map{formatter.number(from: $0.amount!) ?? 0.00}.reduce(0, +)
//Cannot invoke 'reduce' with an argument list of type '(Int, _)
print(incomeSumArray)
//result without .reduce
//[9.99, 6.58, 7777.77]

I think the problem is using ?? 0 with an optional of type NSNumber. You always need the same type when nil-coalescing. You have to convert NSNumber to a numeric value (double) first:
let incomeSum = incomeFilter
.map {
formatter.number(from: $0.amount!)?.doubleValue ?? 0
}
.reduce(0, +)
or
let incomeSum = incomeFilter
.map {
guard
let amountString = $0.amount,
let number = formatter.number(from: amountString)
else {
return 0
}
return number.doubleValue
}
.reduce(0, +)

You could map the incomeFilter to be an array of doubles (incomeFilter.map{formatter.number(from: $0.amount!) ?? 0.00}) before reducing it:
let incomeSumArray = incomeFilter.map {
formatter.number(from: $0.amount!) ?? 0.00
}.map {
$0.doubleValue
}.reduce(0, +)

Related

Convert string into currency number format, with, without or with 0 decimal places

I have a string that always converted into something like this where the user inputs a number and always starting in the decimal places,
So I have 0.01 -> 0.10 -> 1.00
but I don't want something like that, I want to convert only what the user has typed
here's my existing code that convert 100000 into 1,000.00
func convertme(string: String) -> String{
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = ""
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = string
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, string.count), withTemplate: "")
print("amountWithPrefix", amountWithPrefix)
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
expected result:
I want to to format the number on the string without adding additional data, I want to turn (100000. into 100,000.) (100000.0 into 100,000.0
I want my 100000 be converted into 100,000, and only going to have a decimal if the user inputed a decimal too, so when the user inputted 100000.00 it will be converted into 100,000.00.
PS. I have a regex there that accepts only number but not the decimal, how can I make it also accept decimal?
You can simply filter non digits or periods from the original string, try to coerce the resulting string to integer. If successful set the formatter maximum fraction digits to zero otherwise set the maximum fraction digits to 2 and coerce the string to double:
extension Formatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.locale = .init(identifier: "en_US_POSIX")
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = ""
return formatter
}()
}
extension Numeric {
var currencyUS: String {
Formatter.currency.string(for: self) ?? ""
}
}
func convertme(string: String) -> String {
let string = string.filter("0123456789.".contains)
if let integer = Int(string) {
Formatter.currency.maximumFractionDigits = 0
return Formatter.currency.string(for: integer) ?? "0"
}
Formatter.currency.maximumFractionDigits = 2
return Double(string)?.currencyUS ?? "0"
}
convertme(string: "100000") // "100,000"
convertme(string: "100000.00") // "100,000.00"
edit/update:
"100,000." it is not a valid number format. You would need to manually insert your period at the end of the string.
func convertme(string: String) -> String {
var string = string.filter("0123456789.".contains)
// this makes sure there is only one period and keep only the last one in the string
while let firstIndex = string.firstIndex(of: "."),
let _ = string[firstIndex...].dropFirst().firstIndex(of: ".") {
string.remove(at: firstIndex)
}
// get the index of the period in your string
if let index = string.firstIndex(of: ".") {
// get the fraction digits count and set the number formatter appropriately
let fractionDigits = string[index...].dropFirst().count
Formatter.currency.minimumFractionDigits = fractionDigits
Formatter.currency.maximumFractionDigits = fractionDigits
// Number Formatter wont add a period at the end of the string if there is no fractional digits then you need to manually add it yourself
if fractionDigits == 0 {
return (Double(string)?.currencyUS ?? "0") + "."
}
} else {
// in case there is no period set the fraction digits to zero
Formatter.currency.minimumFractionDigits = 0
Formatter.currency.maximumFractionDigits = 0
}
return Double(string)?.currencyUS ?? "0"
}
Playground Testing:
convertme(string: "100000") // "100,000"
convertme(string: "100000.") // "100,000."
convertme(string: "100000.0") // "100,000.0"
convertme(string: "100000.00") // "100,000.00"
convertme(string: "100000.000") // "100,000.000"

Extracting currency value from a formatted string in Swift

I have the following string, from which I want to remove the currency formatting and extract the numeric value for manipulation:
"Product Price":"\u00a3314.95",
I've tried using the following code:
let productvalue = model[indexPath.row].productPrice ?? ""
let prodval = productvalue.replacingOccurrences(of: "\u00a3", with: "")
let proqty = model[indexPath.row].quantity ?? ""
let totalprice = (Int(prodval)) * (Int(proqty))
However, when I run this code, I am getting the following response from an API:
Binary operator '*' cannot be applied to two 'Int?' operands
The problem is that the init methods you use returns an optional value so you need to include a value in case the conversion from String to Int returns nil like
let value = Int(someString) ?? 0
but in your case you are dealing with decimal values so you need to convert to Double
let totalprice = (Double(prodval) ?? 0.0) * (Double(proqty) ?? 0.0)
You can use this extension that will help you remove currency symbols from the amount.
Replace your desired currency with $ for example use € instead of unicode.
extension String {
func removeFormatAmount() -> Double {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .currency
formatter.currencySymbol = "$"
formatter.decimalSeparator = ","
return formatter.number(from: self) as Double? ?? 0
}
}
How to use this extension.
let currencyString = "$1,000.00"
let amount = currencyString.removeFormatAmount() // 1000.0
You can convert currency string to decimal
let str = "£300"
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "GBP"
if let number = formatter.number(from: str) {
let amount = number.decimalValue
print(amount)
}
If you look closely the constructors of integers from string return optional values. So what you have is:
let totalprice = {
let a: Int? = Int(prodval)
let b: Int? = Int(proqty)
return a*b // Error
}()
a quick fix is to force-unwrap it using ! resulting in let totalprice = (Int(prodval))! * (Int(proqty))! but I would not suggest it because it may crash your app.
A but more elegant solution is to use defaults:
let totalprice = {
let a: Int = Int(prodval) ?? 0
let b: Int = Int(proqty) ?? 0
return a*b
}()
But on the other hand why are you even using integers here? What if the price is not a whole number? I suggest you rather use decimal numbers to handle these cases:
let a = NSDecimalNumber(string: prodval)
let b = NSDecimalNumber(string: proqty)
let totalprice = a.multiplying(by: b)
This is now working with decimal numbers directly. To get a double value or integer value you would simply need to use it's properties totalprice.doubleValue or totalprice.intValue. But there is no need for that either. If you need to convert it back to string simply use formatters:
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "$"
let finalOutput: String = formatter.string(from: totalprice)
There are many possible solutions to this and if possible I would try to do it all with formatters and decimal numbers. For instance in your case something like the following might do the trick:
private func generateFormatter(currencySymbol: String = "$", decimalSeparator: String = ".") -> NumberFormatter {
let formatter = NumberFormatter()
formatter.currencySymbol = currencySymbol
formatter.decimalSeparator = decimalSeparator
formatter.numberStyle = .currency
return formatter
}
private func parseValue(_ input: String?, formatterInfo: (currencySymbol: String, decimalSeparator: String)) -> NSNumber? {
guard let input = input else { return nil }
let formatter = generateFormatter(currencySymbol: formatterInfo.currencySymbol, decimalSeparator: formatterInfo.decimalSeparator)
return formatter.number(from: input)
}
private func multiplyValues(_ values: [String?], formatterInfo: (currencySymbol: String, decimalSeparator: String)) throws -> NSNumber {
return try values.reduce(NSDecimalNumber(value: 1.0)) { result, value in
guard let parsedValue = parseValue(value, formatterInfo: formatterInfo) else {
throw NSError(domain: "Parsing values", code: 400, userInfo: ["dev_message": "Could not parse a value \(value ?? "[Null value]")"])
}
return NSDecimalNumber(decimal: result.decimalValue).multiplying(by: NSDecimalNumber(decimal: parsedValue.decimalValue))
}
}
let values = ["$1.2", "$1.6", "$2"]
let result = try? multiplyValues(values, formatterInfo: ("$", "."))
let parsedResult: String = {
guard let result = try? multiplyValues(values, formatterInfo: ("$", ".")) else { return "Could not produce result" }
return generateFormatter(currencySymbol: "$", decimalSeparator: ".").string(from: result) ?? "Could not format result"
}()
print(result ?? "No result")
print(parsedResult)
I hope the code speaks for itself and you can see it is easy to change/inject different formats/symbols.

Is there a way to sort based on an optional property [duplicate]

How can I sort an array of optionals that holds an optional NSdate?
class HistoryItem {
var dateCompleted: NSDate?
}
let firstListObject = someListOfObject.last
let secondListObject = someOtherListOfObject.last
let thirdListObject = evenSomeOtherListOfObject.last //Last returns 'T?'
var array = [firstListObject , secondListObject, thirdListObject]
How can I sort array based on dateCompleted?
Your sort function could use a combination of optional chaining and the nil
coalescing operator:
sort(&array) {
(item1, item2) -> Bool in
let t1 = item1?.dateCompleted ?? NSDate.distantPast() as! NSDate
let t2 = item2?.dateCompleted ?? NSDate.distantPast() as! NSDate
return t1.compare(t2) == NSComparisonResult.OrderedAscending
}
This would sort the items on the dateCompleted value, and all items that
are nil and items with dateCompleted == nil are treated as "in the distant past"
so that they are ordered before all other items.
Update for Swift 3 (assuming that dateCompleted is a Date):
array.sort { (item1, item2) -> Bool in
let t1 = item1?.dateCompleted ?? Date.distantPast
let t2 = item2?.dateCompleted ?? Date.distantPast
return t1 < t2
}
Swift 4. if you want to keep optional values at the end, for example, use Int.max:
self.values.sort { (item1, item2) -> Bool in
let value1 = item1.seconds ?? Int.max
let value2 = item2.seconds ?? Int.max
return value1 < value2
}

Get a Decimal from a String

I have a problem to get a Decimal here.
I have tried this code but the results is 9.0 , How can i get 0.9 ?
let distances = "0.9 mil"
let stratr = distances.characters.split{$0 == " "}.map(String.init)
for item in stratr {
let components = item.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
let part = components.joined(separator: "")
if let doubVal = Double(part) {
print("this is a number -> \(doubVal)")
}
You can separate the string by the space character and then initialize a Float using the first component.
let str = "0.9 mil"
let decimal = str.components(separatedBy: " ").first.flatMap { Float($0) }
print(decimal) // 0.9
The String struct provides an instance method that can be used to remove characters based on a given CharacterSet. In this case, you can use the letters and whitespaces character sets to isolate your decimal value and then create a Decimal from it.
let distances = "0.9 mil"
let decimal = Decimal(string: distances.trimmingCharacters(in: CharacterSet.letters.union(.whitespaces)))
if let decimal = decimal {
print(decimal) // Prints 0.9
}
extension String {
/// "0.9 mil" => "0.9"
var decimals: String {
return trimmingCharacters(in: CharacterSet.decimalDigits.inverted)
}
/// "0.9" => 0.9
var doubleValue: Double {
return Double(self) ?? 0
}
}
Usage:
let distance = "0.9 mil"
print(distance.decimals) // "0.9"
print(distance.decimals.doubleValue) // 0.9
print(distance.doubleValue) // 0 (because Double("0.9 mil") => nil)
Never mind i find the Answer
let distances = "0.9 mil"
let stratr = distances.characters.split{$0 == " "}.map(String.init)
for item in stratr {
let components = item.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
let part = components.joined(separator: ".")
if let doubVal = Double(part) {
print("this is a number -> \(doubVal)")
}
I think when i set Joined(separator : ".") it will joined the String with an "." as separator

NSNumberFormatter : Show 'k' instead of ',000' in large numbers?

I'd like to change my large numbers from 100,000 to $100K if this is possible.
This is what I have so far:
let valueFormatter = NSNumberFormatter()
valueFormatter.locale = NSLocale.currentLocale()
valueFormatter.numberStyle = .CurrencyStyle
valueFormatter.maximumFractionDigits = 0
My Question
Using NSNumberFormatter, how can I output $100K rather than $100,000?
My original question:
This is what I have so far:
self.lineChartView.leftAxis.valueFormatter = NSNumberFormatter()
self.lineChartView.leftAxis.valueFormatter?.locale = NSLocale.currentLocale()
self.lineChartView.leftAxis.valueFormatter?.numberStyle = .CurrencyStyle
self.lineChartView.leftAxis.valueFormatter?.maximumFractionDigits = 0
Which Translates to:
let valueFormatter = NSNumberFormatter()
valueFormatter.locale = NSLocale.currentLocale()
valueFormatter.numberStyle = .CurrencyStyle
valueFormatter.maximumFractionDigits = 0
My output looks like this:
My Question
Using NSNumberFormatter, how can I output $100K rather than $100,000?
update:
I wanted to provide context as to whats going on, watch comments.
func setDollarsData(months: [String], range: Double) {
var dataSets: [LineChartDataSet] = [LineChartDataSet]()
var yVals: [ChartDataEntry] = [ChartDataEntry]()
for var i = 0; i < months.count; i++ {
// I'm adding my values here in value:, value takes a Double
yVals.append(ChartDataEntry(value: county[userFavs[0]]![i], xIndex: i))
}
let set1: LineChartDataSet = LineChartDataSet(yVals: yVals, label: self.userFavs[0])
set1.axisDependency = .Left
set1.setColor(UIColor.redColor().colorWithAlphaComponent(0.5))
set1.setCircleColor(UIColor.redColor())
set1.lineWidth = 2.0
set1.circleRadius = 6.0
set1.fillAlpha = 65 / 255.0
dataSets.append(set1)
let data: LineChartData = LineChartData(xVals: months, dataSets: dataSets)
data.setValueTextColor(UIColor.whiteColor())
// this is where I set the number formatter
self.lineChartView.gridBackgroundColor = UIColor.darkGrayColor()
self.lineChartView.leftAxis.startAtZeroEnabled = false
self.lineChartView.leftAxis.valueFormatter = NSNumberFormatter()
self.lineChartView.leftAxis.valueFormatter?.locale = NSLocale.currentLocale()
self.lineChartView.leftAxis.valueFormatter?.numberStyle = .CurrencyStyle
self.lineChartView.leftAxis.valueFormatter?.maximumFractionDigits = 0
// set it to the chart // END OF THE LINE
self.lineChartView.data = data // outputs to my chart
}
As you can see, once I dump the numbers into yVals, I lose access to them so those extensions will only work if I hack into the framework.
edit/update
Swift 3 or later
extension FloatingPoint {
var kFormatted: String {
return String(format: self >= 1000 ? "$%.0fK" : "$%.0f", (self >= 1000 ? self/1000 : self) as! CVarArg )
}
}
The you can use it like this to format your output:
10.0.kFormatted // "$10"
100.0.kFormatted // "$100"
1000.0.kFormatted // "$1K"
10000.0.kFormatted // "$10K"
162000.0.kFormatted // "$162K"
153000.0.kFormatted // "$153K"
144000.0.kFormatted // "$144K"
135000.0.kFormatted // "$135K"
126000.0.kFormatted // "$126K"
I've bumped into the same issue and solved it by implementing a custom formatter. Just started coding in Swift, so the code might not be the most idiomatic.
open class KNumberFormatter : NumberFormatter {
override open func string(for obj: Any?) -> String? {
if let num = obj as? NSNumber {
let suffixes = ["", "k", "M", "B"]
var idx = 0
var d = num.doubleValue
while idx < 4 && abs(d) >= 1000.0 {
d /= 1000.0
idx += 1
}
var currencyCode = ""
if self.currencySymbol != nil {
currencyCode = self.currencySymbol!
}
let numStr = String(format: "%.1f", d)
return currencyCode + numStr + suffixes[idx]
}
return nil
}
}
I think you can add an extension to NSNumberFormatter. Try the following, I didn't test it so let me know in the comment if it needs to be edited
extension NSNumberFormatter {
func dividedByK(number: Int)->String{
if (number % 1000) == 0 {
let numberK = Int(number / 1000)
return "\(numberK)K"
}
return "\(number)"
}
}