# LINQ, חלק ב' — GroupBy ו-FirstOrDefault📖 שיעור

## מה זה?

"כמה תלמידים יש בכל מחלקה?" — `Where`/`Select`/`OrderBy` לבד לא עונים על זה; צריך לקבץ תלמידים **לפי** מחלקה. `GroupBy` פותר בדיוק את זה. ובכיוון ההפוך — "מצא לי תלמיד בשם דנה" עם `Where(...).First()` **קורס** אם אין תלמיד כזה; `FirstOrDefault` פותר את זה בבטחה, ומחזיר `null` (או ערך ברירת מחדל) במקום לזרוק חריגה.

## מילות מפתח שחשוב לזכור

• `GroupBy` — מקבץ איברים לפי מפתח (`s => s.ClassName`), מחזיר קבוצות שכל אחת מכילה את המפתח ואת האיברים ששייכים אליו

• `First` — מחזיר את האיבר הראשון שעונה על תנאי; **זורק חריגה** אם אין אף איבר תואם

• `FirstOrDefault` — כמו `First`, אך מחזיר `null` (לטיפוסי Reference) או ערך ברירת מחדל (לטיפוסי Value) אם אין התאמה, במקום לזרוק

• `Count()` — סופר איברים באוסף/בקבוצה, אפשר עם או בלי תנאי סינון

```csharp
List<Student> students = new List<Student>
{
    new Student("Dana", "A") , new Student("Avi", "B"), new Student("Rotem", "A")
};   // assume a ClassName field was added

// GroupBy — grouping by class
var byClass = students.GroupBy(s => s.ClassName);

foreach (var group in byClass)
{
    Console.WriteLine($"Class {group.Key}: {group.Count()} students");
    foreach (var student in group)
    {
        Console.WriteLine($"  - {student.Name}");
    }
}
// Class A: 2 students (Dana, Rotem)
// Class B: 1 students (Avi)
```

```csharp
// First vs FirstOrDefault
Student found = students.FirstOrDefault(s => s.Name == "Dana");
if (found != null)
{
    Console.WriteLine($"Found: {found.Name}");
}

Student notFound = students.FirstOrDefault(s => s.Name == "Does not exist");
Console.WriteLine(notFound == null);   // true — didn't crash, just null

// Student crash = students.First(s => s.Name == "Does not exist");   // InvalidOperationException!
```

## הסבר עיקרי

`GroupBy` מחזיר "קבוצות של קבוצות" — כל איבר בתוצאת `GroupBy` הוא בעצמו אוסף (`IGrouping<TKey, TElement>`) עם `.Key` (ערך הקיבוץ, למשל "A") ורשימת האיברים ששייכים אליו. זו הסיבה שצריך `foreach` מקונן (על הקבוצות, ואז על האיברים בכל קבוצה) כדי להדפיס הכל — בדיוק כמו שהראנו בדוגמה.

למה `FirstOrDefault` הוא ברירת המחדל הבטוחה — בדיוק כמו ש-`int.TryParse` עדיף על `int.Parse` לקלט לא-מובטח (מהיחידה הבסיסית), `FirstOrDefault` עדיף על `First` בכל פעם שלא בטוח ב-100% שיש התאמה. `First` שימושי רק כשידוע בוודאות שהאיבר קיים (למשל, בדיוק בדקנו את זה קודם) — אחרת, זריקת `InvalidOperationException` היא הפתעה לא נעימה.

הקשר בין `GroupBy`, `Count`, ו-הפרויקט המסכם — קיבוץ נתונים ואז ספירה בתוך כל קבוצה הוא בדיוק הדפוס שמופיע בסיווג נתונים (Classification) — הפרויקט המסכם של היחידה (Naive Bayes) יסתמך על ספירת תדירויות בתוך קטגוריות, בדיוק כמו `GroupBy` + `Count()` שתרגלנו כאן.

## יתרונות

`GroupBy` הופך קיבוץ וספירה לפי קטגוריה לכמה שורות במקום מבנה `Dictionary` ידני מסובך; `FirstOrDefault` מונע קריסות על "לא נמצא" בלי `try`/`catch` מכביד; `Count()` (עם או בלי תנאי) נותן ספירה מהירה בלי לולאה ידנית.

## חסרונות

