Swift get decimal value for String characters - swift

Is it possible to get the decimal value for String characters in Swift?
something like:
let words:String = "1 ring to rule them all"
var value:Int = 0
for i in 0..<words.count {
let char = words[words.index(words.startIndex,offsetBy:i)]
value += Int(char.decimal)
}
where the first character in "1 ring to rule them all" is 49. Possible?

you could try this:
let words = "1 ring to rule them all"
var value: Int = 0
for i in 0..<words.count {
let char = words[words.index(words.startIndex,offsetBy:i)]
if let val = char.asciiValue {
print("----> char: \(char) val: \(val)") // char and its "decimal" value
value += Int(val)
}
}
print("\n----> value: \(value) \n") // meaningless total value

ok, looks like this is the way:
let characterString = "蜇"
let scalars = characterString.unicodeScalars
let ui32:UInt32 = scalars[scalars.startIndex].value

If you want to add up the Unicode values associated with a string, it would be:
var value = 0
for character in string {
for scalar in character.unicodeScalars {
value += Int(scalar.value)
}
}
Or, alternatively:
let value = string
.flatMap { $0.unicodeScalars }
.compactMap { $0.value }
.reduce(0, +)
While the above adds the values, as requested, if you are trying to get a numeric representation for a string, you might consider using hashValue, or checksum, or CRC, or the like. Simply summing the values will not be able to detect, for example, character transpositions. It just depends upon your use-case for this numeric representation of your string.

Related

Remove non numeric characters from string having multiple float values using for loop

I have a string having characters and float values in a list like let string = "12.1gh34.5abc32.1". i want to remove characters from string and result will be shown in array with float values. is there any solution for this.
Split the string using letters as the separator. Remove empty strings from the result. Map the remaining number strings into real numbers.
let string = "12.1gh34.5abc32.1"
let numbers = string.components(separatedBy: .letters)
.filter { !$0.isEmpty }
.compactMap { Double($0) }
The output is:
[12.1, 34.5, 32.1]
If you want to deal with anything that isn't a decimal digit or comma, you can replace .letters with:
CharacterSet(charactersIn: "0123456789.").inverted
You can use Collection split method and set omittingEmptySubsequences to true:
extension StringProtocol {
func notContains(_ element: Element) -> Bool {
return !contains(element)
}
var numbers: [SubSequence] {
return split(maxSplits: Int.max, omittingEmptySubsequences: true, whereSeparator: "0123456789.".notContains)
}
}
let text = "12.1gh 34.5abc 32.5"
let result = text.numbers // ["12.1", "34.5", "32.5"]
let nums = result.flatMap(Double.init) // [12.1, 34.5, 32.5]

Swift - Convert a binary string to its ascii values

