This article has been localized into Czech by the community.
Seskupování dat: Metoda GroupBy()
Doposud jsme pracovali převážně se seznamy dat. Seřadili jsme je, omezili a přetvořili do nových objektů, ale jedna důležitá operace nám ještě chybí: Seskupování dat. Když data seskupujete, vezmete seznam něčeho a poté jej rozdělíte do několika skupin na základě jedné nebo více vlastností. Představte si, že máme zdroj dat jako tento:
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
Tento plochý seznam objektů uživatelů, ale může být zajímavé seskupit (tyto uživatele) například podle jejich domovské země nebo věku. S LINQ je to velmi snadné, i když použití metody GroupBy() může být na začátku trochu matoucí. Pojďme se podívat, jak to funguje:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByCountry = users.GroupBy(user => user.HomeCountry);
foreach(var group in usersGroupedByCountry)
{
Console.WriteLine("Users from " + group.Key + ":");
foreach(var user in group)
Console.WriteLine("* " + user.Name);
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
}
}
}
Výsledný výstup bude vypadat přibližně takto:
Users from USA:
* John Doe
* Jane Doe
* James Doe
Users from Germany:
* Joe Doe
* Jenna Doe
Příklad se může zdát trochu dlouhý, ale jak brzy pochopíte, většina z něj je jen příprava zdroje dat. Pamatujte, že všechna data by mohla stejně dobře pocházet z XML dokumentu nebo databáze - je to jen snazší ukázat na zdroji dat objektu, který můžete použít tak, jak je.
Zajímavá část je, když vytvoříme proměnnou usersGroupedByCountry. Uděláme to voláním metody GroupBy() na našem zdroji dat a dodáme parametr, podle kterého chceme data seskupit. V tomto případě chci, aby byli uživatelé seskupeni podle jejich domovské země, takže to je vlastnost, kterou dodávám metodě GroupBy(). Výsledkem je objekt s vlastností Key, která obsahuje hodnotu vlastnosti, podle které jsme data seskupili (HomeCountry v tomto případě), jakož i všechny objekty, které do skupiny patří. To použijeme v dalších řádcích k iteraci nad skupinami, které jsme právě vytvořili, a pro každou skupinu vytiskneme Key (HomeCountry) a pak iterujeme a tiskneme všechny objekty User ze skupiny.
Vlastní skupinové klíče
Jak vidíte, seskupování podle existující vlastnosti je velmi jednoduché, ale jak jste si možná již všimli, metody LINQ jsou velmi flexibilní. Je stejně jednoduché vytvořit vlastní skupiny, na základě čehokoliv, co se vám líbí - příkladem toho může být následující případ, kde vytváříme skupiny na základě prvních dvou písmen jména uživatele:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByFirstLetters = users.GroupBy(user => user.Name.Substring(0, 2));
foreach(var group in usersGroupedByFirstLetters)
{
Console.WriteLine("Users starting with " + group.Key + ":");
foreach(var user in group)
Console.WriteLine("* " + user.Name);
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
}
}
}
Jednoduše zavoláme metodu Substring() na jménu, abychom získali první dvě písmena, a poté LINQ na základě toho vytvoří skupiny uživatelů. Výsledek bude vypadat nějak takto:
Users starting with Jo:
* John Doe
* Joe Doe
Users starting with Ja:
* Jane Doe
* James Doe
Users starting with Je:
* Jenna Doe
Jak vidíte, máme svobodu volat metodu uvnitř metody GroupBy() - ve skutečnosti můžeme uvnitř dělat téměř cokoli, pokud vrátíme něco, co LINQ může použít pro seskupení položek. Můžeme dokonce vytvořit metodu, která vrátí nový údaj o položce, a poté ji použít k vytvoření skupiny, jak to děláme v následujícím příkladu:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByAgeGroup = users.GroupBy(user => user.GetAgeGroup());
foreach(var group in usersGroupedByAgeGroup)
{
Console.WriteLine(group.Key + ":");
foreach(var user in group)
Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
public string GetAgeGroup()
{
if (this.Age < 13)
return "Children";
if (this.Age < 20)
return "Teenagers";
return "Adults";
}
}
}
}
Všimněte si, jak jsem implementoval metodu GetAgeGroup() ve třídě User. Vrací řetězec, který definuje věkovou skupinu uživatele, a my ji jednoduše voláme v metodě GroupBy() a používáme ji jako klíč skupiny. Výsledek bude vypadat takto:
Adults:
* John Doe [42 years]
* Jane Doe [38 years]
Teenagers:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Children:
* James Doe [8 years]
Rozhodl jsem se implementovat metodu GetAgeGroup() ve třídě User, protože by mohla být užitečná i na jiných místech, ale někdy potřebujete jen rychlý kus logiky pro vytvoření skupin, který nebude použit jinde. V těchto situacích můžete logiku přímo dodat metodě GroupBy() jako lambda výraz, takto:
var usersGroupedByAgeGroup = users.GroupBy(user =>
{
if (user.Age < 13)
return "Children";
if (user.Age < 20)
return "Teenagers";
return "Adults";
});
Výsledek je samozřejmě stejný!
Seskupování podle kompozitního klíče
Dosud byly klíče našich skupin pouze jednoduché hodnoty, například vlastnost nebo výsledek volání metody. Nicméně máte možnost vytvořit vlastní klíče obsahující několik hodnot - tyto se nazývají složené (kompozitní) klíče. Příkladem použití by mohlo být, pokud bychom chtěli seskupit naše uživatele podle jejich domovské země a věku, jako v tomto příkladu:
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqGroup2
{
class Program
{
static void Main(string[] args)
{
var users = new List<User>()
{
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};
var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
foreach(var group in usersGroupedByCountryAndAge)
{
Console.WriteLine("Users from " + group.Key.HomeCountry + " at the age of " + group.Key.Age + ":");
foreach (var user in group)
Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
}
}
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string HomeCountry { get; set; }
}
}
}
Všimněte si syntaxe, kterou používáme v metodě GroupBy() - místo dodání jedné vlastnosti vytváříme nový anonymní objekt, který obsahuje vlastnosti HomeCountry a Age. LINQ nyní vytvoří skupiny na základě těchto dvou vlastností a přiřadí anonymní objekt k vlastnosti Key skupiny. Máme možnost použít obě vlastnosti, když iterujeme přes skupiny, jak můžete vidět. Výsledek bude vypadat nějak takto:
Users from USA at the age of 42:
* John Doe [42 years]
Users from USA at the age of 38:
* Jane Doe [38 years]
Users from Germany at the age of 19:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Users from USA at the age of 8:
* James Doe [8 years]
Jak je již zvykem, v tomto článku jsme používali syntaxi metody LINQ, ale dovolte mi poskytnout vám srovnávací příklad, jak by to mohlo být provedeno pomocí syntaxe dotazu LINQ:
// Method syntax
var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
// Query syntax
var usersGroupedByCountryAndAgeQ = from user in users group user by new { user.HomeCountry, user.Age } into userGroup select userGroup;
Shrnutí
Jak pravděpodobně vidíte z příkladů v tomto článku, metoda GroupBy() v LINQ je nesmírně silná. Skutečně vám umožňuje používat vaše data novými způsoby s velmi malým množstvím kódu. Dříve by to bylo buď velmi obtížné, nebo by to vyžadovalo relační databázi, ale s LINQ můžete použít jakýkoliv zdroj dat, který chcete a získáte stejnou, snadno použitelnou funkcionalitu.