My professor has given our class hints on solving a problem and has proposed we firstly model our class using:
let students = Map.add 1 Map.empty Map.empty
The world starts of with one student (indicated by their ID eg 1), each student has many class ID's each having a Grade (A+,F etc) associated with them.
I'm having trouble understanding what this code actually means, I understand a single map. For example
let student = Map.add 43 "A+"
How do actually add a new 'student' or 'classID/grade' to the proposed version and return a new Map?
As a followup how would I access/iterate over elements over a map like this? Thanks, functional programming beginner here and really struggling.
Ok so I figured out how to add to it, I just needed to give each Map a table name for example
let students = Map.add 1 (Map.add 43 "A+" Map.empty) Map.empty
And I can just use Let student1 = Map.find 1 students
to get the map of a particular student.
let grades = Map.empty<string,string>
let students = Map.empty<int,Map<string,string>>
let students = students.Add(1,grades.Add("class1","A").Add("class2","B"))
let students = students.Add(2,grades.Add("class3","A").Add("class4","C"))
you can access the value by indexing into the map: students.[1]
will return val it : Map<string,string> = map [("class1", "A"); ("class2", "B")]
val it : Map<string,string> = map [("class1", "A"); ("class2", "B")]
Two other comments: 1) Generally you would build some sort of data structure first, for example a list of tuples, and build the map from there.
let classes = [("class1","A");("class2","B")]
classes |> Map.ofList
2) you can iterate over the map by Map.iter or Map.map:
let classes' = classes |> Map.ofList
and prevent grade inflation :-)
classes' |> Map.map (fun key value -> (key,value+"-"))
Please take a look at: https://en.wikibooks.org/wiki/F_Sharp_Programming/Sets_and_Maps and https://msdn.microsoft.com/en-us/library/ee353880.aspx
Although I could understand the answer, I had a syntax error when trying the answer from s952163 (duplicate variable declaration), so thought I'd post an alternative that may help.
Hopefully the comments in the code explain the advice.
// Alias the types for ease of use later
let studentRecords = Map.empty<int, Map<string, string>>
let grades = Map.empty<string, string>
// create a collection of students
// (note use of aliases here but could use Map.empty<>.Add if preferred)
let students =
studentRecords
.Add(1, grades.Add("class1", "A").Add("class2", "B"))
.Add(2, grades.Add("class1", "D").Add("class2", "C"))
// can index into the student map by the integer key
// and print out the value of the embedded map based on it's key
let studentId = 1
let className = "class2"
let student1 = students.[studentId]
printfn
"StudentId: %i obtained %s in %s"
studentId
student1.[className]
className
// can use Map.map to iterate and obtain some specifics from the embedded map
// eg. This prints out all students and their grades for "class2"
printfn "%s results:" className
students
|> Map.map (fun key value -> printfn "Student %i - %s" key value.[className] )
I have just started f# too, so the answer from s952163 really helped me get to the above. Hope it adds further insight to someone coming to this question.
Regarding your question what let student = Map.add 43 "A+"
means: Map.add
takes 3 arguments, a key, a value, and a map to which to add. student
is hence a function that takes a map, and returns a new map that also contains a key 43
with value "A+"
Another suggestion regarding datatypes: Grades are not free-form strings. You can make your code safer by using a datatype that closely matches the domain. For example, for grades, you can use
type Grade =
| APlus
| A
| B
| F // Omitting the other grades for brevity
If you do that and use pattern matching well, the compiler will help you a great deal with checking your code for corner-cases that you may have overlooked. Similarly, you can avoid mixing up integers that identify students with integers that identify classes.
[<Measure>]
type StudentID
[<Measure>]
type ClassID
// A class that contains 2 students with IDs 1 and 2
let initialClass = Set.ofList [ 1<StudentID>; 2<_> ]
// Adding a student with ID 3 gives a new class
let classWithNewStudent = initialClass.Add 3<_>
Note that you only need to add type annotation at one place, and you can use <_>
throughout the rest. This is not fool-proof of course - you could still do 1<StudentID> + 2<StudentID>
, but at least you are prevented from indexing a per-class map with a student ID.
With that in place, you can now build up maps for the grades within a class:
let gradesForClass101 =
[ (1<StudentID>, APlus); (2<_>, F) ]
|> Map.ofList
let gradesForClass206 =
[ (3<StudentID>, B); (2<_>, F) ]
|> Map.ofList
// Here's the student function from your question:
let addStudent43 = Map.add 43<_> APlus
// This is now a new map that contains students 2, 3, 43
let moreGrades = addStudent43 gradesForClass206
With the within-class maps in place, you can now define a map from classID to studentID to grade:
// This is now a map of maps: For each class, we store
// a mapping classID -> grades that all students got in this class
let gradesPerClass =
[ (206<ClassID>, gradesForClass206); (101<_>, gradesForClass101)]
|> Map.ofList
You were also asking about mutable/immutable in one of your comments - not completely sure what the ask was, but here are two ways of accumulating grades for a student ID 2, with immutable and mutable data structures:
// Compute all grades that student 2 gets
let gradesViaFold =
gradesPerClass
|> Map.fold (fun state _ grades ->
// We don't need the classID here, so use _ as the second arg
match grades.TryFind 2<_> with
// We found a grade for student 2: Add it at the beginning of the list
| Some grade -> grade :: state
// Student 2 did not get a grade for this class: leave the state
// (grades seen so far) empty
| _ -> state
) []
let printGrades g = Seq.iter (printfn "Grade %A") g
do printGrades gradesViaFold
// Compute all grades that student 2 gets, via a mutable list
let gradesMutable = System.Collections.Generic.List<Grade>()
do
gradesPerClass
|> Map.iter (fun _ grades ->
match grades.TryFind 2<_> with
| Some grade -> gradesMutable.Add grade
| _ -> ()
)
printGrades gradesMutable
(using do
just to highlight the parts with side-effects, this is not needed in most cases)
Here's another example of how to go through maps:
// A map from classID to grade for student 2
let gradesForStudent2 =
gradesPerClass
|> Seq.choose (fun (KeyValue(classID, grades)) ->
match grades.TryFind 2<_> with
| Some grade -> Some (classID, grade)
| _ -> None
)
|> Map.ofSeq
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.