I have a string of binary values e.g. "010010000110010101111001". Is there a simple way to convert this string into its ascii representation to get (in this case) "Hey"?
Only found the other way or things for Integer:
let binary = "11001"
if let number = Int(binary, radix: 2) {
print(number) // Output: 25
}
Do someone know a good and efficient solution for this case?
A variant of #OOPer's solution would be to use a conditionally binding while loop and index(_:offsetBy:limitedBy:) in order to iterate over the 8 character substrings, taking advantage of the fact that index(_:offsetBy:limitedBy:) returns nil when you try to advance past the limit.
let binaryBits = "010010000110010101111001"
var result = ""
var index = binaryBits.startIndex
while let next = binaryBits.index(index, offsetBy: 8, limitedBy: binaryBits.endIndex) {
let asciiCode = UInt8(binaryBits[index..<next], radix: 2)!
result.append(Character(UnicodeScalar(asciiCode)))
index = next
}
print(result) // Hey
Note that we're going via Character rather than String in the intermediate step – this is simply to take advantage of the fact that Character is specially optimised for cases where the UTF-8 representation fits into 63 bytes, which is the case here. This saves heap-allocating an intermediate buffer for each character.
Purely for the fun of it, another approach could be to use sequence(state:next:) in order to create a sequence of the start and end indices of each substring, and then reduce in order to concatenate the resultant characters together into a string:
let binaryBits = "010010000110010101111001"
// returns a lazily evaluated sequence of the start and end indices for each substring
// of 8 characters.
let indices = sequence(state: binaryBits.startIndex, next: {
index -> (index: String.Index, nextIndex: String.Index)? in
let previousIndex = index
// Advance the current index – if it didn't go past the limit, then return the
// current index along with the advanced index as a new element of the sequence.
return binaryBits.characters.formIndex(&index, offsetBy: 8, limitedBy: binaryBits.endIndex) ? (previousIndex, index) : nil
})
// iterate over the indices, concatenating the resultant characters together.
let result = indices.reduce("") {
$0 + String(UnicodeScalar(UInt8(binaryBits[$1.index..<$1.nextIndex], radix: 2)!))
}
print(result) // Hey
On the face of it, this appears to be much less efficient than the first solution (due to the fact that reduce should copy the string at each iteration) – however it appears the compiler is able to perform some optimisations to make it not much slower than the first solution.
You may need to split the input binary digits into 8-bit chunks, and then convert each chunk to an ASCII character. I cannot think of a super simple way:
var binaryBits = "010010000110010101111001"
var index = binaryBits.startIndex
var result: String = ""
for _ in 0..<binaryBits.characters.count/8 {
let nextIndex = binaryBits.index(index, offsetBy: 8)
let charBits = binaryBits[index..<nextIndex]
result += String(UnicodeScalar(UInt8(charBits, radix: 2)!))
index = nextIndex
}
print(result) //->Hey
Does basically the same as OOPer's solution, but he/she was faster and has a shorter, more elegant approach :-)
func getASCIIString(from binaryString: String) -> String? {
guard binaryString.characters.count % 8 == 0 else {
return nil
}
var asciiCharacters = [String]()
var asciiString = ""
let startIndex = binaryString.startIndex
var currentLowerIndex = startIndex
while currentLowerIndex < binaryString.endIndex {
let currentUpperIndex = binaryString.index(currentLowerIndex, offsetBy: 8)
let character = binaryString.substring(with: Range(uncheckedBounds: (lower: currentLowerIndex, upper: currentUpperIndex)))
asciiCharacters.append(character)
currentLowerIndex = currentUpperIndex
}
for asciiChar in asciiCharacters {
if let number = UInt8(asciiChar, radix: 2) {
let character = String(describing: UnicodeScalar(number))
asciiString.append(character)
} else {
return nil
}
}
return asciiString
}
let binaryString = "010010000110010101111001"
if let asciiString = getASCIIString(from: binaryString) {
print(asciiString) // Hey
}
A different approach
let bytes_string: String = "010010000110010101111001"
var range_count: Int = 0
let characters_array: [String] = Array(bytes_string.characters).map({ String($0)})
var conversion: String = ""
repeat
{
let sub_range = characters_array[range_count ..< (range_count + 8)]
let sub_string: String = sub_range.reduce("") { $0 + $1 }
let character: String = String(UnicodeScalar(UInt8(sub_string, radix: 2)!))
conversion += character
range_count += 8
} while range_count < characters_array.count
print(conversion)
You can do this:
extension String {
var binaryToAscii: String {
stride(from: 0, through: count - 1, by: 8)
.map { i in map { String($0)}[i..<(i + 8)].joined() }
.map { String(UnicodeScalar(UInt8($0, radix: 2)!)) }
.joined()
}
}

String convert to Int and replace comma to Plus sign

