Sacred
Sacred

Reputation: 157

Adding elements to a Map in F# using a for loop

Code:

let studentMap =
    for i = 0 to count do
        Map.empty.Add(students.[i].getId(), students.[i].getScores())

I am trying to add to a map sequentially. I have student objects in an array and am trying to add them in order. I am confused at how to do this. I thought maybe you make an empty map then add to it in order via a for loop, but it always causes trouble and won't work. Is there a way to add items to a map using a for loop? The <key/value> pairs are this: <string, int array>. That is the way I want it formatted but it keeps giving me trouble. I'll restate the goal again to clarify: I want to be able to add items to a map using a for loop with my student objects array being used to get the appropriate data I need in there. I will be able to give it a string and get back that student's grades for example. I know it's a weird problem I'm working on, but I needed to try something simple at first.

Upvotes: 5

Views: 4764

Answers (4)

Mau
Mau

Reputation: 14468

In functional languages, the built-in data structures are immutable, that is, when you add an element, a new value of the data structure is returned.

If you want to convert one structure to another, you can often use one of the build-in functions like Map.ofArray. If you have a more complex scenario, like a function generating your values, then you can use a 'loop', and each iteration of the loop returns a new value of the data structure. The way to express such a 'loop' in a functional way, is to use a recursive function:

let studentMap =
    let rec loop currentMap i =
        if i >= count then currentMap
        else
            let newMap = Map.add (students.[i].getId()) (students.[i].getScores()) currentMap
            loop newMap (i+1)
    loop Map.empty 0

Now you have full flexibility in how you generate the values (they don't have to be part of an array) and you're avoiding mutable variables that are un-idiomatic in functional programming. Ideally, like in this case, the function should be tail-recursive to allow tail-call optimisation (and avoid stack overflow for large inputs).

Upvotes: 2

Fsharp Pete
Fsharp Pete

Reputation: 751

I think the Map.ofArray approach is probably the best, but it is worthwhile to point out that the for loops can done a bit better:

let map = new System.Collections.Generic.Dictionary<int, Scores>()
for student in students do
   map.Add(student.getId(), student.getScore())

This neatly avoids making array bounds mistakes.

Upvotes: 2

Lee
Lee

Reputation: 144136

You could use a mutable map variable:

let studentMap = 
    let mutable m <- Map.empty  
    for i = 0 to count do  
        m <- m.Add(students.[i].getId(), students.[i].getScores())
    m

or a mutable dictionary:

let studentMap = 
    let m = new System.Collections.Generic.Dictionary<int, Scores>()  
    for i = 0 to count do  
        m.Add(students.[i].getId(), students.[i].getScores())
    m

a more functional solution would be to use a fold:

let studentMap = seq { 0 .. count } |> Seq.fold (fun (m: Map<int, Scores>) i -> m.Add(students.[i].getId(), students.[i].getScores())) Map.empty

Upvotes: 4

polkduran
polkduran

Reputation: 2551

You can try a more functional idiomatic approach:

Let's say you have an array of type Student (in the example below is an empty array):

let students = Array.empty<Student>

You can transform your array in to a Map:

let mapStudents = students 
                    |> Array.map (fun s -> (s.getId(), s.getScore()))
                    |> Map.ofArray

Upvotes: 10

Related Questions