Convert NumberFormatter.Style.spellOut to Int - swift

Try to find a way to convert plain English number (e.g., One, Two...) to Int (e.g., 1, 2...)
I know that there is way to convert from Int to English using
numberFormatter:NumberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.spellOut
var string = numberFormatter.string(from: 3) // "three"
Is there any reverse way of converting this?
I am trying to avoid using array of String like ["One", "Two"..]

Number formatters work in both directions, so you can just use:
let num = numberFormatter.number(from: string)
But you'll need to take some care to make sure it exactly matches the output of the forward direction. "three" will translate to 3, but "Three" won't.
As Sulthan notes, this is absolutely sensitive to the locale of the formatter (which you can set to be different than the user's locale if that's necessary). It is strongly assuming that the input and output from the same formatter match.

Related

How to shift a string's Range?

I have the Range of a word and its enclosing sentence within a big long String. After extracting that sentence into its own String, I'd like to know the position of the word within it.
If we were dealing with integer indexes, I would just subtract the sentence's starting index from the word's range and I'd be done. For example, if the word was in characters 10–12 and its sentence started at character 8, then I'd have a new word range of 2–4.
Here's what I've got, ready to copy&paste to a Playground:
// The Setup (this is just to get easy testing values, no need for feedback on this part)
let bigLongString = "A beginning is the time for taking the most delicate care that the balances are correct. This every sister of the Bene Gesserit knows."
let sentenceInString = bigLongString.range(of: "This every sister of the Bene Gesserit knows.")!
let wordInString = bigLongString.range(of: "sister")!
let sentence = String(bigLongString[sentenceInString])
// The Code In Question
let wordInSentence = ??? // Something that shifts the `wordInString` range
// The Test (again, just for testing. it should read "This every *sister* of the Bene Gesserit knows.")
print(sentence.replacingCharacters(in: wordInSentence,
with: "*\(sentence[wordInSentence])*"))
Also, note that wordInString may refer to any instance of a given word, not just the first one. (So, re-finding the word in sentence, i.e., sentence.range(of: "sister"), won't do the trick here unfortunately.) The range needs to be shifted somehow.
Thanks for reading!
EDIT:
Introducing a slightly more complicated bigLongString seems to be an issue with the solution I posted. E.g.,
let bigLongString = "Really…? Thought I had it."
let sentenceInString = bigLongString.range(of: "Thought I had it.")!
let wordInString = bigLongString.range(of: "I")!
This can get kinda tricky, depending on precisely what you need to do.
NSRange
Firstly, as you may have noticed, Range<String.Index> and NSRange are different.
Range<String.Index> is how Swift represent ranges of indices in native Swift.Strings. It's an opaque type, that's only usable by the String APIs that consume it. It understands Swift strings as collections of Swift.Characters, which represent what Unicode calls "extended grapheme clusters".
NSRange is the older range representation, used by Objective C to represent ranges in Foundation.NSStrings. It's an open container, containing a "start" location and a length. Importantly, these NSRange and NSString understand collections of utf16 encoded unicode scalars.
Because NSRange and NSString expose so many of their internals, they haven't undergone the same migration from utf16 to utf8 that Swift.String underwent. A migration that most people probably didn't even notice, since Swift.String guarded its implementation details much more than NSString did.
NSRange is more amenable to the kinds of simple operations you might be looking for. You can offset the start location just like you describe. However, you need to be careful that the resulting range doesn't start/end in the middle of an extended grapheme cluster. In that case, slicing could lead to a substring with invalid unicode characters (for example, you might accidentally cut an e away from its accent. the accent modifier isn't valid on its own without the e.)
Bridging back and forth between NSRange and Range<String.Index> is possible, but can be error prone if you're not careful. For that reason, I suggest you try to minimize conversions, by trying to either exclusively use NSRange, or Range<String.Index>, but not mix the two too much.
replacingCharacters(in:with:)
I suspect you're only using this as example way of consuming wordInSentence, but it's still worth noting that:
Foundation.NSString.replacingCharacters(in:with:)](https://developer.apple.com/documentation/foundation/nsstring/1412937-replacingoccurrences) is an NSString API that's imported onto Swift.String when Foundation is imported. It accept an NSString. If you're dealing with Range<String.Index>, you should use its Swift-native counterpart, Swift.String.replaceSubrange(_:with:).
Substring is your friend
Don't fight it; unless you absolutely need sentence to be a String, keep it as a Substring for the duration of these short-lived processing actions. Not only does this save you a copy of the string's contents, but it also makes it so that the indices can be shared between the slice and the parent string. This is valid:
let sentence = bigLongString[sentenceInString]
print(sentence[wordInString])
or even just: bigLongString[sentenceInString][wordInString] or bigLongString[wordInString]
Shifting around
I couldn't find a native solution for this, so I rolled my own. I could definitely be missing something simpler, but here's what I came up with:
import Foundation
struct SubstringOffset {
let offset: String.IndexDistance
let parent: String
init(of substring: Substring, in parent: String) {
self.offset = parent.distance(from: parent.startIndex, to: substring.startIndex)
self.parent = parent
}
func convert(indexInParent: String.Index, toIndexIn newString: String) -> String.Index {
let distance = parent.distance(from: parent.startIndex, to: indexInParent)
let distanceInNewString = distance - offset
return newString.index(newString.startIndex, offsetBy: distanceInNewString)
}
func convert(rangeInParent: Range<String.Index>, toRangeIn newString: String) -> Range<String.Index> {
let newLowerBound = self.convert(indexInParent: rangeInParent.lowerBound, toIndexIn: newString)
let span = self.parent.distance(from: rangeInParent.lowerBound, to: rangeInParent.upperBound)
let newUpperBound = newString.index(newLowerBound, offsetBy: span)
return newLowerBound ..< newUpperBound
}
}
// The Setup (this is just to get easy testing values, no need for feedback on this part)
let bigLongString = "Really…? Thought I had it."
let sentenceInString = bigLongString.range(of: "Thought I had it.")!
let wordInString = bigLongString.range(of: "I")!
var sentence: String = String(bigLongString[sentenceInString])
let offset = SubstringOffset(of: bigLongString[sentenceInString], in: bigLongString)
// The Code In Question
let wordInSentence: Range<String.Index> = offset.convert(rangeInParent: wordInString, toRangeIn: sentence)
sentence.replaceSubrange(wordInSentence, with: "*\(sentence[wordInSentence])*")
print(sentence)
OK, this is what I've come up with. It appears to work OK for both examples in the question.
We use the String instance method distance(from:to:) to get the distance between the bigLongString start and the sentence start. (Analogous to the "8" in the question.) Then the word range is shifted back by this amount by shifting the upper and lower bounds separately, and then reforming them into a Range.
let wordStartInSentence = bigLongString.distance(from: sentenceInString.lowerBound,
to: wordInString.lowerBound)
let wordEndInSentence = bigLongString.distance(from: sentenceInString.lowerBound,
to: wordInString.upperBound)
let wordStart = sentence.index(sentence.startIndex, offsetBy: wordStartInSentence)
let wordEnd = sentence.index(sentence.startIndex, offsetBy: wordEndInSentence)
let wordInSentence = wordStart..<wordEnd
EDIT: Updated answer to work for the more complicated bigLongString example (and coincidentally also reduce the "string walking," especially when bigLongString is very big).

How to convert NSString to float without rounding it?

I have tried the following but when I convert the string it is automatically rounded.
let str = "10,60"
let str2 = (str as NSString).floatValue //prints "10.0"
//What I would like to do
let str2 = (str as NSSTring).floatValueNotRounded //prints "10,60"
.floatValue does not handle local formats and your number uses a comma as the decimal point - the the parse just stops at the comma and you get 10. Use either NumberFormatter or Scanner to parse localised numbers. E.g.:
let formatter = NumberFormatter()
let val = formatter.number(from: str)
should work provided your locale uses the comma as the decimal point. If you are in one locale and wish to parse numbers written according to another you can set locale property of the formatter.

How do you get integer values in an NSPoint to display as integer rather than as decimal

How do you get integer values in an NSPoint to display as integer rather than as decimal?
let temp:NSPoint = NSMakePoint(5,7)
print(temp.x) . // displays 5.0
I only want it to display 5, not 5.0
You ask:
How do you get integer values in an NSPoint to display as integer rather than as decimal.
This question may be based upon a false premise. The parameters you supply to NSMakePoint(_:_:) are actually CGFloat (as are the the underlying properties, x and y). So although you supplied integers, but Swift stores them as CGFloat for x and y, regardless.
So, the question is “How do you display the CGFloat properties of a NSPoint without any fractional digits?”
Certainly, as others have suggested, you can create an integer from the CGFloat properties with Int(point.x), etc., but I might suggest that you really might want want to just display these CGFloat without decimal places. There are a bunch of ways of doing this.
Consider:
let point = NSPoint(x: 5000, y: 7000)
You can use NSMakePoint, too, but this is a little more natural in Swift.
Then to display this without decimal places, you have a few options:
In macOS, NSStringFromPoint will generate the full CGPoint without decimal places:
print(NSStringFromPoint(point)) // "{5000, 7000}"
You can use String(format:_:), using a format string that explicitly specifies no decimal places:
print(String(format: "%.0f", point.x)) // "5000"
You can use NumberFormatter and specify the minimumFractionDigits:
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
formatter.numberStyle = .decimal
print(formatter.string(for: point.x)!) // "5,000"
Needless to say, you can save that formatter and you can use it repeatedly, as needed, if you want to keep your code from getting too verbose.
If you’re just logging this to the console, you can use Unified Logging with import os.log and then do:
os_log("%.0f", point.x) // "5000"

Int won't show numbers after the comma

I want to get from a string to a int. My problem is now that i'll get a int value but it wont show the number after the comma. If the string is -0,23, than the int will print 0.How can i fix this? Please help.
let int = (percent_change_24hArray[indexPath.row] as NSString).integerValue
You will probably have to use a NumberFormatter for that, set up for your locale. In the US, the decimal separator is a period. (".") A comma is considered a delimiter.
Plus, assuming you are in a locale where the decimal separator is a comma, converting to an integer will truncate the part after the decimal separator anyway.
Code like the code below should work:
var str = "-0,23"
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "fr_FR")
let aVal = formatter.number(from: str)?.doubleValue
(I'm using France's French as the locale since France uses a comma as a decimal separator.)
Note that you need to think carefully about where you numeric strings come from in order to decide how to handle locale. if these are user-entered strings then you should probably use a number formatter with the default locale for the device. That way a European user can use a comma as a decimal separator and a US user can use a period as a decimal separator and both will work.
You need to use a float, integers don't take numbers after the comma
https://developer.apple.com/documentation/swift/float

Display certain number of letters

I have a word that is being displayed into a label. Could I program it, where it will only show the last 2 characters of the word, or the the first 3 only? How can I do this?
Swift's string APIs can be a little confusing. You get access to the characters of a string via its characters property, on which you can then use prefix() or suffix() to get the substring you want. That subset of characters needs to be converted back to a String:
let str = "Hello, world!"
// first three characters:
let prefixSubstring = String(str.characters.prefix(3))
// last two characters:
let suffixSubstring = String(str.characters.suffix(2))
I agree it is definitely confusing working with String indexing in Swift and they have changed a little bit from Swift 1 to 2 making googling a bit of a challenge but it can actually be quite simple once you get a hang of the methods. You basically need to make it into a two-step process:
1) Find the index you need
2) Advance from there
For example:
let sampleString = "HelloWorld"
let lastThreeindex = sampleString.endIndex.advancedBy(-3)
sampleString.substringFromIndex(lastThreeindex) //prints rld
let secondIndex = sampleString.startIndex.advancedBy(2)
sampleString.substringToIndex(secondIndex) //prints He