F# create random string of letters and numbers [closed] - f#

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 8 years ago.
Improve this question
I have an issue with the below code. I am attempting to create a random string of letters and numbers. The below code works, however only creates 1 random string per session, so if I call the randomNum again it still hold the first random string created.
let randomNum(len : int) =
let rand = new System.Random()
let mutable str = ""
let chars = "ABCDEFGHIJKLMNOPQRSTUVWUXYZ0123456789"
let mutable count = 0
for i in 1 .. len do
count <- (rand.Next() % 36)
str <- String.concat "" [str; chars.[count].ToString()]
str
Does anyone know of an easy way to create a random string each time the randomNum is called.
Thanks

You can do it using a more idiomatic syntax:
let randomStr =
let chars = "ABCDEFGHIJKLMNOPQRSTUVWUXYZ0123456789"
let charsLen = chars.Length
let random = System.Random()
fun len ->
let randomChars = [|for i in 0..len -> chars.[random.Next(charsLen)]|]
new System.String(randomChars)
We keep the chars array, it's length and the Random generator in a cloture and we return a function which takes the desired length to build the string from.
Calling the function:
let randomString10 = randomStr(10)

Here's a version which doesn't use mutability:
module String =
let ofChars arr =
arr |> Array.fold (fun acc elem -> sprintf "%s%c" acc elem) ""
let randomString len =
let rand = new System.Random()
let chars = Array.append [|'A'..'Z'|] [|'0'..'9'|]
|> String.ofChars
Array.init len (fun _ -> chars.[rand.Next(chars.Length)])
|> String.ofChars
(Performance could be improved further by not using sprintf in ofChars.)

Actually it's the
new System.Random()
I have just moved it to the top of my code
let rand = new System.Random()
let randomNum(len : int) =
let mutable str = ""
let chars = "ABCDEFGHIJKLMNOPQRSTUVWUXYZ0123456789"
let mutable count = 0
for i in 1 .. len do
count <- (rand.Next() % 36)
str <- String.concat "" [str; chars.[count].ToString()]
str

Figured it out. I was calling the randomNum function from a different page and storing it as a variable
let randomtitlevertical = Functions.randomNum(10)
let randomtitlevaluevertical = randomtitlevertical
This only ran the randomNum function once per seassion. If I have the following instead
let randomtitlevertical = Functions.randomNum(10)
Then it will run the randomNum fucntion once per project.

Related

Indexes as Ints in Swift

I have the following code which I've commented:
func hash (s:String) -> Int {
var z = 19
let key = "abcdefghijk"
for var i = 0; i < s.characters.count; i++ {
let index = s.startIndex.advancedBy(i)
let char = s[index]
if let f = key.characters.indexOf(char) {
print(f) //This outputs the number I want to use in the next line, but as String.CharacterView.Index, not an Int
z = (z * f) //obviously this won't work
}
// So instead we try this:
z = z * s.startIndex.distanceTo(key.characters.indexOf(char)!))
// It works for the first few characters then I get "fatal error: can not increment endIndex" and a EXC_BAD_INSTRUCTION
}
return z
}
I'm struggling to use Swift String to find the index I want to use as an Int in some kind of a hash function. In case it isn't clear:
User inputs a string, the function iterates through each character, finding that character in the key and then takes the index of that character in key and multiplies it to the existing z counter.
I get the results I want but only in the wrong type which doesn't let me convert to Int. Anyone know how?
Thanks
f = key.characters.indexOf(char) is an index into the characters
of key, therefore you have to compute the distance to the start index of key, not s:
z = z * key.startIndex.distanceTo(key.characters.indexOf(char)!))
which you can move back to your if-let block:
if let f = key.characters.indexOf(char) {
z = z * key.startIndex.distanceTo(f)
}
You also might want to use the overflow operator &*
if let f = key.characters.indexOf(char) {
z = z &* key.startIndex.distanceTo(f)
}
otherwise the application will crash if the result of the
multiplication does not fit into an Int.
Generally, the indexes of a Swift String can only be used
with the same string (regardless of string length), as the following
example demonstrates:
let s1 = "abc"
let i1 = s1.characters.indexOf("b")!
print(i1) // 1
let s2 = "🇩🇪a🇩🇪b🇩🇪c"
print(s2.characters.count) // 6
let d2 = s2.startIndex.distanceTo(i1) // fatal error: can not increment endIndex
If you make your key into an array of Characters, then indexOf will return the Int you need:
func hash (s:String) -> Int {
var z = 19
let key = Array("abcdefghijk".characters)
for char in s.characters {
if let f = key.indexOf(char) {
print(f)
z = (z &* (f + 1))
}
}
return z
}
Also, if your character is the first index in the key you will get a value of 0 which will make your hash value 0, so I've added 1 to f. I also incorporated &* as suggested by #MartinR to prevent Int overflow.
First, don't use forced unwrap as you can end up easily crashing your app (as it seems it already happened).
Second, you already test for the validity of key.characters.indexOf(char), you can place the z computation within the if-let:
z = z * s.startIndex.distanceTo(f)
What happens with your code is that the hash() function crashes as soon as it encounters a character beyond k, so you should also add all possible characters to key.

