My Favorite Functions And Patterns: Group_by

Revelry Labs

Unleashing Human Potential with Technology

function: group by

My Favorite Functions and Patterns: group_by

Thank you for joining me as I share my favorite functions and patterns in functional programming. Today’s focus is on the function group_by. Previously, I published Function: with Statement and Function: Curry. This is the third installation in the series. For all of this week’s functions and patterns,  visit the Index.

I encountered this little gem while pair-programming with a colleague. It affords much potential in the domain of complex sorting operations.

Say we’ve a large list of unsorted, unordered exam results:

def list_exam_results do
    %{name: "Zelda", score: 56},
    %{name: "Eddy", score: 58},
    %{name: "Jack", score: 69},
    %{name: "Abby", score: 84},
    %{name: "Lily", score: 96}

First, we want to segregate records by letter grade (e.g., A, B, C, D, F), and then, for each group, we want to order results by student name. Elixir’s Enum.group_by is perfect.

Implementations of group_by generally take a list of items and then a predicate. Our predicate might look something like:

def by_grade(%{score: score}) do
  cond do
    score < 65 -> "F"
    score < 70 -> "D"
    score < 80 -> "C"
    score < 90 -> "B"
    true -> "A"

Which will take any of our records from list_exam_results and return a letter grade. On its own:

Enum.group_by(list_exam_results(), &by_grade(&1))

Will return:

  "A" => [%{name: "Lily", score: 96}],
  "B" => [%{name: "Abby", score: 84}],
  "D" => [%{name: "Jack", score: 69}],
  "F" => [%{name: "Zelda", score: 56}, %{name: "Eddy", score: 58}]

We’ve now positioned our data so that its subsets can be sorted by letter grade. Our final implementation might resemble:

def prepare_records(coll \\ list_exam_results()) do
  |> Enum.group_by(&by_grade(&1))
  |> {_letter_grade, subset} -> Enum.sort(subset) end)

Which will give us:

  [%{name: "Lily", score: 96}],
  [%{name: "Abby", score: 84}],
  [%{name: "Jack", score: 69}],
  [%{name: "Eddy", score: 58}, %{name: "Zelda", score: 56}]

With Zelda‘s record appearing after Eddy.