Is there really still no way of converting a string to an integer in Cadence? - onflow-cadence

Seems like such a basic thing. A similar question was posted 9 months back in the Cadence discord and the answer was no - just checking that is still the case.
I'm trying to pass a serial number encoded as a string in the NFT metadata as an argument to MetadataViews.Serial() which accepts a UInt64 - so I need to get the string value in the meta into a UInt64.

So, there is no official way to do it, but it can be implemented in cadence:
pub fun parseUInt64(_ string: String) : UInt64? {
let chars : {Character : UInt64} = {
"0" : 0 ,
"1" : 1 ,
"2" : 2 ,
"3" : 3 ,
"4" : 4 ,
"5" : 5 ,
"6" : 6 ,
"7" : 7 ,
"8" : 8 ,
"9" : 9
}
var number : UInt64 = 0
var i = 0
while i < string.length {
if let n = chars[string[i]] {
number = number * 10 + n
} else {
return nil
}
i = i + 1
}
return number
}
}
This was borrowed from https://flowscan.org/contract/A.097bafa4e0b48eef.CharityNFT/overview

Related

Replacing a char in a string based on a condition [duplicate]

This question already has answers here:
Any way to replace characters on Swift String?
(23 answers)
Closed 8 months ago.
I came across this question in CodeWars recently & tried to solve it using Swift 5 (which I'm new to):
Given a string of digits, you should replace any digit below 5 with '0' and any digit 5 and above with '1'. Return the resulting string.
Note: input will never be an empty string
I come from a Python background where we could using indexing with if statements to solve this particular problem like this:
def bin(x):
newstr = ""
for num in x:
if int(num) < 5:
newstr += "0"
else:
newstr += "1"
return newstr
But when I tried to use indexing in Swift( like the Python style), I failed...which, after a while of researching made me come across this method: String.replacingOccurrences(of: , with: )
So if my Swift code for the above ques is like this:
func fakeBin(digits: String) -> String {
for i in digits{
if (Int(i)<5){
some code to replace the char with '0'
}
else{some code to replace the char with '1'}
return digits
}
How do I use the above method String.replacingOccurrences(of: , with: ) in my code and achieve the desired output? If this is not possible, how else can we do it?
Note: I have already looked up for solutions on the website & found one:How do I check each individual digit within a string? But wasn't of any use to me as the questioner wanted the help in Java
A Swift String is a collection of Characters. You can map each character to "0" or "1", and join the result to a new string:
func fakeBin(digits: String) -> String {
return String(digits.map { $0 < "5" ? "0" : "1" })
}
Your code equivalent would be:
func fakeBinBasicForLoop(digits: String) -> String {
var output = ""
for aChar in digits {
if let intValue = Int(String(aChar)) { //It's a number
if intValue < 5 {
output.append("0")
} else {
output.append("1")
}
} else { //Not a number
output.append(aChar)
}
}
return output
}
I'm checking if it's an integer, just in case.
Manually, with replacingOccurrences(of:with:):
func fakeBinReplacingOccurences(digits: String) -> String {
var output = digits
output = output.replacingOccurrences(of: "0", with: "0") //No really needed in this case, we can ommit it
output = output.replacingOccurrences(of: "1", with: "0")
output = output.replacingOccurrences(of: "2", with: "0")
output = output.replacingOccurrences(of: "3", with: "0")
output = output.replacingOccurrences(of: "4", with: "0")
output = output.replacingOccurrences(of: "5", with: "1")
output = output.replacingOccurrences(of: "6", with: "1")
output = output.replacingOccurrences(of: "7", with: "1")
output = output.replacingOccurrences(of: "8", with: "1")
output = output.replacingOccurrences(of: "9", with: "1")
return output
}
The issue is that it iterate all the string each time, so you'll have 9 loops.
Allso, if you start by replacing 5, 6, 7, 8, 9 (ie in reverse order), your higher values will be replaced by 1, and then, replaced by 0. Order matters.
An alternative solution with reduce(into:_:):
func fakeBinReduceInto(digits: String) -> String {
let output = digits.reduce(into: "") { partialResult, character in
guard let intValue = Int(String(character)) else { partialResult.append(character); return }
if intValue < 5 {
partialResult.append("0")
} else {
partialResult.append("1")
}
}
return output
}
You can also use a map() as suggested by other answers.

What's the best way to get updated Character by moving a given Integer

Swift character is so hard to manipulate..
I have a simple request:
For a given string, I'd like to encode it with a "moving integer" for all the alphabetical digits.
For example: "abc", move 1: would become "bcd"; "ABC", move 1 -> "BCD".
One thing to note is, if after moving, the range it larger than "z" or "Z", it should loop back and calculate from "a" or "A" again. e.g "XYZ", move 1 -> "YZA"
This would be very easy to do in Java. But could anyone show me what would be the cleanest way to do it swift?
I've done something like:
let arr: [Character] = Array(s)
for i in arr {
let curAsciiValue = i.asciiValue!
var updatedVal = (curAsciiValue + UInt8(withRotationFactor))
if i >= "A" && i <= "Z" {
newAsciiVal = 65
updatedVal = updatedVal - 65 >= 26 ? 65 + (updatedVal - 65) % 26 : updatedVal
} else if i >= "a" && i <= "z" {
newAsciiVal = 97
updatedVal = updatedVal - 97 >= 26 ? 97 + (updatedVal - 97) % 26 : updatedVal
}
}
What should be best way to do this?
Better to bring all your logic together extending the Character type:
extension Character {
var isAlphabet: Bool { isLowercaseAlphabet || isUppercaseAlphabet }
var isLowercaseAlphabet: Bool { "a"..."z" ~= self }
var isUppercaseAlphabet: Bool { "A"..."Z" ~= self }
func rotateAlphabetLetter(_ factor: Int) -> Self? {
precondition(factor > 0, "rotation must be positive")
guard let asciiValue = asciiValue, isAlphabet else { return nil }
let addition = asciiValue + UInt8(factor % 26)
return .init(UnicodeScalar(addition > (isLowercaseAlphabet ? 122 : 90) ? addition - 26 : addition))
}
}
Character("a").rotateAlphabetLetter(2) // "c"
Character("A").rotateAlphabetLetter(2) // "C"
Character("j").rotateAlphabetLetter(2) // "l"
Character("J").rotateAlphabetLetter(2) // "L"
Character("z").rotateAlphabetLetter(2) // "b"
Character("Z").rotateAlphabetLetter(2) // "B"
Character("ç").rotateAlphabetLetter(2) // nil
Character("Ç").rotateAlphabetLetter(2) // nil
Expanding on that you can map your string elements using compactMap:
let string = "XYZ"
let rotatedAlphabetLetters = string.compactMap { $0.rotateAlphabetLetter(2) } // ["Z", "A", "B"]
And to convert the sequence of characters back to string:
extension Sequence where Element == Character {
var string: String { .init(self) }
}
let result = rotatedAlphabetLetters.string // "ZAB"
or simply in one liner:
let result = "XYZ".compactMap { $0.rotateAlphabetLetter(2) }.string // "ZAB"

Can't advance past endIndex Swift

Something wrong with String Index when checking and deleting symbols. How can I improve it?
func romanToInt(_ s: String) -> Int {
let romanDigits = ["I" : 1,
"V" : 5,
"X" : 10,
"L" : 50,
"C" : 100,
"D" : 500,
"M" : 1000]
let romanSums = ["IV" : 4,
"IX" : 9,
"XL" : 40,
"XC" : 90,
"CD" : 400,
"CM" : 900]
var sum = 0
var str = s
var charIndex = str.startIndex
for index in str.indices {
if index != str.index(before: str.endIndex) {
charIndex = str.index(after: index)
} else {
charIndex = str.index(before: str.endIndex)
}
let chars = String(str[index]) + String(str[charIndex])
if romanSums[chars] != nil {
print(chars)
str.remove(at: charIndex)
sum += romanSums[chars]!
print(sum)
} else {
let char = String(str[index])
print(char)
sum += romanDigits[char]!
print(sum)
}
print(str)
}
return sum
}
let check = romanToInt("MCMXCIV")
CONSOLE LOG:
M
1000
MCMXCIV
CM
1900
MCXCIV
XC
1990
MCXIV
IV
1994
MCXI
Fatal error: Can't advance past endIndex
You are modifying the string you are iterating over, so your indices become invalid. Instead, you could add a skipChar boolean that says that you've already handled the next character and then skip that character by executing continue:
func romanToInt(_ s: String) -> Int {
let romanDigits = ["I" : 1,
"V" : 5,
"X" : 10,
"L" : 50,
"C" : 100,
"D" : 500,
"M" : 1000]
let romanSums = ["IV" : 4,
"IX" : 9,
"XL" : 40,
"XC" : 90,
"CD" : 400,
"CM" : 900]
var sum = 0
var str = s
var charIndex = str.startIndex
var skipChar = false
for index in str.indices {
if skipChar {
skipChar = false
continue
}
if index != str.index(before: str.endIndex) {
charIndex = str.index(after: index)
} else {
charIndex = str.index(before: str.endIndex)
}
let chars = String(str[index]) + String(str[charIndex])
if romanSums[chars] != nil {
print(chars)
skipChar = true
sum += romanSums[chars]!
print(sum)
} else {
let char = String(str[index])
print(char)
sum += romanDigits[char]!
print(sum)
}
print(str)
}
return sum
}
let check = romanToInt("MCMXCIV")
print(check)
1994
for index in str.indices {
...
str.remove(at: charIndex)
It is not valid to modify a string while you are iterating over it. str.indices is fetched one time here, and is no longer valid once you've modified the underlying string.
I'm sure there will be a lot of implementations of this because it's the kind of small, fun problem that attracts implementations. So why not? This just screams recursion to me.
let romanDigits: [Substring: Int] = ["I" : 1,
"V" : 5,
"X" : 10,
"L" : 50,
"C" : 100,
"D" : 500,
"M" : 1000]
let romanSums: [Substring: Int] = ["IV" : 4,
"IX" : 9,
"XL" : 40,
"XC" : 90,
"CD" : 400,
"CM" : 900]
func romanToInt<S: StringProtocol>(_ s: S) -> Int
where S.SubSequence == Substring {
if s.isEmpty { return 0 }
if let value = romanSums[s.prefix(2)] {
return value + romanToInt(s.dropFirst(2))
} else if let value = romanDigits[s.prefix(1)] {
return value + romanToInt(s.dropFirst(1))
} else {
fatalError("Invalid string")
}
}
let check = romanToInt("MCMXCIV")
Of course this doesn't really check for valid sequences, so it's kind of junk. "IIIIXXIII" is kind of gibberish, but it works. But it's in keeping with the original approach.
Use reduce to make it flow here:
func romanToInt(_ s: String) -> Int {
if s.isEmpty {return 0}
let romanDigits = ["I" : 1,
"V" : 5,
"X" : 10,
"L" : 50,
"C" : 100,
"D" : 500,
"M" : 1000]
let romanSums = ["IV" : 4,
"IX" : 9,
"XL" : 40,
"XC" : 90,
"CD" : 400,
"CM" : 900]
return s.dropFirst().reduce((s.first!, romanDigits["\(s.first!)"]!)){
return ( $1, //current char
$0.1 + //previous sum
(romanSums["\($0.0)\($1)"] //add double value
?? ((romanDigits["\($1)"]!). + romanDigits["\($0.0)"]!)) //or single value and add duplicated
- romanDigits["\($0.0)"]!) // minus duplicated
}.1
}
print(romanToInt("MCMXCIV")). //1994
You are mutating str inside the loop, its end index is going change, in this case it gets lower than its original value. You can fix your code by checking that you haven't exceeded the endIndex on each iteration by using a while loop :
var index = str.startIndex
while index < str.endIndex {
...
//Before the closing curly brace of the while loop
index = str.index(after: index)
}
I was trying to reproduce a crash reported by one my users with the same message Can't advance past endIndex but I was not able to do it. Your code helped me figure out that this error changed in later versions of swift.
Your same code would report cannot increment beyond endIndex with swift 4.x runtime libraries, and String index is out of bounds with 5.x. The exact version numbers for the changes I do not know. But I suspect it is 4.0.0 and 5.0.0.-

Converting UnitVolume.metricCups to a fraction in swift 4

Is it possible to convert the format that comes out of converted(to: UnitVolume.metricCups) into a fraction so the answer is read just like you would from say a cookbook etc.
Example after the conversion is complete I get an answer of 1.5 metric cups, but I would like to see it as 1 1/2 or say 0.333 as 1/3 cup.
Swift documentation doesn't really mention any steps for this that I'm aware of. Thank you.
This is what the storyboard looks like:
let millimeters = Measurement(value: waterResult, unit: UnitVolume.milliliters)
let resultWaterConversion = millimeters.converted(to:UnitVolume.metricCups)
cupsWater.text = "\(resultWaterConversion)"
I was able to solve my issue by using a switch and a little modification with the string values eg "3/4" to get it just right for my needs.
Here is the code i used:
`// MARK:- Conversion to display rice amount in the correct format for readability
let resultRiceInteger = Int(resultRiceConversion.value)
let resultRiceFraction = Int(100 * (resultRiceConversion.value - Double(resultRiceInteger))) // To avoid bad effects due to Double
var decimalTextR = ""
switch resultRiceFraction {
case 0...9 : decimalTextR = "" // Now, approximate
case 10 : decimalTextR = "1/10" // First when we are very close to real fraction
case 11 : decimalTextR = "1/9"
case 12...13 : decimalTextR = "1/8"
case 14...15 : decimalTextR = "1/7"
case 16...18 : decimalTextR = "1/6"
case 19...22 : decimalTextR = "1/5"
case 23...29 : decimalTextR = "1/4"
case 30...40 : decimalTextR = "1/3"
case 41...60 : decimalTextR = "1/2"
case 61...72 : decimalTextR = "2/3"
case 73...79 : decimalTextR = "3/4"
case 90...110 : decimalTextR = "1"
default : decimalTextR = ""
}
cupsWater.text = "\(resultRiceInteger) and \(decimalTextR)" // UILabel displayed on my stored board.
let cupsRiceText = resultRiceConversion.value > 1 ? "cups" : "cup"
if resultRiceInteger > 0 {
cupsRice.text = "\(resultRiceInteger) \(decimalTextR) \(cupsRiceText)" // UILabel displayed on my stored board.
} else {
cupsRice.text = "\(decimalTextR) \(cupsRiceText)"
}`

Swift 4 - using a variable to select dictionary

I have a number of dictionarys in my swift code that have a standard naming convention. What I am trying to do is programmatically selection which dictionary to extract the data from. As as example
var listSelectionValue = "aaaa"
let aaaalist : [Int : String ] = [1: "first value in aaaaa", 2 : "second value in aaaa"]
let bbbblist : [Int : String ] =[1: "first value in bbbb", 2 : "second value in bbbb"]
I then want to use the value in listSelectionValue for pull data from the correct dictionary. Sorry if this is exceedingly obvious, maybe I don't know right terminology to search for !!
Cheers,
Cameron
An if then else question?
var listSelectionValue = "aaaa"
let aaaalist = [Int : String ]()
if listSelectionValue == "aaaa"{
aaaalist : [Int : String ] = [1: "first value in aaaaa", 2 : "second value in aaaa"]
}
else{
let bbbblist : [Int : String ] =[1: "first value in bbbb", 2 : "second value in bbbb"]
}