F# Adding to a Mutable List - f#

I'm writing a console based calculator application for a university project and have run into a bit of an error.
When a user selects an option from the menu (1,2,3,4 etc), they are able to input 2 numbers and either add, subtract, divide or multiply the sum. After each successful sum is solved and displayed, I want them to be added to my List sumHistory.
Here is my code currently, I feel like it should work but I'm obviously running into something small and silly preventing the list from being displayed when told to!
let sumHistory = new List<string>()
match input with
|"1" -> Console.WriteLine("Please enter 2 integers: ")
let a= Console.ReadLine()
let b= Console.ReadLine()
let A: int = int32 a
let B: int = int32 b
let C = (add A B)
let D = (string A + " + " + string B + " = " + string C)
Console.WriteLine(D)
sumHistory.Add(D)
|"2" -> Console.WriteLine("Please enter 2 integers: ")
let a= Console.ReadLine()
let b= Console.ReadLine()
let A: int = int32 a
let B: int = int32 b
let C = (sub A B)
let D = (string A + " - " + string B + " = " + string C)
Console.WriteLine(D)
sumHistory.Add(D)
|"3" -> Console.WriteLine("Please enter 2 integers: ")
let a= Console.ReadLine()
let b= Console.ReadLine()
let A: int = int32 a
let B: int = int32 b
let C = (div A B)
let D = (string A + " / " + string B + " = " + string C)
Console.WriteLine(D)
sumHistory.Add(D)
|"4" -> Console.WriteLine("Please enter 2 integers: ")
let a= Console.ReadLine()
let b= Console.ReadLine()
let A: int = int32 a
let B: int = int32 b
let C = (mul A B)
let D = (string A + " * " + string B + " = " + string C)
Console.WriteLine(D)
sumHistory.Add(D)
|"5" -> sumHistory |> Seq.iteri (fun index item -> printfn "%i: %s" index sumHistory.[index])
|"6" -> let data = ReadInText()
data.Read()

[SOLVED]
After actually reading through my own code I realised it was just a silly mistake.
The List was declared within the while and therefore was being restarted cleared after every sum!
Moving let sumHistory = new List<string>() to the top of the document just underneath namespace declarations solved this.

Related

Any suggestions on how to optimize this string -> double parser further, in F#?

Part of testing, I have to load massive CSV files (200+mb each) and I'm trying to cut down the load time since this is adding up.
As the CSV files are all floating point numbers, I tried to see if I could speed that up.
One important point: I do NOT need precision beyond 5 digits after the comma. The code I post here parses it anyways to give a fair comparison to .NET's parser, but any shortcut that preserves that precision is good.
The code I came up with:
open System
open System.Diagnostics
[<EntryPoint>]
let main _ =
let r = Random()
let numbers =
seq {
for _ in 0 .. 10000000 do
let n = (1000. * r.NextDouble()) - 500.
yield (n, sprintf "%.8f" n)
}
|> Seq.toList
let parseString (number: string) : double =
let len = number.Length
let rec parse (index: int) (nSign: double) (signMultiplier: double) (nAccumulator: int64) : float =
if index < len then
match number.[index] with
| x when x >= '0' && x <= '9' -> parse (index + 1) (signMultiplier * nSign) signMultiplier (nAccumulator * 10L + int64 x - int64 '0')
| x when x = '.' -> parse (index + 1) nSign 0.1 nAccumulator
| x when x = '-' -> parse (index + 1) -nSign signMultiplier nAccumulator
| _ -> parse (index + 1) nSign signMultiplier nAccumulator
else
double nAccumulator * nSign
parse 0 +1. 1. 0L
let benchmark name (func: string -> double) =
let allowedError = 0.00001
printf "checking %s - " name
let sw = Stopwatch()
sw.Start()
numbers
|> List.iter (fun (num, str) ->
let parsed = func str
let delta = num - parsed
if abs delta > allowedError then
failwithf "(%f, %s) not matching %f" num str parsed
)
sw.Stop()
printfn "%i ms" sw.ElapsedMilliseconds
benchmark ".net parser" (fun x -> double x)
benchmark "parser1" (fun x -> parseString x)
0
Is there anything obvious I missed to speed this up?
Additionally, there is something I do not understand: I tried to make the test numbers an array instead of a list and suddenly the processing took longer, while I'd expect the opposite. If anyone has some insights as to why it is happening, I'd be happy to know. You can just change the toList to toArray and replace the List with Array in the benchmark loop to test this.
Edit:
to load the data, here is the code I use:
// load a csv file
let loadCSV (stream: Stream) =
seq {
use s = new StreamReader (stream)
while not s.EndOfStream do
yield s.ReadLine ()
}
|> Seq.map (fun line -> line.Split(",") |> Array.toList)
|> Seq.toList

