简体   繁体   中英

“Subsetting” a dictionary in F#

I'm a beginner in F# and I'm trying to write a function to subset a dictionary given list, and return the result.

I tried this, but it doesn't work.

let Subset (dict:Dictionary<'T,'U>) (sub_list:list<'T>) =
    let z = dict.Clear
    sub_list |> List.filter (fun k -> dict.ContainsKey k)
             |> List.map (fun k -> (k, dict.TryGetValue k) )
             |> List.iter (fun s -> z.Add s)

|> List.iter (fun s -> z.Add s);;
  --------------------------------------^^^

stdin(597,39): error FS0039: The field, constructor or member 'Add' is not defined

Perhaps there is a native function in F# to do that ?

thanks

EDIT thanks to @TheInnerLight for his answer below can you just educate me a bit more, and tell me how i should adapt that function if i want to return the original variable being modified ? (of course it would be possible to go from where we call that function, call it with a temp variable, and reassign)

You have written:

let z = dict.Clear

z is of type unit->unit yet you are calling z.Add .

I suspect you want to write

let subset (dict:Dictionary<'T,'U>) (sub_list:list<'T>) =
    let z = Dictionary<'T,'U>() // create new empty dictionary
    sub_list |> List.filter (fun k -> dict.ContainsKey k)
             |> List.map (fun k -> (k, dict.[k]) )
             |> List.iter (fun s -> z.Add s)
    z

TryGetValue is going to return something of type bool*'U in F#, which I suspect you don't want if already filtering by ContainsKey so you probably want to look up directly with dict.[k] .

Note that Dictionary is a mutable collection so if you were to actually call dict.Clear() , it wouldn't return a new empty dictionary, it would mutate the existing one by clearing all elements. The immutable F# data structure usually used for key-value relationships is Map , see https://msdn.microsoft.com/en-us/library/ee353880.aspx for things you can do with Map .

Here is a map version ( this is the solution I recommend ):

let subset map subList =
    subList 
    |> List.choose (fun k -> Option.map (fun v -> k,v) (Map.tryFind k map))
    |> Map.ofList

Edit (in response to the question edit about modifying the input variable):

It's possible to update an existing dictionary using the destructive update operator <- on a mutable variable.

Option 1:

let mutable dict = Dictionary<Key,Value>() // replace this with initial dictionary
let lst = [] // list to check against
dict <- sublist dict lst

Likewise, my first function could be changed to perform only a side effect (removing unwanted elements).

Option 2:

let subset (d : System.Collections.Generic.Dictionary<'T,'U>) (sub_list : list<'T>) =
    sub_list 
    |> List.filter (d.ContainsKey >> not)
    |> List.iter (d.Remove >> ignore)

For an F# beginner I don't really recommend Option 1 and I really don't recommend Option 2.

The functional approach is to favour immutable values, pure functions, etc. This means you will be better off thinking of your functions as defining data transformations rather than as defining a list of instructions to be performed.

Because F# is a multi-paradigm language, it's easy to fall back on the imperative in the early stages but you will probably gain the most from learning your new language if you force yourself to adopt the standard paradigm and idioms of that language even if those idioms feel strange and uncomfortable to begin with.

The immutable data structures like Map and list are pretty efficient at sharing data as well as providing good time complexity so these are really the go-to collections when working in F#.

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.

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