twreid
twreid

Reputation: 1453

List flattening in F#

I'm new to F# and trying to rewrite one of our applications in F# to try and learn it along the way and I am having a bit of trouble flattening a list. I have searched and found several answers, but I can't seem to get any of them to work.

My data type is saying it is val regEntries: RegistryKey list list

I would like it to only be one list.

Below is my code:

namespace DataModule

module RegistryModule =
    open Microsoft.Win32

let regEntries = 
    ["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"
     "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"]
    |> List.map (fun x -> Microsoft.Win32.Registry.LocalMachine.OpenSubKey(x))
    |> List.map (fun k ->
        List.ofArray (k.GetSubKeyNames())
        |> List.map (fun x -> k.OpenSubKey(x))
        |> List.filter (fun x -> x.GetValue("ProductId") <> null))

Upvotes: 15

Views: 12294

Answers (4)

JaredPar
JaredPar

Reputation: 754763

Try the following

let regEntries = 
    ["SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"
     "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"]
    |> Seq.collect (fun p ->
       let k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(p)
       k.GetSubKeyNames()
       |> Seq.map (fun x -> k.OpenSubKey(x)) 
       |> Seq.filter (fun x -> x.GetValue("ProductId") <> null)))
    |> List.ofSeq

The Seq.concat method is useful for converting a T list list to a T list. Note that I switched a lot of your List. calls to Seq. calls. There didn't seem to be any need to create an actual list until the very end hence I kept it as a simple seq

Upvotes: 14

Sam Eaton
Sam Eaton

Reputation: 1835

Flattening a list in F#, use the List.collect function with the id function as the predicate:

[ [1; 2]; [3; 4; 5]; [6] ] |> List.collect id

returns:

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

Works fine for nested lists:

let myList = 
  [ 
    [ [1; 2]; [3; 4; 5] ];
    [ [6]; [] ]
  ]

myList 
  |> List.collect id
(* [[1; 2]; [3; 4; 5]; [6]; []] *)

myList
  |> List.collect id
  |> List.collect id
(* [1; 2; 3; 4; 5; 6] *)

Upvotes: 2

aloisdg
aloisdg

Reputation: 23521

Note that if you found this question looking for a basic way to flatten:

let flatten (source : 'T seq seq) :'T seq =
    System.Linq.Enumerable.SelectMany(source, id)

This is a basic call to the .net SelectMany with the F#'s id function.

Upvotes: 1

Tomas Petricek
Tomas Petricek

Reputation: 243051

Using the various map and filter functions is definitely an option - and it works great (and it is also great way to learn about functional abstractions).

However, you can also use sequence comprehensions, which is a nice syntactic sugar that makes writing these sort of tasks a bit easier (in my opinion). To do the same thing as what is happening in the answer by Jared, you can write:

let subKeys = 
  [@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; //"
   @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"] // "

let regEntries = 
  [ for subKey in subKeys do     
      let k = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(subKey)
      for name in k.GetSubKeyNames() do
        let x = k.OpenSubKey(name) 
        if x.GetValue("ProductId") <> null then
          yield x ]

The nesting simply becomes nested for loop and filtering is expressed using if - to produce a new value of the sequence you can use yield and the fact that the expression is enclosed in square brackets makes it a list comprehension.

Upvotes: 6

Related Questions