2

My code snippet is:

unwanted = " £€₹jetztabfromnow"

let favouritesPriceLabel = priceDropsCollectionView.cells.element(boundBy: UInt(index)).staticTexts[IPCUIAHighlightsPriceDropsCollectionViewCellPriceLabel].label
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: "jetzt").flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last

favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed

My problem is, this didn't work:

let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: unwanted).flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last

I have a price like "from 3,95 €" - I want to cut all currencies "£€₹" and words like "from" or "ab"

Do you have a solution for me, what I can use here?

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Saintz
  • 69
  • 7

5 Answers5

1

Rather than mess around with trying to replace or remove the right characters or using regular expressions, I'd go with Foundation's built-in linguistic tagging support. It will do a lexical analysis of the string and return tokens of various types. Use it on this kind of string and it should reliably find any numbers in the string.

Something like:

var str = "from 3,95 €"

let range = Range(uncheckedBounds: (lower: str.startIndex, upper: str.endIndex))

var tokenRanges = [Range<String.Index>]()

let scheme = NSLinguisticTagSchemeLexicalClass
let option = NSLinguisticTagger.Options()
let tags = str.linguisticTags(in: range, scheme: scheme, options: option, orthography: nil, tokenRanges: &tokenRanges)
let tokens = tokenRanges.map { str.substring(with:$0) }

if let numberTagIndex = tags.index(where: { $0 == "Number" }) {
    let number = tokens[numberTagIndex]
    print("Found number: \(number)")
}

In this example the code prints "3,95". If you change str to "from £28.50", it prints "28.50".

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
  • Hi, i test your version. But i have an question. I wanna put your version in a helper method. I have more than one price to check and to trim on distributed at several places. How can i make an hand over from the test to the helper and back again to get the results an work with it further. Is it possible to change it to an double? – Saintz Jun 16 '17 at 07:02
  • I'm not sure I understand. It sounds like you would want to create a `func` that contains this code, with the string as an argument. Return the result of the linguistic analysis from the `func`. Strings can be converted into numeric types, so the function can return a number instead of a string. – Tom Harrington Jun 16 '17 at 15:03
0

You can filter by special character by removing alphanumerics.

extension String {

    func removeCharacters(from forbiddenChars: CharacterSet) -> String {
        let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) }
        return String(String.UnicodeScalarView(passed))
    }
}

let str = "£€₹jetztabfromnow12"

let t1 = str.removeCharacters(from: CharacterSet.alphanumerics)
print(t1) // will print: £€₹

let t2 = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(t2) // will print: 12

Updated 1:

var str = "£3,95SS"

str = str.replacingOccurrences(of: ",", with: "")

let digit = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(digit) // will print: 395

let currency = str.removeCharacters(from: CharacterSet.alphanumerics)
print(currency) // will print: £

let amount = currency + digit
print(amount) // will print: £3,95

Update 2:

let string = "£3,95SS"

