Home > Blockchain >  map multiple elements with same key in F#
map multiple elements with same key in F#

Time:11-05

Suppose two sorted lists with same length, key value list let k = [1;1;2;2;3;3], and a number listlet n = [1;2;3;4;5;6]. The ith position on both lists maps with each other, meaning the element n=1 has a key value of k=1, n=2 has a key value of k=1... so on.

How to create a list that maps multiple elements with same key value in F#?

So the output would be in a format [[n1;n2;key];[n1;n2;key]...], the example output is [[1;2;1];[3;4;2];[5;6;3]].

CodePudding user response:

This can be achieved by "zipping" the two lists together and then grouping by the key:

let groupByKey keys numbers =
    List.zip keys numbers
    |> List.groupBy fst
    |> List.map (fun (key, values) -> key, (values |> List.map snd))

groupByKey k n // [(1, [1; 2]); (2, [3; 4]); (3, [5; 6])]

The above version of the function doesn't have the exact output format that you specified, but it is a more structured output, where each item in the list is a tuple of the key and the list of values for that key. This might be preferred for cleaner code, depending on what you're going to do with the result.

To get your specified format it can be changed slightly:

let groupByKey keys numbers =
    List.zip keys numbers
    |> List.groupBy fst
    |> List.map (fun (key, values) -> [
        yield! values |> List.map snd
        yield key 
    ])

groupByKey k n // [[1; 2; 1]; [3; 4; 2]; [5; 6; 3]]

CodePudding user response:

I think what you are looking for is Seq.zip, which takes two sequences (list are sequences) and gives back a sequence of pairs.

Also note you specified your lists with commas, but F# uses semi-colons to separate list elements.

let keys = [ 1; 1; 2; 2; 3; 3 ]
let values = [ 1; 2; 3; 4; 5; 6 ]

let map =
  values
  |> Seq.zip keys
  |> Seq.groupBy (fun (k, v) -> k)
  |> Seq.map (fun (k, vs) -> k, vs |> Seq.map snd |> Seq.toList)
  |> Map.ofSeq

printfn "%A" map

// map [(1, [1; 2]); (2, [3; 4]); (3, [5; 6])]

To get the format you are looking for (although I think you should consider a Map) you can unpack it like so:

let mapList =
  map
  |> Map.toSeq
  |> Seq.map (fun (k, vs) -> vs @ [ k ])
  |> Seq.toList

printfn "%A" mapList

// [[1; 2; 1]; [3; 4; 2]; [5; 6; 3]]

CodePudding user response:

What you describe is a map data-structure, so i would use one, instead of implementing it yourself with a bunch of list functions you can convert your two lists in such a way

let k = [1;1;2;2;3;3]
let n = [1;2;3;4;5;6]

let mapKeyToValues keys values =
    let folder m k v =
        m |> Map.change k (function
            | Some old -> Some (v :: old)
            | None     -> Some [v]
        )
    List.fold2 folder Map.empty keys values

Now with

let r = mapKeyToValues k n

You get a data-structur like

Map [
    1, [2; 1] 
    2, [4; 3]
    3, [6; 5]
]

This would be equivalent to the JSON-Object

{
    "1": [2,1],
    "2": [4,3],
    "3": [6,5]
}

Usually you could write

let r = Map (List.zip k n)

This way, out of a tuple with (k,v) you create such a map-datastructure. But a new key, just ovverrides an older, so you get

Map [
    (1, 2)
    (2, 4)
    (3, 6)
]

That's the reason why you need Map.change. You go through every (key,value) of a list with List.fold2 and then add it to the map data-structure. If a key is not present (the None case) you create a list as a value with your only value. In the Some case a key was already added, and you add your value to the list that is already present.

In this example i suppose the order of the values doesn't matter. If you want the same order as they appear in the value list, you must use List.foldBack2 instead of List.fold2. But you must change the order of some arguments. This should be an exercise for you, to understand it better.

There is a Map module with different functions to work with such a data-structure, and it also provides things like Map.map, Map.filter, Map.fold, Map.find, ... and so on. So use this instead. If really needed you also could use Map.toList to transform it back to a list again, or use Map.fold.


Anyway as a reminder, as some people use (List.zip and then List.map) or (List.zip and then List.fold). There is a List.map2 and List.fold2 that does this in one operation instead of two.

  • Related