With Elmish.wpf/F#, how to display in WPF a string option as the string or null, not "Some(string)"?

I am a newbie to F#. In WPF, I am using DisplayMemberBinding within a Datagrid as:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:AppointmentListView ItemsSource="{Binding Columns[0].AppointmentKeys}" Height="140" Background="Bisque">
<ListView.View>
<GridView>
<GridViewColumn Header="First" DisplayMemberBinding="{Binding FirstName}" Width="100"/>
<GridViewColumn Header="Last" DisplayMemberBinding="{Binding LastName}" Width="120"/>
<GridViewColumn Header="BirthDate" DisplayMemberBinding="{Binding BirthDate, StringFormat=d}" Width="100"/>
</GridView>
</ListView.View>
</local:AppointmentListView>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
The (complete) backing F# module (in Elmish.wpf) is:
module MyDataGrid.DataGrid
open Elmish
open Elmish.WPF
open System
type Visit =
{ ServiceTime: DateTime option
DoNotSee: Boolean option
ChartNumber: int option
LastName: string option
FirstName: string option
Mi: string option
BirthDate: DateTime option
PostingTime: DateTime option
AppointmentTime: DateTime option }
type Cell =
{RowNumber: int
ColumnNumber: int
AppointmentKeys: Visit list
ColumnTime: TimeSpan
AppointmentCount: int
AppointmentTime: DateTime option // all lines in the cell have the same appointment time.
}
let SetCell (rowNumber: int, columnNumber: int) =
let AppointmentsPerCell = 4
{RowNumber = rowNumber
ColumnNumber = columnNumber
AppointmentKeys = [for x in 1 .. AppointmentsPerCell ->
{
ServiceTime = Some System.DateTime.Now
DoNotSee = Some false
ChartNumber = Some 8812
LastName= Some ("LastName" + string x)
FirstName= Some ("FirstName" + string x)
Mi = Some "J"
BirthDate = Some(DateTime(2020,09,14))
PostingTime = Some DateTime.Now
AppointmentTime = Some DateTime.Now
}]
ColumnTime = System.TimeSpan.FromMinutes(float(columnNumber * 15))
AppointmentCount = 4
AppointmentTime = Some(DateTime.Now)
}
type Row =
{RowTime: string
Columns: Cell list}
let SetRow (rowNumber: int, startTime: System.TimeSpan)=
let columnCount = 4
let hr = System.TimeSpan.FromHours(1.0)
let rowTime = startTime + System.TimeSpan.FromTicks(hr.Ticks * int64(rowNumber))
{ RowTime = rowTime.ToString("h':00'")
Columns = [for columnNumber in 1 .. columnCount -> SetCell(rowNumber, columnNumber) ]
}
type Model =
{ AppointmentDate: DateTime
Rows: Row list
SelectedRow: Row option}
type Msg =
| SetAppointmentDate of DateTime
| SetSelectedRow of Row option
let init =
let rowCount = 9
let startTime = TimeSpan.FromHours(float(8))
{ AppointmentDate = DateTime.Now
Rows = [for rowNumber in 0 .. rowCount -> SetRow(rowNumber, startTime)]
SelectedRow = None
}
let update msg m =
match msg with
| SetAppointmentDate d -> {m with AppointmentDate = d}
| SetSelectedRow r -> {m with SelectedRow = r}
let bindings () : Binding<Model, Msg> list = [
"SelectedAppointmentDate" |> Binding.twoWay( (fun m -> m.AppointmentDate), SetAppointmentDate)
"Rows" |> Binding.oneWay( fun m -> m.Rows)
"SelectedRow" |> Binding.twoWay( (fun m -> m.SelectedRow), SetSelectedRow)
]
let designVm = ViewModel.designInstance init (bindings ())
let main window =
Program.mkSimpleWpf (fun () -> init) update bindings
|> Program.withConsoleTrace
|> Program.runWindowWithConfig
{ ElmConfig.Default with LogConsole = true; Measure = true }
window
The DisplayMememberBindings show LastName as "Some(LastName1)" and BirthDate as "Some(09/14/2020 00:00:00)".
How can I get the LastName: string option to return either null or the value of the string so the display shows "LastName1" and not "Some(LastName1)?
The same goes for the birth date, how to show BirthDate as "9/14/2020" and not "Some(09/14/2020 00:00:00)?
TIA
Full source code at: Example DataGrid
Your code only has three bindings. You should have a binding for every individual piece of data. Specifically, you should change your Rows binding from a OneWay binding to a SubModel binding. Then repeat this for all your other types.
Then, the question you specifically asked about is how to display LastName1 instead of Some(LastName1) and 9/14/2020 instead of Some(09/14/2020 00:00:00). Create the bindings for these individual pieces of optional data with Binding methods that ends in Opt like Binding.oneWayOpt or Binding.twoWayOpt.
For newbies like me, here is my full F# working solution in Elmish.WPF/F#:
module MyDataGrid.DataGrid
open Elmish
open Elmish.WPF
open System
module Visit =
type Model =
{ ServiceTime: DateTime option
DoNotSee: Boolean option
ChartNumber: int option
LastName: string option
FirstName: string option
Mi: string option
BirthDate: DateTime option
PostingTime: DateTime option
AppointmentTime: DateTime option
Id: int}
let SetVisits appointmentsPerCell = [for x in 1 .. appointmentsPerCell ->
{
ServiceTime = Some System.DateTime.Now
DoNotSee = Some false
ChartNumber = Some 8812
LastName= Some ("LastName" + string x)
FirstName= Some ("FirstName" + string x)
Mi = Some "J"
BirthDate = Some(DateTime(2020,09,14))
PostingTime = Some DateTime.Now
AppointmentTime = Some DateTime.Now
Id = x
}]
let bindings() = [
"FirstName" |> Binding.oneWayOpt( fun (_, m) -> m.FirstName)
"LastName" |> Binding.oneWayOpt( fun (_, m) -> m.LastName)
"BirthDate" |> Binding.oneWayOpt( fun (_, m) -> m.BirthDate)
"ServiceTime" |> Binding.oneWayOpt( fun (_, m) -> m.ServiceTime)
]
module Cell =
type Model =
{ RowNumber: int
ColumnNumber: int
AppointmentKeys: Visit.Model list
ColumnTime: TimeSpan
AppointmentCount: int
AppointmentTime: DateTime option // all lines in the cell have the same appointment time.
Id: int
}
let SetCell (rowNumber: int, columnNumber: int) =
let AppointmentsPerCell = 4
{RowNumber = rowNumber
ColumnNumber = columnNumber
AppointmentKeys = Visit.SetVisits AppointmentsPerCell
ColumnTime = System.TimeSpan.FromMinutes(float(columnNumber * 15))
AppointmentCount = 4
AppointmentTime = Some(DateTime.Now)
Id=rowNumber*10 + columnNumber
}
let bindings() =[
"AppointmentKeys" |> Binding.subModelSeq(
(fun (_, m) -> m.AppointmentKeys),
(fun v -> v.Id),
Visit.bindings
)
]
module Row =
type Model =
{ RowTime: string
Columns: Cell.Model list
Id: int }
let SetRow (rowNumber: int, startTime: System.TimeSpan)=
let columnCount = 4
let hr = System.TimeSpan.FromHours(1.0)
let rowTime = startTime + System.TimeSpan.FromTicks(hr.Ticks * int64(rowNumber))
{ RowTime = rowTime.ToString("h':00'")
Columns = [for columnNumber in 1 .. columnCount -> Cell.SetCell(rowNumber, columnNumber) ]
Id = rowNumber
}
let bindings () = [
"RowTime" |> Binding.oneWay( fun (_,r) -> r.RowTime)
"Columns" |> Binding.subModelSeq(
(fun (_, m) -> m.Columns),
(fun c -> c.Id),
Cell.bindings
)
]
type Model =
{ AppointmentDate: DateTime
Rows: Row.Model list
SelectedRow: Row.Model option}
type Msg =
| SetAppointmentDate of DateTime
| SetSelectedRow of Row.Model option
let init () =
let rowCount = 9
let startTime = TimeSpan.FromHours(float(8))
{ AppointmentDate = DateTime.Now
Rows = [for rowNumber in 0 .. rowCount -> Row.SetRow(rowNumber, startTime)]
SelectedRow = None
}
let update msg m =
match msg with
| SetAppointmentDate d -> {m with AppointmentDate = d}
| SetSelectedRow r -> {m with SelectedRow = r}
let bindings () : Binding<Model, Msg> list = [
"SelectedAppointmentDate" |> Binding.twoWay( (fun m -> m.AppointmentDate), SetAppointmentDate)
"Rows" |> Binding.subModelSeq(
(fun m -> m.Rows),
(fun r -> r.Id),
Row.bindings
)
"SelectedRow" |> Binding.twoWay( (fun m -> m.SelectedRow), SetSelectedRow)
]
let main window =
Program.mkSimpleWpf init update bindings
|> Program.withConsoleTrace
|> Program.runWindowWithConfig
{ ElmConfig.Default with LogConsole = true; Measure = true }
window
enter code here

