简体   繁体   English

F#基础知识:地图地图?

[英]F# Basics: Map of Maps?

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. 世界从一个学生开始(由他们的ID表示,例如1),每个学生都有许多班级ID,每个班级都有一个与他们相关的成绩(A +,F等)。

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? 如何在建议的版本中添加新的“学生”或“classID / grade”并返回新地图?

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 好的,所以我想出了如何添加它,我只需要给每个Map一个表名,例如

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 student1 = Map.find 1 students来获取特定学生的地图。

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] 您可以通过索引到地图来访问该值: students.[1] will return val it : Map<string,string> = map [("class1", "A"); ("class2", "B")] 将返回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. 另外两条评论:1)通常,您首先会构建某种数据结构,例如元组列表,然后从那里构建映射。

let classes = [("class1","A");("class2","B")]
classes |> Map.ofList

2) you can iterate over the map by Map.iter or Map.map: 2)您可以通过Map.iter或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 请查看: https//en.wikibooks.org/wiki/F_Sharp_Programming/Sets_and_Mapshttps://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. 虽然我能理解答案,但是在尝试s952163(重复变量声明)的答案时出现了语法错误,所以我想发布一个可能有帮助的替代方案。

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. 我刚开始使用f#,所以s952163的答案确实帮助我达到了上述目标。 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. 关于你的问题, let student = Map.add 43 "A+"意味着: Map.add需要3个参数,一个键,一个值和一个要添加的地图。 student is hence a function that takes a map, and returns a new map that also contains a key 43 with value "A+" 因此, student是一个获取地图的函数,并返回一个新地图,该地图还包含值为"A+"的键43

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. 这当然不是万无一失的 - 您仍然可以做1<StudentID> + 2<StudentID> ,但至少您无法使用学生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: 使用类内地图,您现在可以定义从classID到studentID到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: 您还在一条评论中询问了mutable / immutable - 不完全确定问题是什么,但这里有两种方法可以为学生ID 2积累成绩,具有不可变和可变的数据结构:

// 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) (使用do来突出显示具有副作用的部分,在大多数情况下do

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM