Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq like function GroupMultipleValBy

Tags:

linq

f#

In the process of learning the awesomness that is F# + Linq I came to a problem I can not solve(nicely) using functional, OOP, nor Linq like syntax, would anyone be willing to help?

Lets say my input is the following sequence:

let db = seq [ ("bob", 90, ['x';'y'])
               ("bob", 70, ['z'])
               ("frank", 20, ['b'])
               ("charlie", 10, ['c']) ]

Rows could read for example "Student bob has enrolled in x,y in semester 90"

What I need is this instead:

[ ("bob",  [90; 70],  ['x'; 'y'; 'z']) 
  ("frank",    [20],  ['b'])
  ("charlie",  [10],  ['c']) ]

This would read instead "Bob has finished semesters 90,70 and taken x,y,z".

Linq/Relational approach usualy gives the most readable solutions to such problems. But the best I can come up with is:

type Student = string
type Semester = int
type Class = char

let restructure (inp:seq<Student * Semester * Class list>) = query {
        for (student, semester, classes) in inp do
        groupValBy (semester,classes) student into data 
        yield (data.Key, Seq.map fst data, Seq.collect snd data)
    }

Which is neither readable, nor fast, nor pretty, nor idiomatic, and due to intricacies of F# requires that I write the input type signature...

Is there a better way some GroupMultipleValBy function? Thank you very much!


2 Answers

If you can stick to "classic" F# code you can rewrite it in a more readable way (especially by using locals to make the code even more readable)
Well it seems Tomas beat me to this, we've gone roughly the same road with some "quirks" in the middle

let restructure inp =
  // could have been defined at a more global scope as helpers
  let fst3 (x, _, _) = x
  let flip f y x = f x y

  let folder cont (student, semester, classes) (_, semesters, allClasses) =
    cont (student, semester :: semesters, classes @ allClasses)

  let initialState = "", [], []

  inp
  |> Seq.groupBy fst3
  |> Seq.map (snd >> flip (Seq.fold folder id) initialState)

The hard part to understand is the folder one, to keep semesters in the wanted order we either have to add an extra step reversing that part or as done here using a continuation

That (and the use of flip to keep it point free) added with the use of @ makes me think Tomas code is "better" (but his answer makes semesters and classes seq instead of list)


Addendum

Here's Tomas code written in a way I find more readable (but that's a matter of taste) and maybe more agnostic about what's being manipulated although it's longer
[that doesn't take anything to it's answer which is great]

let restructure inp =
  // could have been defined at a more global scope as helpers
  let fst3 (x, _, _) = x
  let snd3 (_, y, _) = y
  let trd3 (_, _, z) = z

  let mapping (key, values) =
    key,
    // replace with commented part to have lists instead of seqs
    values |> Seq.map snd3, //[ for value in values -> snd3 value ],
    values |> Seq.collect trd3 //[ for value in values do yield! trd3 value ]

  inp
  |> Seq.groupBy fst3
  |> Seq.map mapping
like image 136
Sehnsucht Avatar answered Aug 17 '26 17:08

Sehnsucht


If you do not insist on using the query syntax (which is needed if you are working with databases, but is just one of the options when working with in-memory data), then I would probably use simple Seq.groupBy function:

db 
|> Seq.groupBy (fun (name, _, _) -> name)
|> Seq.map (fun (name, group) ->
    name,
    group |> Seq.map (fun (_, sem, _) -> sem),
    group |> Seq.collect (fun (_, _, courses) -> courses) )

Here, we are saying that we want to group records by student name and return a triple with:

  • The student name, which was used as the grouping key
  • Get semester of all the records
  • Collect all the courses they attended

This is not shorter than your version, but I think a combination of groupBy and map is a fairly common pattern that is quite easy to understand. That said, I'm quite curious to see other answers! I can imagine there would be a nicer way of doing this...

like image 26
Tomas Petricek Avatar answered Aug 17 '26 16:08

Tomas Petricek



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!