F# problems calling a function that takes a integer and return a string

I'm pretty new to programming in F#, and I am working on a project at the moment, with a function that takes an integer and returns a string value.
My problem (se my code below) is that no matter what I do, I cant return the values of my str, calling my function.
let mulTable (n:int):string = string n
let mutable m = 1
let mutable str = ""
while m < 10 do
str <- "m\t" + str
m <- m + 1
printfn "%A" (mulTable str)
My idea here is that I want to store the value of m, in str, så that str in the end of my while loop contains the values of "1 2 3 4 5 6 7 8 9". But no matter what I try my printfn "%A" mulTable str, returns "this expressions was exspected to have type int, but here has type string". I have tried converting my str to a string in my mutable value like:
let mutable str = ""
let mutable str1 = str |> int
and then I try to call my str1 using function mulTable instead of calling str. But still it does not work.
What am I missing here? I've been trying every single possible solution I can think of, without being able to solve my problem.
A fix of your own algorithm could be:
let getSequenceAsString max =
let concat s x = sprintf "%s\t%i" s x
let mutable m = 1
let mutable str = ""
while m < max do
str <- concat str m
m <- m + 1
str
printfn "%A" (getSequenceAsString 10)
But as others have shown it's a lot of work that can be done more easily:
open System
let getSequenceAsString max =
String.Join("\t", [1..max-1])
If you want each number reverted as you ask for in a comment it could be done this way:
let getSequenceAsString min max =
let revert x =
let rec rev y acc =
match y with
| 0 -> acc
| _ -> rev (y / 10) (sprintf "%s%i" acc (y % 10))
rev x ""
String.Join("\t", ([min..max-1] |> List.map revert))
printfn "%A" (getSequenceAsString 95 105)
Gives:
"59 69 79 89 99 001 101 201 301 401"
You can easily join an array of strings into a string array, and then print it out if necessary.
open System
let xs = [1..9] |> List.map string
//you should avoid using mutable variables, and instead generate your list of numbers with an list comprehension or something similar.
String.Join("\t", xs)
//A little exploration of the List/Array/String classes will bring this method up: "concatenates the members of a collection using the separator string.
This gives me:
val it : string = "1 2 3 4 5 6 7 8 9"
I've adjusted the code to make it produce results similar to what you wanted:
let mulTable (n:int):string = string n
let mutable m = 1
let mutable str = ""
while m < 10 do
str <- mulTable m+ "\t" + str
m <- m + 1
printfn "%A" (str)
I've used your mulTable to convert m to string, but for printfn you don't need to use that, because str is already a string.
Still the result would be 9 8 7 6 5 4 3 2 1
There are more then one way to revert the string, one of them would be to split the string into an array of characthers and then revert the array. From resulting array we will build a new string again. It would look something like:
printf "%A" (new System.String(str.ToCharArray() |> Array.rev ))
Edit
To achieve the same result, I would suggest to use more functional style, using recursion and avoiding mutating variables.
let getNumbersString upperLimit =
let rec innerRecursion rem acc=
match rem with
| 0 -> acc
| _ -> innerRecursion (rem-1) (sprintf "%i "rem::acc)
innerRecursion upperLimit [] |> String.concat ""
getNumbersString 9
Will result in
val it : string = "1 2 3 4 5 6 7 8 9 "