Assigning a value post while-loop results in error

I'm struggling to figure out why I receive the following error:
Block following this 'let' is unfinished. Expect an expression.
let hashset = System.Collections.Generic.HashSet<int>()
let mutable continueLooping = true
while (continueLooping) do
let value = System.Random().Next(0, 12)
let success = hashset.Add(value)
continueLooping <- hashset.Count <> 12
let z = hashet
The error is based on the following line:
let z = hashset
Why am I receiving this error?
NOTE:
I am new to F#. As a result, please forgive my ignorance.
as far as I can tell it's just because you mixed tabs and spaces in there - and in deed this works if I evaluate it in FSharpInteractive:
let hashset = System.Collections.Generic.HashSet<int>()
let mutable continueLooping = true
while (continueLooping) do
let value = System.Random().Next(0, 12)
let success = hashset.Add(value)
continueLooping <- hashset.Count <> 12
let z = hashset
evaluates to
val hashset : System.Collections.Generic.HashSet<int>
val mutable continueLooping : bool = false
val z : System.Collections.Generic.HashSet<int>
then z |> Seq.toArray evaluates to
val it : int [] = [|2; 8; 9; 3; 4; 10; 5; 11; 0; 6; 1; 7|]
which seems fine
btw: as you have a slight typo in there: ... z = hashet instead of hashsetI think you did not copy&paste the code that caused your error anyways.

Loop through list of 2 tuples to replace part of a string

I'm trying to replace chained String.Replace() calls with a more functional version. Original:
let ShortenRomanNumeral (num : string) : string =
num.Replace("VIIII", "IX").Replace("IIII", "IV").Replace("LXXXX", "XC").Replace("XXXX", "XL").Replace("DCCCC", "CM").Replace("CCCC", "CD")
Functional version that works with one key value pair:
let ShortenRomanNumeral' (str : string) (k : string) (v : string) : string =
let strAfterReplace =
str.Replace(k, v)
strAfterReplace
I'm struggling to extend it to work with a list of tuples, such as
let replacements = [("VIIII", "IX"); ("IIII", "IV"); ...]
How can I write this function to apply the Replace() to the string for each key and value in the replacements list?
Fold is good. But just to demonstrate another way to do it...
// You can put the input string
// as the LAST parameter not first
let shortenRomanNumeral (k:string,v:string) (input:string) =
input.Replace(k,v)
// This allows you to do partial application like this
let replace4 = shortenRomanNumeral ("IIII", "IV")
let replace9 = shortenRomanNumeral ("VIIII", "IX")
// replace9 and replace4 have the signature string->string
// they are now simple string transformation functions
replace4 "abcIIIIdef" |> printfn "result is '%s'"
replace9 "abcVIIIIdef" |> printfn "result is '%s'"
// and they can be composed together.
// Order is important. Do 9 before 4.
let replace4and9 = replace9 >> replace4
replace4and9 "VIIII abc IIII" |> printfn "result is '%s'"
// With this approach, you can now transform a list of tuples
// into a list of string transforms using List.map
let listOfTransforms =
[("VIIII", "IX"); ("IIII", "IV");]
|> List.map shortenRomanNumeral
// and you can combine all these into one big transformation
// function using composition
let transformAll =
listOfTransforms
|> List.reduce (>>)
// finally you can apply the big function
transformAll "VIIII abc IIII" |> printfn "result is '%s'"
A fold will do the job:
let ShortenRomanNumeral' (str : string) (k : string, v : string) : string =
let strAfterReplace =
str.Replace(k, v)
strAfterReplace
let replacements = [("VIIII", "IX"); ("IIII", "IV"); ]
let replaceValues str = List.fold ShortenRomanNumeral' str replacements
replaceValues "VI VII VIIII I II III IIII" // "VI VII IX I II III IV"
Note that I only modified the last parameter of ShortenRomanNumeral' to accept tupled values.

