פרק 9c - מערך מונים


מערך מונים, מערך דגלים (מציינים), ומערך צוברים

מערך שבו כל אינדקס (מיקום) מייצג ערך מסוים, והערך במיקום זה מציין את מספר הפעמים שהערך הופיע.

שאלה לדוגמה

הבעיה: נתון מערך של מספרים בתחום 0–100. כתבו פונקציה המחזירה מערך מונים המצביע על כמות ההופעות של כל מספר.

פתרון

int[] CountOccurrences(int[] arr) {
  int[] counts = new int[101];
  foreach (int x in arr) {
    if (x >= 0 && x <= 100) counts[x]++;
  }
  return counts;
}

מערך דגלים בוליאניים (מערך מציינים)

מערך בוליאני (bool[]) בו כל אינדקס מייצג ערך מסוים, והערך true מציין נוכחות או תקינות, ו-false חוסר.

שאלה לדוגמה

הבעיה: נתון מערך של מספרים בתחום 0–100. כתבו פונקציה שבודקת אם מספר נתון n הופיע לפחות פעם אחת במערך.

פתרון

bool[] BuildFlags(int[] arr) {
  bool[] flags = new bool[101];
  foreach (int x in arr) {
    if (x >= 0 && x <= 100) flags[x] = true;
  }
  return flags;
}

bool Exists(int n, bool[] flags) {
  return (n >= 0 && n < flags.Length) && flags[n];
}

הערה חשובה: בשימוש בטווח ערכים פתוח (למשל כל מספר int), שיטות אלו עלולות לגרום לבעיות אחסון וביצועים קשות. במקרים מעשיים נהוג להשתמש במבני נתונים דינמיים (למשל Dictionary<int,int> או HashSet<int>).