let pattern = "-?\\d+(,\\d+)*?\\.?\\d+?"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
        let range = match.range
        let start = string.index(string.startIndex, offsetBy: range.location)
        let end = string.index(start, offsetBy: range.length)
        let digit = string.substring(with: start..<end)
        print(digit) //3,95

        let symbol = string.removeCharacters(from: CharacterSet.symbols.inverted)
        print(symbol) // £

        print(symbol + digit) //£3,95

    } else {
        print("Not found")
    }
} catch {
    print("Regex Error:", error)
}
Subramanian P
  • 4,365
  • 2
  • 21
  • 25
  • Ok, if i print (t1) i will get the £€₹, but i want to get the 3,95 – Saintz Jun 15 '17 at 13:55
  • do you want to get only numbers? updated my answer to get the digits from string – Subramanian P Jun 15 '17 at 13:56
  • Will that work when the number is 3,95? Does it remove the decimal point/comma ? – koen Jun 15 '17 at 14:09
  • Ok, and how i can put the String where i get the price in it? See at my example upper there... favouritesPriceLabelTrimmed is the String with the full String like "from 3,95 €" Where should it be placed in let t2 = str.removeCharacters(from: CharacterSet.decimalDigits.inverted) – Saintz Jun 15 '17 at 14:12
  • Worked, but do you have an idea to give it back as an Double and not as an String digit? If i do this : `let favouritesPriceLabelTrimmed = favouritesPriceLabel.removeCharacters(from: CharacterSet.decimalDigits.inverted).toDouble(with: favouritesPriceLabel)` I get on : `favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed` an bug : "Ambiguous reference to member" – Saintz Jun 16 '17 at 06:28
  • My code snippet is now : `var favouritesHighlightsDictionary = [Double: Double]() favouritesPriceLabel = favouritesPriceLabel.replacingOccurrences(of: " £€₹jetztabfromnow", with: "") let favouritesPriceLabelTrimmed = favouritesPriceLabel.removeCharacters(from: CharacterSet.decimalDigits.inverted).toDouble(with: favouritesPriceLabel) print(favouritesPriceLabelTrimmed) favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed` – Saintz Jun 16 '17 at 06:29
  • `favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed` ... In this, what the data type of `favouritesTitleLabel` – Subramanian P Jun 16 '17 at 06:58
  • Change the code into `favouritesHighlightsDictionary[Double(favouritesPriceLabel)] = favouritesPriceLabelTrimmed` – Subramanian P Jun 16 '17 at 07:01
  • Now i get a bug message : "EXC_BAD_INSTRUCTION (code=EXC_i386_INVOP, subcode=0x0) – Saintz Jun 16 '17 at 10:23
  • `favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed` ... In this, what the data type of `favouritesTitleLabel` ?? – Subramanian P Jun 16 '17 at 10:52
  • Yes, it is a dictionary with two types first the title (favouritesTitleLabel) of the product and second the trimmed price (without any characters! best where the trimmed price is an double (favouritesPriceLabelTrimmed)) – Saintz Jun 16 '17 at 10:58
  • Here is the complete code snippet : `for index in 0.. – Saintz Jun 16 '17 at 11:05
  • Data type of `favouritesTitleLabel` is a string – Saintz Jun 16 '17 at 11:06
  • Ok, it worked, but now i get the "£3,95" -> "395", is it possible to get the "3,95" or the "3.95". - Or to give "£3,95" back as an double "3,9444444449" ??? That would be better for my task... – Saintz Jun 16 '17 at 11:34
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146868/discussion-between-subramanian-and-saintz). – Subramanian P Jun 16 '17 at 11:46
  • Sorry, i have my next meeting now. It worked now, i get the "395" digit, but better was to get a double as result like "3,9444444449". You know what i mean? You have an idea, what i have to change? Maybe the "...CharacterSet.decimalDigits.inverted)" to "...CharacterSet.decimalDigits, .symbols)" ?? – Saintz Jun 16 '17 at 11:56
  • You have to use the regex to grab the decimal value from string.. I will update answer – Subramanian P Jun 16 '17 at 12:05
  • Check my 2nd update. You have to use the string extension also. – Subramanian P Jun 16 '17 at 12:22
0

One way is to place the unwanted strings into an array, and use String's replacingOccurrences(of:with:) method.

let stringToScan = "£28.50"
let toBeRemoved = ["£", "€", "₹", "ab", "from"]
var result = stringToScan
toBeRemoved.forEach { result = result.replacingOccurrences(of: $0, with: "") }
print(result)

...yields "28.50".

Joshua Kaden
  • 1,210
  • 11
  • 16
0

If you just want to extract the numeric value use regular expression, it considers comma or dot decimal separators.

let string = "from 3,95 €"

let pattern = "\\d+[.,]\\d+"
do {
    let regex = try NSRegularExpression(pattern: pattern, options: [])
    if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
        let range = match.range
        let start = string.index(string.startIndex, offsetBy: range.location)
        let end = string.index(start, offsetBy: range.length)
        print(string.substring(with: start..<end)) // 3,95
    } else {
        print("Not found")
    }
} catch {
    print("Regex Error:", error)
}
vadian
  • 274,689
  • 30
  • 353
  • 361
0

I asked if you had a fixed locale for this string, because then you can use the locale to determine what the decimal separator is: For example, try this in a storyboard.

let string = "some initial text 3,95 €" // define the string to scan

// Add a convenience extension to Scanner so you don't have to deal with pointers directly.
extension Scanner {
    func scanDouble() -> Double? {
        var value = Double(0)
        guard scanDouble(&value) else { return nil }
        return value
    }

    // Convenience method to advance the location of the scanner up to the first digit. Returning the scanner itself or nil, which allows for optional chaining
    func scanUpToNumber() -> Scanner? {
        var value: NSString?
        guard scanUpToCharacters(from: CharacterSet.decimalDigits, into: &value) else { return nil }
        return self
    }
}

let scanner = Scanner(string: string)
scanner.locale = Locale(identifier: "fr_FR")
let double = scanner.scanUpToNumber()?.scanDouble() // -> double = 3.95 (note the type is Double?)

Scanners are a lot easier to use than NSRegularExpressions in these cases.

Abizern
  • 146,289
  • 39
  • 203
  • 257
  • Sorry, i saw it today, that you asked. None, it can be changed between more locales, like (FR, IT, DE, UK, ES and IN (UK)) – Saintz Jun 16 '17 at 07:16