`GroupBy` על אוספים ענקיים דורש מעבר מלא על כל הנתונים (לא "עצל" כמו חלק מפעולות LINQ אחרות) — עלול להיות כבד לזיכרון על נתונים גדולים מאוד; קוד `foreach` מקונן על תוצאת `GroupBy` עלול להיות מבלבל למתחילים בהתחלה.

## נקודות חשובות

• `GroupBy` מחזיר קבוצות עם `.Key` (ערך הקיבוץ) ואוסף האיברים בכל קבוצה

• `First` זורק חריגה אם אין התאמה; `FirstOrDefault` מחזיר `null`/ברירת מחדל בבטחה

• `FirstOrDefault` הוא הבחירה הבטוחה כברירת מחדל, בדיוק כמו `TryParse` על קלט

• `Count()` יכול לקבל תנאי אופציונלי (`students.Count(s => s.Age >= 18)`) לספירה מסוננת ישירות

## טעויות נפוצות

• להשתמש ב-`First` כשלא בטוחים שיש התאמה, ולקבל `InvalidOperationException` בלתי-צפוי

• לשכוח לבדוק `null` אחרי `FirstOrDefault` ולנסות לגשת לשדה על תוצאה ריקה (`NullReferenceException`)

• לנסות לגשת לתוצאת `GroupBy` כאילו היא `Dictionary` רגיל, במקום `foreach` על קבוצות

## סיכום

`GroupBy` מקבץ איברים לפי מפתח, ומחזיר קבוצות עם `.Key` ואוסף האיברים בכל אחת — שימושי לסטטיסטיקות וספירה לפי קטגוריה. `FirstOrDefault` שולף איבר בודד בבטחה, ומחזיר `null`/ערך ברירת מחדל אם אין התאמה, במקום לזרוק חריגה כמו `First`. שני הכלים האלה, יחד עם `Where`/`Select`/`OrderBy` מהשיעור הקודם, משלימים את ארגז הכלים הבסיסי של LINQ שנדרש לפרויקט המסכם של היחידה.

## דוקומנטציה רשמית

[Microsoft Learn — GroupBy](https://learn.microsoft.com/dotnet/api/system.linq.enumerable.groupby)

[Microsoft Learn — FirstOrDefault](https://learn.microsoft.com/dotnet/api/system.linq.enumerable.firstordefault)

---

## תרגילים

### תרגיל 1 — GroupBy בסיסי

**המשימה:** מתוך `List<Student>` עם שדה `ClassName`, קבצו לפי מחלקה עם `GroupBy`, והדפיסו לכל מחלקה כמה תלמידים יש בה.

**בדיקה:** כל מחלקה מוצגת עם המספר הנכון של תלמידים ששייכים אליה.

### תרגיל 2 — FirstOrDefault בטוח

**המשימה:** כתבו פונקציה `Student FindStudentByName(List<Student> students, string name)` שמשתמשת ב-`FirstOrDefault` ומחזירה `null` אם לא נמצא, בלי לקרוס.

**בדיקה:** חיפוש שם קיים מחזיר את התלמיד הנכון; חיפוש שם לא קיים מחזיר `null` בלי חריגה.

---

## פרויקט מסכם

**המשימה:** בנו "דוח סטטיסטיקות כיתה" שמשלב את כל כלי ה-LINQ מהשיעורים.

**דרישות:**
1. `GroupBy` לפי `ClassName`, ולכל קבוצה חשבו ממוצע ציונים (`Average()`) וספירת תלמידים (`Count()`)
2. מיינו את הקבוצות לפי ממוצע יורד (`OrderByDescending`)
3. השתמשו ב-`FirstOrDefault` כדי למצוא ולהדפיס בנפרד את התלמיד עם הציון הגבוה ביותר בכל המערכת (כולל טיפול במקרה של רשימה ריקה)

**בדיקה:** הדוח מציג את כל המחלקות ממוינות לפי ממוצע, כל אחת עם ממוצע וכמות תלמידים נכונים; התלמיד המצטיין מזוהה נכון בנפרד.

## מה בפרק הבא

בפרק הבא — **פרויקט מסכם: Naive Bayes** — פרויקט הסיכום הראשון של הקורס, שמשלב CSV, LINQ, OOP ו-SOLID לבניית מסווג טקסט פשוט מאפס.
