-2

I have an array of values like [0.75, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0040000000000000001, ...] and I need to remove the duplicates. I only want to focus on the first 3 digits after the decimal point. How do I do this?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
loltospoon
  • 239
  • 1
  • 9

3 Answers3

3

Your problem strongly suggests you're using the wrong type for your data. Rather than trying to fix it up at the point of uniquing, I suspect you really just want to modify your model so the issue doesn't occur.

If you want to do decimal-based math, you should use decimal-based numbers, like NSDecimalNumber. For example, considering the case where you do have doubles coming into the system, you can convert them to NSDecimalNumber with a "0.001 accuracy" this way:

let values = [0.75, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0040000000000000001]

let behavior = NSDecimalNumberHandler(roundingMode: .plain,
                                      scale: 3,
                                      raiseOnExactness: false,
                                      raiseOnOverflow: false,
                                      raiseOnUnderflow: false,
                                      raiseOnDivideByZero: false)

let decimalNumbers = values.map {
    NSDecimalNumber(value: $0).rounding(accordingToBehavior: behavior)
}

let uniqueDecimals = Set(decimalNumbers)

Once you are working with NSDecimalNumber, and applying the appropriate rounding rules, then most operations work as you expect. You can just put them into a Set to unique them. You can check for equality. You can print them, and they will behave like decimal numbers. Make your model match your meaning, and most other problems disappear.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    Thank you. I got completely lost looking for that init. – Rob Napier May 01 '17 at 00:17
  • 1
    Completely unrelated, but I just watched your [`Do iOS 2016`](https://www.youtube.com/watch?v=_S6UOrwS-Tg) talk. I loved it, and it's going to be my go-to for explaining sum-types vs product-types, and why people shouldn't use parallel arrays to store data members that should be bound together in a struct. Great work! :) – Alexander May 01 '17 at 01:08
1

You can use NumberFormatter to fix the minimum and maximum fraction digits and use a set to filter the duplicate elements:

let array = [0.75, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0040000000000000001]

let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.minimumFractionDigits = 3
numberFormatter.maximumFractionDigits = 3

var set = Set<String>()
let orderedSet: [Double] = array.flatMap {
    guard let string = numberFormatter.string(for: $0) else { return nil }
    return set.insert(string).inserted ? $0 : nil
}

orderedSet   // [0.75, 0.005, 0.004]

If you need Strings (as suggested by @Hamish):

var set = Set<String>()
let orderedSet: [String] = array.flatMap {
    guard let string = numberFormatter.string(for: $0) else { return nil }
    return set.insert(string).inserted ? string : nil
}

orderedSet   // ["0.750", "0.005", "0.004"]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

Your problem is a little bit complicated as your equality for Doubles is based on the first 3 digits after the decimal point. Many threads describe about removing duplicates where simple equality applies, but I cannot find one including Doubles with comparison in your question.

You usually use Set to eliminate duplicates, but Set<Double> uses strict equality which does not fulfill your requirement.

Normalizing the value may work in your case:

extension Double {
    var my_normalized: Double {
        return (self * 1000).rounded() / 1000
    }
}

print(0.0050000000000000001.my_normalized == 0.0051.my_normalized) //->true

Using this, you can write something like this:

let arr = [0.75, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0050000000000000001, 0.0040000000000000001 /*,...*/]

var valueSet: Set<Double> = []
var result: [Double] = []
arr.forEach {value in
    let normalizedValue = value.my_normalized
    if !valueSet.contains(normalizedValue) {
        valueSet.update(with: normalizedValue)
        result.append(value)
    }
}
print(result) //->[0.75, 0.0050000000000000001, 0.0040000000000000001]

If you do not mind the order of the result and it can contain normalized value, the code can be simpler:

let simpleResult = Array(Set(arr.map {$0.my_normalized}))
print(simpleResult) //->[0.75, 0.0050000000000000001, 0.0040000000000000001]
OOPer
  • 47,149
  • 6
  • 107
  • 142