Using Swift, I'm trying to take a list of numbers input in a text view in an app and create a sum of this list by extracting each number for a grade calculator. Also the amount of values put in by the user changes each time. An example is shown below:
String of: 98,99,97,96...
Trying to get: 98+99+97+96...
Please Help!
Thanks
Use components(separatedBy:) to break up the comma-separated string.
Use trimmingCharacters(in:) to remove spaces before and after each element
Use Int() to convert each element into an integer.
Use compactMap (previously called flatMap) to remove any items that couldn't be converted to Int.
Use reduce to sum up the array of Int.
let input = " 98 ,99 , 97, 96 "
let values = input.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }
let sum = values.reduce(0, +)
print(sum) // 390
For Swift 3 and Swift 4.
Simple way: Hard coded. Only useful if you know the exact amount of integers coming up, wanting to get calculated and printed/used further on.
let string98: String = "98"
let string99: String = "99"
let string100: String = "100"
let string101: String = "101"
let int98: Int = Int(string98)!
let int99: Int = Int(string99)!
let int100: Int = Int(string100)!
let int101: Int = Int(string101)!
// optional chaining (if or guard) instead of "!" recommended. therefore option b is better
let finalInt: Int = int98 + int99 + int100 + int101
print(finalInt) // prints Optional(398) (optional)
Fancy way as a function: Generic way. Here you can put as many strings in as you need in the end. You could, for example, gather all the strings first and then use the array to have them calculated.
func getCalculatedIntegerFrom(strings: [String]) -> Int {
var result = Int()
for element in strings {
guard let int = Int(element) else {
break // or return nil
// break instead of return, returns Integer of all
// the values it was able to turn into Integer
// so even if there is a String f.e. "123S", it would
// still return an Integer instead of nil
// if you want to use return, you have to set "-> Int?" as optional
}
result = result + int
}
return result
}
let arrayOfStrings = ["98", "99", "100", "101"]
let result = getCalculatedIntegerFrom(strings: arrayOfStrings)
print(result) // prints 398 (non-optional)
let myString = "556"
let myInt = Int(myString)

Swift 3.0 iterate over String.Index range