F# solution to "Escape from Zurg" puzzle

I have written an F# program to solve the "Escape from Zurg" puzzle
My code is the following. but somehow something is wrong with the way I am returning the boolean value when the puzzle is solved.
On the line
retVal = Move (cost + (MoveCost toy1 toy2)) Right remainingElements
I get a warning
The expression should have type 'unit' but has type 'bool'. If assigning a property use the syntax 'obj.Prop <- expr'
and I see that even though the function returns true when the puzzle is soved. when it returns the retVal remains false.
Below is my code.
open System
type Direction =
| Left
| Right
type Toy = {Name: string; Cost: int}
let toys = [
{Name="Buzz"; Cost=5};
{Name="Woody"; Cost=10};
{Name="Rex"; Cost=20};
{Name="Hamm"; Cost=25};
]
let MoveCost toy1 toy2 =
if (toy1.Cost > toy2.Cost) then
toy1.Cost
else
toy2.Cost
let rec Move cost direction group =
match group with
| [] -> if (cost > 60) then
false
else
Console.WriteLine("Solution Found!")
true
| _ ->
match direction with
| Left ->
let retVal = false
let combinations = Set.ofSeq (seq {for i in group do for j in group do if i <> j then if i < j then yield i, j else yield j, i})
for pair in combinations do
let (toy1, toy2) = pair
let remainingElements = List.filter (fun t-> t.Name <> toy1.Name && t.Name <> toy2.Name) group
retVal = Move (cost + (MoveCost toy1 toy2)) Right remainingElements
if (retVal) then
Console.WriteLine ("Move " + toy1.Name + " and " + toy2.Name + " with the total cost of " + cost.ToString())
retVal
| Right ->
let retVal = false
let toysOnRightBank = List.filter (fun t-> not(List.exists (fun g-> g = t) group)) toys
for toy in toysOnRightBank do
let cost = cost + toy.Cost
let retVal = Move cost Left (toy :: group)
if (retVal) then
Console.WriteLine("Move " + toy.Name + " back with the cost of " + toy.Cost.ToString())
retVal
[<EntryPoint>]
let main args =
let x = Move 0 Left toys
0
You cann't reassign a let binding. It should be:
let mutable retVal = false
...
retVal <- Move (cost + (MoveCost toy1 toy2)) Right remainingElements
However, you could easily rewrite it so that mutable isn't needed:
let res =
[
for i in group do
for j in group do
if i < j then yield i, j elif i > j then yield j, i
]
|> List.filter (fun (toy1, toy2) ->
let remainingElements = List.filter (fun t-> t.Name <> toy1.Name && t.Name <> toy2.Name) group
Move (cost + (MoveCost toy1 toy2)) Right remainingElements)
match res with
| [] -> false
| _ ->
res |> List.iter (fun (toy1, toy2) ->
Console.WriteLine ("Move " + toy1.Name + " and " + toy2.Name + " with the total cost of " + cost.ToString()))
true
EDIT: I posted a complete solution on gist, if you need a reference implementation.