דוגמא להתמודדות מאי 2025 יתכן שדורש הגדרות קומפילציה מיוחדות ```csharp using System; using System.Collections; using System.Collections.Generic; public class Program { private const int FLAG_VALUE = int.MinValue; //using Queue<int?> nullable takes 2X space /// /// working on queue from Collections generic (didn't check perf differences on Queue from Mivney) /// validates all whole numbers beween min, and max are in the queue. /// need to test again - it might be possible to revert to bool[] and still get it going /// under the 2.1 billion limit. /// /// </param> /// /// public static bool CheckValidQ(Queue q) { int max = int.MinValue; int min = int.MaxValue; int current = 0; int length = 0; // Find min, max, and count by looping through queue once // Add flag marker to detect when we've completed the loop q.Enqueue(FLAG_VALUE); do { current = q.Dequeue(); if (current != FLAG_VALUE) { //Console.WriteLine(current); if (current > max) max = current; if (current < min) min = current; length++; q.Enqueue(current); // Put it back at the end } } while (current != FLAG_VALUE); // Stop when we hit the flag // Check if range matches length (consecutive sequence) if (max - min + 1 != length) return false; // Create BitArray to track which values we've seen long range = (long)max - (long)min + 1; if (range > int.MaxValue) throw new ArgumentException("Range too large for BitArray"); var bits = new BitArray((int)range); // Loop through queue again to mark bits (flag is still at front) //int check = q.Dequeue(); // Remove the flag marker q.Enqueue(FLAG_VALUE); // Add it back at the end do { current = q.Dequeue(); if (current != FLAG_VALUE) { bits.Set(current - min, true); q.Enqueue(current); // Put it back at the end } } while (current != FLAG_VALUE); // Check if all positions are set to true for (int i = 0; i < 100; i++) //Console.WriteLine(bits.Get(i)); if (!bits.Get(i)) return false; return true; } /// /// Creates a test queue at the desired size, with sequential whole numbers. /// starting from int.MinValue + 5 /// public static Queue CreateReasonableTestQueue(int millions = 100) { long count = millions * 1_000_000L; const int startValue = int.MinValue + 5; Console.WriteLine($"Creating test queue with {count:N0} elements ({millions} million)..."); var queue = new Queue(); for (long i = 0; i < count; i++) { queue.Enqueue(startValue + (int)i); if (i % 10_000_000 == 0 && i > 0) // Progress bar Console.WriteLine($"Added {i:N0} elements..."); } Console.WriteLine($"Test queue created! Size: {queue.Count:N0}"); return queue; } public static void Main() { int millionElements = 2000; // above int.MaxValue, it's impossible to configure the bits array. try { Console.WriteLine("=== Queue Validation Test ==="); var startTime = DateTime.Now; Queue testQueue; testQueue = CreateReasonableTestQueue(millionElements); var creationTime = DateTime.Now; Console.WriteLine($"Queue creation took: {(creationTime - startTime).TotalSeconds:F2} seconds"); Console.WriteLine("Starting validation..."); bool isValid = CheckValidQ(testQueue); var endTime = DateTime.Now; Console.WriteLine($"Validation result: {isValid}"); Console.WriteLine($"Validation took: {(endTime - creationTime).TotalSeconds:F2} seconds"); Console.WriteLine($"Total time: {(endTime - startTime).TotalSeconds:F2} seconds"); Console.WriteLine($"Final queue size: {testQueue.Count:N0}"); } catch (OutOfMemoryException) { Console.WriteLine("ERROR: Out of memory! Try a smaller test size."); } catch (Exception ex) { Console.WriteLine($"ERROR: {ex.Message}"); } } } ``` </details> --- ## מערך צוברים (Prefix Sum Array) מערך שבו כל אינדקס `i` מכיל את הסכום המצטבר של כל הערכים במערך המקורי מ־0 עד `i`. שימוש במערך צוברים מאפשר חישוב סכום תת־מערך בין שני אינדקסים `l` ו־`r` בזמן O(1) לאחר בנייתו. ### שאלה לדוגמה **הבעיה:** נתון המערך `[1, 3, 5, 2, 4]`. בנו מערך צוברים, והשתמשו בו לחישוב סכום הערכים בתחום מ־`l=1` עד `r=3`. #### פתרון ```csharp int[] BuildPrefixSums(int[] arr) { int n = arr.Length; int[] prefix = new int[n]; prefix[0] = arr[0]; for (int i = 1; i < n; i++) { prefix[i] = prefix[i - 1] + arr[i]; } return prefix; } int RangeSum(int l, int r, int[] prefix) { return (l == 0) ? prefix[r] : prefix[r] - prefix[l - 1]; } // שימוש: int[] arr = { 1, 3, 5, 2, 4 }; int[] prefix = BuildPrefixSums(arr); // {1, 4, 9, 11, 15} int sum_1_3 = RangeSum(1, 3, prefix); // 11 - 1 = 10 ``` ### שאלה לדוגמה **הבעיה:** נתון מערך של מספרים בתחום 0–100. כתבו פונקציה המחזירה מערך מונים המצביע על כמות ההופעות של כל מספר. #### פתרון ```csharp int[] CountOccurrences(int[] arr) { int[] counts = new int[101]; foreach (int x in arr) { if (x >= 0 && x <= 100) counts[x]++; } return counts; } ``` --- ## מערך דגלים בוליאניים (מערך מציינים) מערך בוליאני (`[]bool`) בו כל אינדקס מייצג ערך מסוים, והערך `true` מציין נוכחות או תקינות, ו-`false` חוסר. ### שאלה לדוגמה **הבעיה:** נתון מערך של מספרים בתחום 0–100. כתבו פונקציה שבודקת אם מספר נתון `n` הופיע לפחות פעם אחת במערך. #### פתרון ```csharp bool[] BuildFlags(int[] arr) { bool[] flags = new bool[101]; foreach (int x in arr) { if (x >= 0 && x <= 100) flags[x] = true; } return flags; } bool Exists(int n, bool[] flags) { return (n >= 0 && n < flags.Length) && flags[n]; } ``` --- > **הערה חשובה:** בשימוש בטווח ערכים פתוח (למשל כל מספר `int`), שיטות אלו עלולות לגרום לבעיות אחסון וביצועים קשות. במקרים מעשיים נהוג להשתמש במבני נתונים דינמיים (למשל `>Dictionary<int,int` או `HashSet`). ## קישורים [⬅ עברו לפרק 9 - מערכים](/cs2/Chapter9) [⬅ עברו לפרק 9a - גרסת ללא אנימציות](/cs2/Chapter9a) [⬅ עברו לפרק 9c - מערך מונים וצוברים](/cs2/Chapter9c) [⬅ עברו לתרגול 9.3 - מערך מונים - עדיין לא קיים!~!!!!](/cs2/Chapter9Ex9.3) ## תרגול [⬅ עברו לתרגול 9.1 - מערך חד ממדי](/cs2/Chapter9Ex9.1) [⬅ עברו לתרגול 9.2 - מערכים - שאלות ב- CodeWars](/cs2/Chapter9Ex9.2) ## סרטונים [סרטוני פרק 9: הסרטון על מערך מונים](https://www.youtube.com/watch?v=LxYAJY81gNo&list=PLnVUJu2KuoA2cT3X-Fui7j6HZJWZM6vnK&index=24)