The following was possible with Swift 2.2:
let m = "alpha"
for i in m.startIndex..<m.endIndex {
print(m[i])
}
a
l
p
h
a
With 3.0, we get the following error:
Type 'Range' (aka 'Range') does not conform to protocol 'Sequence'
I am trying to do a very simple operation with strings in swift -- simply traverse through the first half of the string (or a more generic problem: traverse through a range of a string).
I can do the following:
let s = "string"
var midIndex = s.index(s.startIndex, offsetBy: s.characters.count/2)
let r = Range(s.startIndex..<midIndex)
print(s[r])
But here I'm not really traversing the string. So the question is: how do I traverse through a range of a given string. Like:
for i in Range(s.startIndex..<s.midIndex) {
print(s[i])
}
You can traverse a string by using indices property of the characters property like this:
let letters = "string"
let middle = letters.index(letters.startIndex, offsetBy: letters.characters.count / 2)
for index in letters.characters.indices {
// to traverse to half the length of string
if index == middle { break } // s, t, r
print(letters[index]) // s, t, r, i, n, g
}
From the documentation in section Strings and Characters - Counting Characters:
Extended grapheme clusters can be composed of one or more Unicode scalars. This means that different characters—and different representations of the same character—can require different amounts of memory to store. Because of this, characters in Swift do not each take up the same amount of memory within a string’s representation. As a result, the number of characters in a string cannot be calculated without iterating through the string to determine its extended grapheme cluster boundaries.
emphasis is my own.
This will not work:
let secondChar = letters[1]
// error: subscript is unavailable, cannot subscript String with an Int
Another option is to use enumerated() e.g:
let string = "Hello World"
for (index, char) in string.characters.enumerated() {
print(char)
}
or for Swift 4 just use
let string = "Hello World"
for (index, char) in string.enumerated() {
print(char)
}
Use the following:
for i in s.characters.indices[s.startIndex..<s.endIndex] {
print(s[i])
}
Taken from Migrating to Swift 2.3 or Swift 3 from Swift 2.2
Iterating over characters in a string is cleaner in Swift 4:
let myString = "Hello World"
for char in myString {
print(char)
}
If you want to traverse over the characters of a String, then instead of explicitly accessing the indices of the String, you could simply work with the CharacterView of the String, which conforms to CollectionType, allowing you access to neat subsequencing methods such as prefix(_:) and so on.
/* traverse the characters of your string instance,
up to middle character of the string, where "middle"
will be rounded down for strings of an odd amount of
characters (e.g. 5 characters -> travers through 2) */
let m = "alpha"
for ch in m.characters.prefix(m.characters.count/2) {
print(ch, ch.dynamicType)
} /* a Character
l Character */
/* round odd division up instead */
for ch in m.characters.prefix((m.characters.count+1)/2) {
print(ch, ch.dynamicType)
} /* a Character
l Character
p Character */
If you'd like to treat the characters within the loop as strings, simply use String(ch) above.
With regard to your comment below: if you'd like to access a range of the CharacterView, you could easily implement your own extension of CollectionType (specified for when Generator.Element is Character) making use of both prefix(_:) and suffix(_:) to yield a sub-collection given e.g. a half-open (from..<to) range
/* for values to >= count, prefixed CharacterView will be suffixed until its end */
extension CollectionType where Generator.Element == Character {
func inHalfOpenRange(from: Int, to: Int) -> Self {
guard case let to = min(to, underestimateCount()) where from <= to else {
return self.prefix(0) as! Self
}
return self.prefix(to).suffix(to-from) as! Self
}
}
/* example */
let m = "0123456789"
for ch in m.characters.inHalfOpenRange(4, to: 8) {
print(ch) /* \ */
} /* 4 a (sub-collection) CharacterView
5
6
7 */
The best way to do this is :-
let name = "nick" // The String which we want to print.
for i in 0..<name.count
{
// Operation name[i] is not allowed in Swift, an alternative is
let index = name.index[name.startIndex, offsetBy: i]
print(name[index])
}
for more details visit here
Swift 4.2
Simply:
let m = "alpha"
for i in m.indices {
print(m[i])
}
Swift 4:
let mi: String = "hello how are you?"
for i in mi {
print(i)
}
To concretely demonstrate how to traverse through a range in a string in Swift 4, we can use the where filter in a for loop to filter its execution to the specified range:
func iterateStringByRange(_ sentence: String, from: Int, to: Int) {
let startIndex = sentence.index(sentence.startIndex, offsetBy: from)
let endIndex = sentence.index(sentence.startIndex, offsetBy: to)
for position in sentence.indices where (position >= startIndex && position < endIndex) {
let char = sentence[position]
print(char)
}
}
iterateStringByRange("string", from: 1, to: 3) will print t, r and i
When iterating over the indices of characters in a string, you seldom only need the index. You probably also need the character at the given index. As specified by Paulo (updated for Swift 4+), string.indices will give you the indices of the characters. zip can be used to combine index and character:
let string = "string"
// Define the range to conform to your needs
let range = string.startIndex..<string.index(string.startIndex, offsetBy: string.count / 2)
let substring = string[range]
// If the range is in the type "first x characters", like until the middle, you can use:
// let substring = string.prefix(string.count / 2)
for (index, char) in zip(substring.indices, substring) {
// index is the index in the substring
print(char)
}
Note that using enumerated() will produce a pair of index and character, but the index is not the index of the character in the string. It is the index in the enumeration, which can be different.

Convert String.CharacterView.Index to int [duplicate]

