Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does one get the first key,value pair from F# Map without knowing the key?

How does one get the first key,value pair from F# Map without knowing the key?

I know that the Map type is used to get a corresponding value given a key, e.g. find.

I also know that one can convert the map to a list and use List.Head, e.g.

List.head (Map.toList map)

I would like to do this
1. without a key
2. without knowing the types of the key and value
3. without using a mutable
4. without iterating through the entire map
5. without doing a conversion that iterates through the entire map behind the seen, e.g. Map.toList, etc.

I am also aware that if one gets the first key,value pair it might not be of use because the map documentation does not note if using map in two different calls guarantees the same order.

If the code can not be written then an existing reference from a site such as MSDN explaining and showing why not would be accepted.

TLDR;

How I arrived at this problem was converting this function:

let findmin l = 
    List.foldBack
        (fun (_,pr1 as p1) (_,pr2 as p2) -> if pr1 <= pr2 then p1 else p2)
        (List.tail l) (List.head l)

which is based on list and is used to find the minimum value in the associative list of string * int.

An example list:

["+",10; "-",10; "*",20; "/",20]

The list is used for parsing binary operator expressions that have precedence where the string is the binary operator and the int is the precedence. Other functions are preformed on the data such that using F# map might be an advantage over list. I have not decided on a final solution but wanted to explore this problem with map while it was still in the forefront.

Currently I am using:

let findmin m =
    if Map.isEmpty m then 
        None
    else
        let result = 
            Map.foldBack 
                (fun key value (k,v) -> 
                    if value <= v then (key,value) 
                    else (k,v))
                m ("",1000)
        Some(result)

but here I had to hard code in the initial state ("",1000) when what would be better is just using the first value in the map as the initial state and then passing the remainder of the map as the starting map as was done with the list:

(List.tail l) (List.head l)

Yes this is partitioning the map but that did not work e.g.,

let infixes = ["+",10; "-",10; "*",20; "/",20]
let infixMap = infixes |> Map.ofList
let mutable test = true
let fx k v : bool =
    if test then 
        printfn "first"
        test <- false
        true
    else 
        printfn "rest"
        false
let (first,rest) = Map.partition fx infixMap

which results in

val rest : Map<string,int> = map [("*", 20); ("+", 10); ("-", 10)]  
val first : Map<string,int> = map [("/", 20)]

which are two maps and not a key,value pair for first

("/",20)

Notes about answers

For practical purposes with regards to the precedence parsing seeing the + operations before - in the final transformation is preferable so returning + before - is desirable. Thus this variation of the answer by marklam

let findmin (map : Map<_,_>) = map |> Seq.minBy (fun kvp -> kvp.Value)

achieves this and does this variation by Tomas

let findmin m = 
    Map.foldBack (fun k2 v2 st ->
        match st with
        | Some(k1, v1) when v1 < v2 -> st
        | _ -> Some(k2, v2)) m None

The use of Seq.head does return the first item in the map but one must be aware that the map is constructed with the keys sorted so while for my practical example I would like to start with the lowest value being 10 and since the items are sorted by key the first one returned is ("*",20) with * being the first key because the keys are strings and sorted by such.

For me to practically use the answer by marklam I had to check for an empty list before calling and massage the output from a KeyValuePair into a tuple using let (a,b) = kvp.Key,kvp.Value

like image 557
Guy Coder Avatar asked Feb 07 '23 05:02

Guy Coder


2 Answers

I don't think there is an answer that fully satisfies all your requirements, but:

  1. You can just access the first key-value pair using m |> Seq.head. This is lazy unlike converting the map to list. This does not guarantee that you always get the same first element, but realistically, the implementation will guarantee that (it might change in the next version though).

  2. For finding the minimum, you do not actually need the guarantee that Seq.head returns the same element always. It just needs to give you some element.

  3. You can use other Seq-based functons as @marklam mentioned in his answer.

  4. You can also use fold with state of type option<'K * 'V>, which you can initialize with None and then you do not have to worry about finding the first element:

    m |> Map.fold (fun st k2 v2 ->
        match st with
        | Some(k1, v1) when v1 < v2 -> st
        | _ -> Some(k2, v2)) None
    
like image 51
Tomas Petricek Avatar answered Feb 09 '23 18:02

Tomas Petricek


Map implements IEnumerable<KeyValuePair<_,_>> so you can treat it as a Seq, like:

let findmin (map : Map<_,_>) = map |> Seq.minBy (fun kvp -> kvp.Key)

like image 33
marklam Avatar answered Feb 09 '23 18:02

marklam