30 Kasım 2017 Perşembe

Linq GroupBy ile KeySelector

Giriş
İmzası şöyle
public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector
)
GroupBy ile Key ve Count Kullanımı
Elimizde şöyle bir liste olsun
"FF"
"ABC"
"CC"
"FF"
"FF"
Eğer bu listede FF değerine sahip kaç eleman var diye öğrenmek istersek şöyle yaparız.
var f = Rank.GroupBy(x => x)
            .Select(g => new { Value = g.Key, Count = g.Count() })
            .Where(s => s.Value == "FF");
Bu durumda şu sonucu alırız.
value: ff
count: 3
GroupBy ile Key Nesnesinin Alanlarına Erişmek
Yukarıdaki örnekten farklı olarak Key.Port gibi anahtarın alt parçalarına erişiyoruz. Elimizde Trip nesnesinden oluşuna bir nesne olsun
public  class Trip
{
  public string Port {get;set;}
  public string TripId {get;set;}
  public string StatusId {get;set;}
}
Bu listeyi TripId, ve Port alanlarına göre gruplayalım. Daha sonra sayısı >1 olan grupları seçelim. Yani aynı yere birden fazla yapılan seyahatleri seçiyoruz.
YourList
.GroupBy(c => new { c.TripId, c.Port })
.Where(grp => grp.Count() > 1)
.Select(grp => new { Port = grp.Key.Port, TripId = grp.Key.TripId });
Dictonary Haline Çevirmek
Elimizde iki tane dizi olsun.
{"A", "C", "A", "A", "B", "B", "A", "A" }
{ 1, 1, 2, 3, 1, 10, 5, 7 }
İlk diziye göre gruplamak için şöyle yaparız.
var list = first.Zip(second, (f, s) => new { First = f, Second = s });
Dictionary<string, int[]> d = list.GroupBy(i => i.First)
  .ToDictionary(k => k.Key, v => v.Select(val => val.Second)
                                  .ToArray()
                );
Çıktı olarak şunu alırız.
A: { 1, 2, 3, 5, 7 }
B: { 1, 10 }
C: { 1 }
Gruplanmış Listedeki Elemanlara Erişmek
Gruplama yapıldıktan sonra elimizde bir multimap varmış gibi düşünülebilir. Bu multimap aynı IEnumerable gibi metodlar sağlar.

Örnek - en küçük olanı seçmek
Elimizde şöyle bir sınıf olsun
class Member 
{
  public string CategoryId { get; set; }
  public string MemberName { get; set; }
  public int Distance { get; set; }
}
Bu sınıftan oluşan şöyle bir liste olsun .
var list = new List<Member>();
list.Add(new { CategoryId = "01", MemberName="andy" Distance=3});
list.Add(new { CategoryId = "02", MemberName="john" Distance=5});
list.Add(new { CategoryId = "01", MemberName="mathew" Distance=7});
list.Add(new { CategoryId = "03", MemberName="bakara" Distance=2});
Her grubun en kısa mesafeye sahip elemanını istersek şu çıktıyı bekleriz.
{ CategoryId = "01", MemberName="andy" Distance=3};
{ CategoryId = "02", MemberName="john" Distance=5};
{ CategoryId = "03", MemberName="bakara" Distance=2};
Dolayısıyla OrderBy ve ardından First çağırırsak beklediğimiz çıktıyı alırız.
var grouped = list.GroupBy(item => item.CategoryId);
var shortest = grouped.Select(grp => grp.OrderBy(item => item.Distance).First());
Örnek - en büyük olanı seçmek
İki listeyi birleştirip en büyük olanı seçmek için şöyle yaparız.
var result = item1.Union(item2)
                  .GroupBy(e => e.id)
                  .Select(g => 
                      g.OrderByDescending(e => e.Rating).First()
                  );
Örnek
Elimizde şöyle bir liste olsun.
BuildingID | Ticket | Amount |CustomerID | BuildingName 
10         | 001    | 50     | 1         | JP Building
11         | 002    | 45     | 1         | Tiskon
52         | 452    | 35     | 2         | Lalit
65         | 568    | 78     | 2         | Tuilp
41         | 121    | 12     | 1         | BK Trp
Multimap'in value listesini başka nesneye çevirmek için şöyle yaparız.
var objCustomerBuildingMappingResult = listTickets.GroupBy(l => l.CustomerID)
  .Select(grp => new CustomerBuildingMapping
        {
          CustomerID = grp.Key,
          LeadId = long.Parse(grp.Key) + 1000,
          BuildingInfo = grp.Select(l => new BuildingInfo {
            BuildingID = l.BuildingID,
            TicketNumber = l.Ticket,
            Amount = l.Amount,
            BuildingName = l.BuildingName
          }).ToList(),
  }).ToList();
Çıktı olarak şunu alırız.
LeadID 1001
CustomerID 1
BuildingInfo
               BuildingID | Ticket | Amount  | BuildingName 
               10         | 001    | 50      | JP Building
               11         | 002    | 45      | Tiskon
               41         | 121    | 12      | BK Trp
LeadID 1002
CustomerID 2
BuildingInfo
               BuildingID | Ticket | Amount  | BuildingName 
               52         | 452    | 35      | Lalit
               65         | 568    | 78      | Tulip
3. Diğer
Bir diğer örnek MoreLinq'te Batch ismiyle geçiyor. Elimizde şöyle bir liste olsun
var listOfStrings = new List<string>
{
  "String 1",
  "String 2",
  "String 3",
  "String 4",
  "String 5",
  "String 6"
};
Bu listeyi 3'lük kümeler halinde bölerek işlemek istersek şöyle yaparız.Önce Batch isimli bir metod yazarız. İlk select listemizi yeni bir nesne ile sarmalar. GroupBy sarmalayan nesneleri 3'lük olacak şekilde kümeler. Son select ise gruplanmış ancak sarmalanmış olan nesneleri açarak sadece kendi nesnemizi içeren 3'lük liste döndürür.
public static class EnumerableExtensions
{
  public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items,
                                                       int maxItems)
  {
    return items.Select((item, inx) => new { item, inx })
      .GroupBy(x => x.inx / maxItems)
      .Select(g => g.Select(x => x.item));
  }
}
Daha sonra bu metodumuzu şöyle çağırırız.
foreach (var batch in listOfStrings.Batch(3))
{
  // batch is an IEnumerable<T>, and will have 3 items.
  foreach (var item in batch)
  {

  }
}
GroupBy'ın İçi Nasıldır
GroupBy bir multimap yani Lookup döner. İçi şuna benzer.
Func<TSource, TElement> elementSelector = x => x;

<TKey, TElement> lookup = new Lookup<TKey, TElement>(comparer);
foreach (TSource tSource in source)
{
  TKey key = keySelector(tSource);

  // simplified pseudo-code
  if (!lookup.Contains(key))
    lookup.Add(new Grouping(key)); 

  lookup[key].Add(elementSelector(tSource));
}

foreach(IGrouping<TKey, TElement> grouping in lookup)
  yield return grouping;



Hiç yorum yok:

Yorum Gönder