Char value in F#

Lets say I have a string "COLIN".
The numeric value of this string would is worth:
3 + 15 + 12 + 9 + 14 = 53.
So
A = 1, B = 2, C = 3, and so on.
I have no idea how to even start in F# for this.
let mutable nametotal = 0
let rec tcalculate name =
name.ToString().ToCharArray()
|> Seq.length
Here is what I have so far. The seq.length is just there for testing to see if the toCharArray actually worked.
What you have is decent; here's another version:
#light
let Value (c:char) =
(int c) - (int 'A') + 1
let CalcValue name =
name |> Seq.sum_by Value
printfn "COLIN = %d" (CalcValue "COLIN")
// may be of interest:
printfn "%A" ("COLIN" |> Seq.map Value |> Seq.to_list)
It assumes the original input is uppercase. "int" is a function that converts a char (or whatever) to an int; Seq.sum_by is perfect for this.
I also show an example of using map, not sure what you're interested in.
If the 'mapping' is more arbitrary, you could use a strategy like the code below, where you can specify a data structure of what value each letter maps to.
#light
let table = [
'C', 3
'O', 15
'L', 12
'I', 9
'N', 14
]
let dictionary = dict table
let Value c =
match dictionary.TryGetValue(c) with
| true, v -> v
| _ -> failwith (sprintf "letter '%c' was not in lookup table" c)
let CalcValue name =
name |> Seq.sum_by Value
printfn "COLIN = %d" (CalcValue "COLIN")
I've found a hackish way to do this using the ascii value of the character, and getting the number from there but i think there might be a better way.
let tcalculate name =
name.ToString().ToLower().ToCharArray()
|> Seq.map (fun char -> Convert.ToInt32 char - 96)
|> Seq.sum
work's beautifully and maybe even more efficient then "mapping" but I'd like to view the solution I asked for
thanks all.
all you need to do, is make the string lowercase, turn it into a char array like you have done, loop through each letter, take the value of each char and subtract the value of 'a' and add one. that will make each letter have the value of its position in the alphabet.
I realize this is very old but I am recently learning F# and playing with the ideas in this question. Maybe someone will find it useful:
let table =
Seq.zip ['A'..'Z'] (Seq.initInfinite((+) 1))
|> Map.ofSeq
let calc (input : string) =
let s = input.ToUpper()
match s with
| _ when Seq.forall System.Char.IsLetter s ->
Some (Seq.sumBy (fun c -> table.[c]) s)
| _ -> None
let sumOfChar name = // F# functional answer
name
|> List.ofSeq // to char array
|> List.map (fun c -> int (System.Char.ToUpper c) - int 'A' + 1) // to value
|> List.fold (+) 0 // sum
sumOfChar "Herb" // 33
// Or simply this version:
let sumOfCharBy name =
let value c = int (System.Char.ToUpper c) - int 'A' + 1
List.sumBy value (List.ofSeq name)
sumOfCharBy "HerbM" // 46
// or simply:
let sumOfCharBy name =
name |> Seq.sumBy (fun c -> int (System.Char.ToUpper c) - int 'A' + 1)
sumOfCharBy "HMartin" // 83

Resources