I want to convert the index of a letter contained within a string to an integer value. Attempted to read the header files but I cannot find the type for Index, although it appears to conform to protocol ForwardIndexType with methods (e.g. distanceTo).
var letters = "abcdefg"
let index = letters.characters.indexOf("c")!
// ERROR: Cannot invoke initializer for type 'Int' with an argument list of type '(String.CharacterView.Index)'
let intValue = Int(index) // I want the integer value of the index (e.g. 2)
Any help is appreciated.
edit/update:
Xcode 11 • Swift 5.1 or later
extension StringProtocol {
func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}
extension Collection {
func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}
extension String.Index {
func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}
Playground testing
let letters = "abcdefg"
let char: Character = "c"
if let distance = letters.distance(of: char) {
print("character \(char) was found at position #\(distance)") // "character c was found at position #2\n"
} else {
print("character \(char) was not found")
}
let string = "cde"
if let distance = letters.distance(of: string) {
print("string \(string) was found at position #\(distance)") // "string cde was found at position #2\n"
} else {
print("string \(string) was not found")
}
Works for Xcode 13 and Swift 5
let myString = "Hello World"
if let i = myString.firstIndex(of: "o") {
let index: Int = myString.distance(from: myString.startIndex, to: i)
print(index) // Prints 4
}
The function func distance(from start: String.Index, to end: String.Index) -> String.IndexDistance returns an IndexDistance which is just a typealias for Int
Swift 4
var str = "abcdefg"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Note: If String contains same multiple characters, it will just get the nearest one from left
var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
encodedOffset has deprecated from Swift 4.2.
Deprecation message:
encodedOffset has been deprecated as most common usage is incorrect. Use utf16Offset(in:) to achieve the same behavior.
So we can use utf16Offset(in:) like this:
var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
When searching for index like this
⛔️ guard let index = (positions.firstIndex { position <= $0 }) else {
it is treated as Array.Index. You have to give compiler a clue you want an integer
✅ guard let index: Int = (positions.firstIndex { position <= $0 }) else {
Swift 5
You can do convert to array of characters and then use advanced(by:) to convert to integer.
let myString = "Hello World"
if let i = Array(myString).firstIndex(of: "o") {
let index: Int = i.advanced(by: 0)
print(index) // Prints 4
}
To perform string operation based on index , you can not do it with traditional index numeric approach. because swift.index is retrieved by the indices function and it is not in the Int type. Even though String is an array of characters, still we can't read element by index.
This is frustrating.
So ,to create new substring of every even character of string , check below code.
let mystr = "abcdefghijklmnopqrstuvwxyz"
let mystrArray = Array(mystr)
let strLength = mystrArray.count
var resultStrArray : [Character] = []
var i = 0
while i < strLength {
if i % 2 == 0 {
resultStrArray.append(mystrArray[i])
}
i += 1
}
let resultString = String(resultStrArray)
print(resultString)
Output : acegikmoqsuwy
Thanks In advance
Here is an extension that will let you access the bounds of a substring as Ints instead of String.Index values:
import Foundation
/// This extension is available at
/// https://gist.github.com/zackdotcomputer/9d83f4d48af7127cd0bea427b4d6d61b
extension StringProtocol {
/// Access the range of the search string as integer indices
/// in the rendered string.
/// - NOTE: This is "unsafe" because it may not return what you expect if
/// your string contains single symbols formed from multiple scalars.
/// - Returns: A `CountableRange<Int>` that will align with the Swift String.Index
/// from the result of the standard function range(of:).
func countableRange<SearchType: StringProtocol>(
of search: SearchType,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> CountableRange<Int>? {
guard let trueRange = self.range(of: search, options: options, range: range, locale: locale) else {
return nil
}
let intStart = self.distance(from: startIndex, to: trueRange.lowerBound)
let intEnd = self.distance(from: trueRange.lowerBound, to: trueRange.upperBound) + intStart
return Range(uncheckedBounds: (lower: intStart, upper: intEnd))
}
}
Just be aware that this can lead to weirdness, which is why Apple has chosen to make it hard. (Though that's a debatable design decision - hiding a dangerous thing by just making it hard...)
You can read more in the String documentation from Apple, but the tldr is that it stems from the fact that these "indices" are actually implementation-specific. They represent the indices into the string after it has been rendered by the OS, and so can shift from OS-to-OS depending on what version of the Unicode spec is being used. This means that accessing values by index is no longer a constant-time operation, because the UTF spec has to be run over the data to determine the right place in the string. These indices will also not line up with the values generated by NSString, if you bridge to it, or with the indices into the underlying UTF scalars. Caveat developer.
In case you got an "index is out of bounds" error. You may try this approach. Working in Swift 5
extension String{
func countIndex(_ char:Character) -> Int{
var count = 0
var temp = self
for c in self{
if c == char {
//temp.remove(at: temp.index(temp.startIndex,offsetBy:count))
//temp.insert(".", at: temp.index(temp.startIndex,offsetBy: count))
return count
}
count += 1
}
return -1
}
}