Applying a filter to get a single item and using the filter function to transform the result

In the following example code, I filter a list of strings on a regular expression, knowing that there can only be a single entry that will match that string. I then use the same match string to get 2 grouped values out of the single remaining value.
let input = ["aaaa bbbb";"aaabbbb";"cccc$$$$";"dddddda";" "]
let ValuesOfAB (input: string list) =
let matchString = "(?<a>\w+)\s(?<b>\w+)"
let value = input |> List.filter (fun line -> Regex.Matches(line, matchString).Count <> 0)
|> List.head
(Regex.Matches(value, matchString).[0].Groups.["a"].Value, Regex.Matches(value, matchString).[0].Groups.["b"].Value)
let a = ValuesOfAB input
Is there a better way where I don't have to use Regex.Matches on the same string again for a second time to get the values I wish to return?
Use List.pick:
let input = ["aaaa bbbb";"aaabbbb";"cccc$$$$";"dddddda";" "]
let valuesOfAB (input: string list) =
let matchString = "(?<a>\w+)\s(?<b>\w+)"
let v = input |> List.pick (fun line -> let m = Regex.Match(line, matchString)
if m.Success then Some m else None)
v.Groups.["a"].Value, v.Groups.["b"].Value
let a = valuesOfAB input
Explanation:
You would like to match the first string in the list and return Match object in order that you don't have to run Regex again. List.pick fits the task quite well.
With each string, you need to match at least once so Regex.Match and Match.Success is enough for the purpose.

How to convert string array to float array and substitute Double.NaN for non-numeric values?

I'm writing a parser for CSV data, and am trying to determine how to handle records
that are blank ("") or contain character data ("C"). The parser code I have below works great, but forces me to deal with the float conversions later. I'd like to be able to just make my string[][] a float[][], and handle the conversions when I parse the file, but I notice that it blows up with any non-numeric data. Ideally there would be no non-numeric or blank values, but they are unavoidable, and as such, have to be dealt with.
Can someone possibly recommend a concise approach to attempt to convert to Double, and then if it doesn't work, replace with Double.NaN instead? (Without sacrificing much performance if possible). Thank you.
let stringLine = [| "2.0"; "", "C"|]
let stringLine2Float = Array.map float stringLine
//desiredFloatArray = [| 2.0; Double.NaN; Double.NaN |]
type csvData = { mutable RowNames: string[]; mutable ColNames: string[]; mutable Data: string[][] }
let csvParse (fileString: string) =
let colNames = ((fileLines fileString |> Seq.take 1 |> Seq.nth 0).Split(',')).[1..]
let lines = fileLines fileString |> Seq.skip 1 |> Array.ofSeq
let rowNames = Array.init lines.Length string;
let allData : string [][] = Array.zeroCreate rowNames.Length
for i in 0..rowNames.Length - 1 do
let fields = lines.[i].Split(',')
allData.[i] <- fields.[1..]
rowNames.[i] <- fields.[0]
{ RowNames = rowNames; ColNames = colNames; Data = allData }
Use this instead of the built-in float conversion:
let cvt s =
let (ok,f) = System.Double.TryParse(s)
if ok then f else nan

Resources