# IGrouping<K, T> and ILookup<K, T>

> A `GroupBy` that is not reduced to one row per group answers C#'s `IGrouping<K, T>` — each group is a sequence of its elements that also carries its `Key`, so you can hold a group, iterate it, take its first element, filter groups by their size, or turn them into a dictionary. `ToLookup` holds the same groups as an `ILookup<K, T>`, indexed by key, where a key it does not hold answers an empty sequence. Over rows read from the data store the rows are read first, then grouped exactly as C#'s LINQ groups them.

<!-- id: query-igrouping · area: query · stability: stable · html: https://osysharp.com/reference/query/igrouping/ -->

## Summary        {#summary}
Group a sequence and keep the groups — each one is a sequence of its elements with a `Key`, exactly as in C#:

```osy title="each group's key, its size and its first element" test app=query-igrouping
class Visit { public string Room = ""; public string Who = ""; public int Minutes; }

string Summary(List<Visit> visits) {
  var parts = new List<string>();
  foreach (var g in visits.GroupBy(v => v.Room)) {
    parts.Add(g.Key + ":" + g.Count() + ":" + g.First().Who);
  }
  return string.Join(",", parts);
}
```

Groups come out in the order their keys first appear, and each group's elements stay in source order.

## Signature      {#signature}
```osy syntax
IEnumerable<IGrouping<K, T>> xs.GroupBy(x => key)                  // groups of the elements
IEnumerable<IGrouping<K, E>> xs.GroupBy(x => key, x => element)    // groups of a projection of them
K           g.Key                                                  // the key every element of the group shares
T           g.First() / g.Last() / g.FirstOrDefault(x => p)        // one element of the group
ILookup<K, T>  xs.ToLookup(x => key[, x => element])               // the same groups, held by key
IEnumerable<T> lookup[key]                                         // that key's elements — EMPTY when absent
int         lookup.Count                                           // how many keys
bool        lookup.Contains(key)                                   // does it hold that key?
```

## Description    {#description}

### What a group is   {#group}
A group is a list of its elements that carries one more thing: the `Key` they share. So every list verb works on it
— `Count()`, `Sum(x => …)`, `OrderBy`, `First()`, `foreach` — and `g.Key` answers the key. A variable can hold one:
`IGrouping<string, Visit> g` is a type you can write.

That makes the ordinary C# shapes work, with C#'s results:

```osy title="first of each, groups bigger than one, and a count per key" test app=query-igrouping
List<string> FirstPerRoom(List<Visit> visits) =>
  visits.GroupBy(v => v.Room).Select(g => g.First().Who).ToList();

int RoomsVisitedTwice(List<Visit> visits) =>
  visits.GroupBy(v => v.Room).Where(g => g.Count() > 1).Count();

Dictionary<string, int> MinutesPerRoom(List<Visit> visits) =>
  visits.GroupBy(v => v.Room).ToDictionary(g => g.Key, g => g.Sum(v => v.Minutes));
```

### Over rows from the data store   {#stored-rows}
A grouping that reduces each group to **one row of key and aggregates** — `GroupBy(k).Select(g => new T { … g.Key,
g.Count(), g.Sum(…) })` — is a real `GROUP BY` in the database: see [GroupBy (and HAVING)](https://osysharp.com/reference/query/group-by/). Anything else you do with a
group needs the group's ROWS — its first element, the group itself, a `foreach` over it — so the rows the query
selects are read and then grouped, as C#'s LINQ would group them. A `Where` before the `GroupBy` still filters in the
database, so only those rows travel:

```osy title="each wall's hardest climb — the rows the Where selects, grouped" test app=query-igrouping-stored
entity Climb {
  [Required, MaxLength(40)] string Wall;
  [Required, MaxLength(40)] string Name;
  int Grade;
}

List<string> HardestPerWall() =>
  Climb.Where(c => c.Grade > 0)
       .GroupBy(c => c.Wall)
       .Select(g => g.Key + "=" + g.OrderByDescending(c => c.Grade).First().Name)
       .ToList();
```

### One element per group reads one or two rows a group   {#one-per-group}
When the projection uses the group only through `g.Key` and one pick — `g.First()`, `g.Last()`,
`g.FirstOrDefault()`, `g.LastOrDefault()`, optionally after `g.OrderBy(…)`/`ThenBy(…)` — the database returns only
the rows that decide each group: its first row in source order, which places the group, and the row the pick chooses.
`HardestPerWall` above reads two rows a wall however many climbs each wall holds. The answer is the one C# gives over
every row — ties go to the earlier row, as a stable sort leaves them — and an `OrderBy` before the `GroupBy` sets that
source order:

```osy title="each wall's easiest climb, the wall with the hardest climb first" test app=query-igrouping-stored
List<string> EasiestPerWall() =>
  Climb.OrderByDescending(c => c.Grade)
       .GroupBy(c => c.Wall)
       .Select(g => g.Key + ":" + g.Last().Name)
       .ToList();
```

While the current unit of work holds an unsaved change to those rows, every row is read and grouped instead, so the
answer includes what it has not saved yet.

### `ToLookup` — the groups, held by key   {#lookup}
`ToLookup` makes the same groups and holds them by key. Unlike a `Dictionary`, indexing it with a key it does not
hold is not an error: it answers an **empty** sequence. Enumerating it yields its groups, in first-appearance order.

```osy title="who visited each room — and nobody, for a room never visited" test app=query-igrouping
string Visitors(List<Visit> visits, string room) {
  ILookup<string, string> byRoom = visits.ToLookup(v => v.Room, v => v.Who);
  if (!byRoom.Contains(room)) return "nobody (" + byRoom.Count + " rooms visited)";
  return string.Join(", ", byRoom[room]);
}
```

A custom key comparer is not supported; normalise the key instead (`v => v.Room.ToUpperInvariant()`).

## See also       {#see-also}
- [GroupBy (and HAVING)](https://osysharp.com/reference/query/group-by/) — one row per group, computed by the database (and `Take(n)` on it)
- [LINQ over a local list](https://osysharp.com/reference/query/in-memory-linq/) — every other LINQ verb over a list you hold
- [Sequence fields on a class](https://osysharp.com/reference/class/collections/) — `List<T>`, `Dictionary<K, V>` and the other collections a group can become
