51. foreach implementation

היום נדבר על איך עובד foreach בC#.

קיים ממשק שנקרא IEnumerable. לממשק זה פונקציה אחת:

1
2
3
4
public interface IEnumerable
{
IEnumerator GetEnumerator();
}

שמחזירה ממשק אחר שנקרא IEnumerator:

1
2
3
4
5
6
7
8
9
10
11
public interface IEnumerator
{
bool MoveNext();
object Current
{
get;
}
void Reset();
}

הממשק הזה מאפשר לנו לעשות 3 פעולות:

  1. להתקדם אחד קדימה (ולקבל תשובה אם הצלחנו להתקדם אחד קדימה)
  2. לראות מה האיבר הנוכחי
  3. לחזור להתחלה

החליטו שכל מי שרוצה שיוכלו לעשות foreach עליו, צריך לממש IEnumerable.

איך זה עובד?

כאשר אנחנו כותבים קוד מהסוג הבא:

1
2
3
4
foreach (Person person in people)
{
// Do stuff
}

מה שקורה באמת זה משהו כזה:

1
2
3
4
5
6
7
IEnumerator enumerator = people.GetEnumerator();
while (enumerator.MoveNext())
{
Person person = (Person) enumerator.Current;
// Do stuff
}

כך שבעצם foreach זה כולה syntactic sugar 😃

עכשיו, אפשר להסביר כל מיני דברים, למשל:

איך הוא יודע שהCollection שלי השתנה באמצע הforeach?

ובכן, למשל בList<T>, הפונקציה GetEnumerator, מחזירה איזשהו IEnumerator שמחזיק reference לList. בנוסף יש לשניהם Member שנקרא Version, שכל פעם שמתבצעת פעולה של שינוי על הרשימה (הוספה/הסרה/עדכון וכו’), הוא גדל ב1.

ואז בכל MoveNext, הEnumerator בודק ששני הVersionים שווים, ובמידה ולא, זורק Exception.

שבוע בר מנייה טוב

שתף

50. Factory by Dictionary

למי שלא מכיר, הDesign Pattern ששמו Factory הוא Design Pattern נפוץ המשמש לאתחול אובייקטים.

לדוגמה:

1
2
3
4
public abstract class Shape
public class Circle : Shape
public class Triangle : Shape
public class Square : Shape

יש לנו 3 מחלקות ואנחנו מעוניינים ליצור מחלקה עפ"י השם שלה.

המימוש הכי פשוט הוא משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ShapeFactory
{
public static Shape CreateByName(string name)
{
switch (name)
{
case "Circle":
{
return new Circle();
}
case "Triangle":
{
return new Triangle();
}
case "Square":
{
return new Square();
}
default:
{
return null;
}
}
}
}

ואז אנחנו יכולים להשתמש בזה ככה:

1
2
Shape triangle =
ShapeFactory.CreateByName("Triangle");

אלא שהמימוש לא כל כך יפה.

נוכל לעשות את זה טיפה יותר יפה באמצעות שימוש בDictionary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ShapeFactory
{
private static Dictionary<string, Func<Shape>> m_NameToShapeCreator;
static ShapeFactory()
{
m_NameToShapeCreator =
new Dictionary<string, Func<Shape>>();
m_NameToShapeCreator["Circle"] = () => new Circle();
m_NameToShapeCreator["Triangle"] = () => new Triangle();
m_NameToShapeCreator["Square"] = () => new Square();
}
public static Shape CreateByName(string shapeName)
{
Func<Shape> shapeDelegate;
bool shapeFound =
m_NameToShapeCreator.TryGetValue(shapeName,
out shapeDelegate);
if (shapeFound)
{
return shapeDelegate();
}
else
{
return null;
}
}
}

ולמרחיקי הלכת, ניתן לאתחל את הDictionary באמצעות Collection Initializer:

1
2
3
4
5
6
7
8
9
10
static ShapeFactory()
{
m_NameToShapeCreator =
new Dictionary<string, Func<Shape>>()
{
{"Circle", () => new Circle()},
{"Triangle", () => new Triangle()},
{"Square", () => new Square()}
};
}

שימו לב שלא דיברנו כאן איך נכון לאתחל את הFactory (כלומר את הDictionary הזה), אלא רק איך אפשר לנצל את הDictionary בשביל המטרה של Factory.

סופ"ש אדיר

שתף

49. Recursive Dictionary

לפעמים אנחנו רוצים ליצור מבנה נתונים מקונן:

למשל:

1
Dictionary<string, Dictionary<string, int>> firstNameToLastNameToId;

ואז למשל נוכל לעשות משהו כזה:

1
firstNameToLastNameToId["John"]["Doe"] = 3;

או יותר מדויק, כזה:

1
2
3
4
5
6
7
8
9
10
11
firstNameToLastNameToId =
new Dictionary<string, Dictionary<string, int>>()
{
{
"John", new Dictionary<string, int>()
{
{"Smith", 123456789},
{"Doe", 987654321}
}
}
};

עם זאת, לפעמים ייתכן ואנחנו רוצים שהקינון יהיה ללא הגבלה, למשל אם אנחנו רוצים לבנות איזשהו עץ.

אפשר לפתור את הבעיה בצורה הבאה:

1
2
3
4
public class NestedCollection<TKey> :
Dictionary<TKey, NestedCollection<TKey>>
{
}

ואז נוכל ליצור משהו כזה:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
NestedCollection<string> banaiFamily =
new NestedCollection<string>()
{
{
"Meir",
new NestedCollection<string>()
{
{
"Jacob",
new NestedCollection<string>() {{"Ehud", null}}
},
{
"Yossi",
new NestedCollection<string>() {{"Yuval", null}}
},
{
"Yizhak",
new NestedCollection<string>()
{
{"Meir", null},
{"Orna", null},
{"Evyatar", null}
}
},
{
"Haim",
null
}
}
}
};

מגניב, לא?

יום מעולה

שתף

48. Member Access

מדי פעם אנחנו נתקלים בשורות מהצורה הבאה:

1
2
3
4
Organization facebook;
facebook.GetDepartment("Administration").People[0].Name = "Mark";
facebook.GetDepartment("Administration").People[0].LastName = "Zuckerberg";

במקום זאת, עדיף לכתוב כך:

1
2
3
4
5
6
Organization facebook;
Person admin = facebook.GetDepartment("Administration").People[0];
admin.Name = "Mark";
admin.LastName = "Zuckerberg";

ולמרחיקי הלכת!:

1
2
3
4
5
6
7
8
9
Organization facebook;
Department administartion =
facebook.GetDepartment("Administration");
Person admin = administartion.People[0];
admin.Name = "Mark";
admin.LastName = "Zuckerberg";

מהשיקולים הבאים:

  1. במקרה הטוב, לגישה לשני הMemberים הראשונים אין side effects, וכל מה שקורה כאן זה שנכתב יותר IL, מה שגם משפיע על הביצועים, אבל בצורה זניחה.
    זה אמנם לא נראה כך, אבל בשיטה הראשונה שהצגתי, כתבנו הרבה יותר קוד, שהרי התבצע אותו קוד מספר פעמים!
    שתי הכתיבות שהצגתי יוצרות פחות IL.
  2. במקרה הפחות טוב, לגישה לGetDepartment , לPeople ולאינדקס במקום אפס, יש side effects, ולכן קורות תופעות לוואי מספר פעמים.
  3. נוצר לנו קוד שיותר קל לתחזק אותו, ברור יותר ששתי השורות האלה הן אחת עם השנייה. הדבר גם עוזר לקריאות בד"כ.
  4. הדבר הכי חשוב, לדעתי, הוא שהדבר הזה מאפשר לנו לדבג יותר בקלות את הקוד, עם ביטויים פחות ענקיים בWatch, או בConditional Breakpoint וכו’. לא יודע כמה מכם נתקלו בתופעה שConditional Breakpoint מסובך גורם לVisual Studio לבטל אותו.

בכל אופן כדאי להשתמש בזה איפה שמתאים.

לדוגמה, אם היינו אולי משנים רק את השם הפרטי בדוגמה זו, אולי היה עדיף שנשאיר את זה בשורה אחת.

המשך יום טוב

שתף

47. System Action

בהמשך לטיפ היומי של אתמול,

מיקרוסופט גילו שהסוג השני הנפוץ של delegateים שיש לנו הם delegateים שלא מחזירים כלום, ומקבלים עד 4 פרמטרים.

לכן המציאו את Action (נמצאים בSystem.Core.dll בnamespace ששמו system, פרט לAction שנמצא בmscorlib 😃)

1
2
3
4
5
public delegate void Action();
public delegate void Action<T>(T obj);
public delegate void Action<T1, T2>(T1 arg1, T2 arg2);
public delegate void Action<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3);
public delegate void Action<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);

לדוגמה מהדוגמאות שראינו ביום חמישי:

1
2
3
4
public delegate void PrintDelegate(string output);
PrintDelegate printDelegate =
x => Console.WriteLine(x);

במקום נוכל להשתמש בAction:

1
2
Action<string> printDelegate =
x => Console.WriteLine(x);

גם זה חוסך לנו הרבה delegateים מיותרים ומגעילים.


באופן כללי בבואכם לכתוב delegateים משלכם חשבו על הנקודות הבאות:

  1. האם קיים delegate כזה כבר (לרבות Func וAction)
  2. אם לא, אולי אני עושה משהו לא בסדר (אולי אני מקבל יותר מדי פרמטרים, אולי זה לא נכון שאני מקבל פרמטרים מסוג params/ref/out)
  3. במידה ועברתם את שלב 2, חשבו אם אפשר להשתמש בGenerics כדי לאפשר Reuse לdelegate שלכם ולא להשתמש בו באופן חד פעמי
  4. זכרו שקיימים covariance וcontravariance ברמת השפה בין delegateים וmethod groups, כך שdelegate פחות/יותר ספציפי יכול לשמש יותר method groups ממה שנראה לעין.

המשך יום טוב

שתף

46. System Func

בהמשך לשבוע שעבר,

מסתבר שגילו אי שם במיקרוסופט שרוב הdelegateים שאנחנו יוצרים נראים כמו משהו כזה:

1
public delegate bool MyDelegate(string first, int second);

כאשר הפרמטרים יכולים להיות מכל סוג,

בד"כ אין יותר מ4, וגם הReturnType יכול להיות מכל סוג.

לכן החליטו לחסוך מאיתנו לכתוב אלפי delegateים כאלה, ויצרו לנו את הdelegateים הבאים בSystem.Core.dll בnamespace System:

1
2
3
4
5
public delegate TResult Func<TResult>();
public delegate TResult Func<T1, TResult>(T1 arg1);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);

יש פה הרבה פרמטרים גנריים, אבל בסופו של דבר זה חוסך לנו הרבה קוד:

למשל במקום הdelegate הזה שראינו בחמישי

1
public delegate int AddDelegate(int first, int second);

נוכל להשתמש בFunc ולכתוב משהו כזה:

1
2
3
4
5
6
Func<int,int,int> add =
(x, y) =>
{
double z = Math.Sqrt(x*x + y*y);
return Convert.ToInt32(z);
};

במקום הdelegate שכתבנו בתחילת המייל, נוכל להשתמש ב

1
2
Func<string, int, bool> stringEqualsNumber =
(str, number) => str.Equals(number.ToString());

הערה: בFramework 4.0 הוסיפו גרסאות של Func לעד 16 ארגומנטים.

שבוע טוב

שתף

45. Lambda expressions

ראינו בפעמים הקודמות מה זה Anonymous delegates ואיך משתמשים בהם.

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

1
2
3
Console.WriteLine(First(numbers,
delegate(int number)
{ return (number % 2 == 0); })); // Prints 15

או כזה:

1
2
3
4
5
Predicate<int> isOdd =
delegate(int number)
{ return (number%2 == 0); };
Console.WriteLine(First(numbers, isOdd)); // Prints 15

בC# 3.0 הגיעו למסקנה שזה פוגע בקריאות וגורם לאנשים לסלוד משימוש בAnonymous delegates.

לכן באו והציגו את הפתרון באמצעות Lambda Expressions.


מה זה Lambda Expression? מדובר על גישה כללית שמגיעה מתחום הלוגיקה המתמטית שנקראת תחשיב למבדא.

אחת המטרות היא לצמצם את כתיבת הגדרת פונקציות בצורה פורמלית.

בC# זה בא לידי ביטוי כך:

נוכל לכתוב במקום

1
2
3
Predicate<int> isOdd =
delegate(int number)
{ return (number%2 == 0); };

כך:

1
2
3
Predicate<int> isOdd =
(int number) =>
{ return (number%2 == 0); };

אלא שאפשר עדיין להתווכח ולומר שהביטוי לא קריא.

גילו שמסתבר שככל שמציינים פחות, הביטוי נהיה יותר קריא. למשל מאחר והחתימה של הdelegate נראית כך:

1
public delegate bool Predicate<T>(T item);

אין צורך שנציין את הסוג של הארגומנט בשני הצדדים, אלא הקומפיילר יגלה את זה implicity בזמן בקימפול.

אז הביטוי נהפך להיות כזה:

1
2
3
Predicate<int> isOdd =
(number) =>
{ return (number%2 == 0); };

בנוסף, בגלל שמדובר בפרמטר אחד, מותר לנו להשמיט את הסוגריים סביבו:

1
2
3
Predicate<int> isOdd =
number =>
{ return (number%2 == 0); };

לבסוף, בגלל שיש לנו שורת קוד אחת, והיא של return, נוכל גם להסיר את הסוגריים ואת הreturn:

1
2
Predicate<int> isOdd =
number => (number%2 == 0);

הביטוי נהיה מאוד פשוט והרבה יותר קריא!


קצת על כתיבה של Lambda expressions:

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

למשל אם יש לנו את הdelegate הזה:

1
public delegate int AddDelegate(int first, int second);

נוכל להציב לתוכו את הביטוי הבא:

1
AddDelegate add = (x, y) => x + y;

אם מדובר על delegate שלא מקבל פרמטרים, גם נצטרך לשים סוגריים:

1
2
3
public delegate string StringDelegate();
StringDelegate stringDelegate =
() => DateTime.Now.ToString();

שימו לב לסוגריים הריקים.

גם אם יש לנו חתימה שלא מחזירה ערך נוכל להשתמש בצורת כתיבה של שורה אחת:

1
2
3
4
public delegate void PrintDelegate(string output);
PrintDelegate printDelegate =
x => Console.WriteLine(x);

לבסוף, אם יש לנו מימוש של יותר משורה אחת, עדיין נוכל להשתמש בLambda Expression, אבל עם סוגריים מסולסלים:

1
2
3
4
5
6
AddDelegate add =
(x, y) =>
{
double z = Math.Sqrt(x*x + y*y);
return Convert.ToInt32(z);
};

שימו לב שזה עדיין יותר קריא מהכתיב של anonymous methods.

סופ"ש אנונימי מצוין

שתף

44. Local variables and anonymous delegates

בפעמים האחרונות ראינו קצת מה זה Anonymous delegates, והבנו גם למה זה טוב.

אחד הדברים המגניבים בAnonymous delegates זה שהם יכולים להשתמש במשתנים המקומיים של הפונקציה בה מוגדר הdelegate האנונימי.

למשל, אם נמשיך עם הדוגמה של אתמול, יש לנו סדרה של מספרים שלמים:

1
public delegate int Sequence(int n);

נוכל ליצור פונקציה שיוצרת סדרה חשבונית:

1
2
3
4
5
6
7
public static Sequence CreateArithmeticSequence(int first, int difference)
{
return delegate(int n)
{
return (first + difference*(n - 1));
};
}

שימו לב שהdelegate שלנו ניגש לפרמטרי הפונקציה ומשתמש בהם.

למשל אם נשתמש בזה:

1
2
3
4
5
6
7
8
Sequence oddNumbers = CreateArithmeticSequence(1, 2);
for (int i = 1; i <= 10; i++)
{
Console.Write(oddNumbers(i) + ",");
}
// 1,3,5,7,9,11,13,15,17,19

אבל אתם עשויים לחשוב שהdelegate האנונימי יכול לגשת אולי רק לפרמטרים של הפונקציה.

אז זהו שלא – ניצור באופן שקול פונקציה כזאת

1
2
3
4
5
6
7
8
9
public static Sequence CreateArithmeticSequence(int first, int second)
{
int difference = second - first;
return delegate(int n)
{
return (first + difference*(n - 1));
};
}

וגם זה יתקמפל ויעבוד:

1
2
3
4
5
6
Sequence oddNumbers = CreateArithmeticSequence(1, 3);
for (int i = 1; i <= 10; i++)
{
Console.Write(oddNumbers(i) + ",");
}

איך זה עובד?

מבט חטוף בReflector מראה שהקומפיילר החכם מג’נרט את הקוד הבא, בתור מחלקה פנימית:

(שיניתי את השמות כדי שיהיה אפשר לקרוא אותו קצת)

1
2
3
4
5
6
7
8
9
10
11
12
13
[CompilerGenerated]
private sealed class SequenceInfo
{
// Fields
public int difference;
public int first;
// Methods
public int CreateArithmeticSequence(int n)
{
return (this.first + (this.difference * (n - 1)));
}
}

ולמשל בדוגמה השנייה שראינו הקוד מקומפל למשהו כזה:

1
2
3
4
5
6
7
8
9
10
11
public static Sequence CreateArithmeticSequence(int first, int second)
{
int difference = second - first;
SequenceInfo sequenceInfo = new SequenceInfo();
sequenceInfo.first = first;
sequenceInfo.difference = difference;
return sequenceInfo.CreateArithmeticSequence;
}

מגניב, לא?

המשך שבוע אנונימי טוב

שתף

43. More anonymous delegates

מאחר ואני מרגיש שלא הצלחתי להבהיר את החשיבות של Anonymous delegates, אני שולח עוד דוגמה.

הפעם מהעולם המתמטי.

נניח שיש לנו סדרה מתמטית למשל

$ 1,2,3,4,5,6,7,8,9,10,\dots \\ 1,4,9,16,25,36,49,81,100,\dots \\ 4,8,15,16,23,42$

שלושת הסדרות הן סדרות מתמטיות. שתי הראשונות הן אינסופיות והשלישית היא סופית.

לעתים ניתן לתאר סדרה ע”י נוסחה מתמטית שמחזירה את איבר הסדרה במקום הn.

למשל במקרה הראשון ניתן לתאר את הסדרה ע”י הנוסחה $ n \mapsto n$, במקרה השני ע”י $ n \mapsto n^2$,

ובמקרה השלישי קשה למצוא נוסחה.

משהו שניתן לעשות על סדרה מתמטית הוא ליצור ממנה סדרה חדשה שנקראת סדרת הפרשים:

אותה ניתן לייצר ע”י לקיחת כל איבר והסרה ממנו של האיבר הקודם, למשל:

$ 1,2,3,4,5,6,7,8,9,10,\dots $

נקבל את סדרת ההפרשים

$ 2-1,3-2,4-3,5-4,6-5,7-6,8-7,9-8,10-9,\dots \\ 1,1,1,1,1,1,1,1,1,\dots$

דוגמה נוספת:

$ 1,4,9,16,25,36,49,81,100,\dots $

נקבל את סדרת ההפרשים

$ 4-1,9-4,16-9,25-16,36-25,49-36,81-49,100-81,\dots \\ 3,5,7,9,11,13,15,17,19,\dots$

כעת אם בא לנו לתכנת את זה, אפשר להשתמש בAnonymous delegates.

נגדיר delegate כזה

1
public delegate int Sequence(int n);

זה delegate שמייצג סדרה שכל איבריה טבעיים.

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

1
2
3
4
5
6
7
8
public static Sequence Difference(Sequence givenSequence)
{
return delegate(int n)
{
return givenSequence(n + 1) -
givenSequence(n);
};
}

כעת נוכל להשתמש בזה כדי למצוא סדרות הפרש:

למשל הדוגמה הראשונה:

1
2
3
4
5
6
7
8
9
10
11
12
13
Sequence naturalNumbers =
delegate(int n)
{
return n;
};
Sequence naturalDifference =
Difference(naturalNumbers);
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(naturalDifference(i));
}

ידפיס משהו כזה

$ 1,1,1,1,1,1,1,1,1,1$

בדוגמה השנייה

1
2
3
4
5
6
7
8
9
10
11
12
13
Sequence squareNumbers =
delegate(int n)
{
return n*n;
};
Sequence squaresDifference =
Difference(squareNumbers);
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(squaresDifference(i));
}

ידפיס משהו כזה:

$ 3,5,7,9,11,13,15,17,19,21$

מה שממש מגניב זה שאנחנו יכולים להפעיל סדרת הפרש כמה פעמים שאנחנו רוצים ברצף:

1
2
3
4
5
6
7
8
9
10
11
12
13
Sequence fifthPowers =
delegate(int n)
{
return n*n*n*n*n;
};
Sequence powersDifference =
Difference(Difference(Difference(fifthPowers)));
for (int i = 1; i <= 10; i++)
{
Console.WriteLine(powersDifference(i));
}

ונקבל משהו כזה

$ 390,750,1230,1830,2550,3390,4350,5430,6630,7950 $

זהו, בסה"כ מה שעשיתי פה די מסובך לעשות בלי Anonymous delegates.

המשך יום אנונימי טוב

שתף

42. Understanding anonymous delegates

פעם שעברה ראינו מה זה delegate אנונימי.

הרבה אנשים עשויים לחשוב (ולעתים, בצדק) שמדובר בסה”כ בקיצור מרושל לכך שלא היה למתכנת כוח לכתוב מתודה שתעשה בשבילו את העבודה.

אני אדגים עכשיו משהו שקל לעשות באמצעות anonymous delegates, ויותר מסובך לעשות בלי.

נניח שאנחנו רוצים לכתוב פונקציה שתדפיס את כל המחרוזות הבינאריות בגודל מסוים. זו משימה שנראית מאוד פשוטה, והנה קוד שרושם את כל המחרוזות הבינאריות בגודל 5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static IEnumerable<string> BinaryStrings()
{
List<string> result =
new List<string>();
for (int i5 = 0; i5 <= 1; i5++)
{
for (int i4 = 0; i4 <= 1; i4++)
{
for (int i3 = 0; i3 <= 1; i3++)
{
for (int i2 = 0; i2 <= 1; i2++)
{
for (int i1 = 0; i1 <= 1; i1++)
{
result.Add(string.Format("{0}{1}{2}{3}{4}",
i1,
i2,
i3,
i4,
i5));
}
}
}
}
}
return result;
}

כפי שאנחנו רואים, פשוט יש לנו 5 לולאות מקוננות אחת בתוך השנייה ואנחנו מדפיסים בכל פעם את הערכים של כולם משורשרים.

אז מה הבעיה?

נניח שאנחנו רוצים בצורה זו להדפיס את כל המחרוזות הבינאריות לפי גודל של פרמטר שקיבלנו.

נצטרך ליצור מספר לולאות מקוננות בגודל המספר שקיבלנו.

בעייתי קצת, לא?

שתי אופציות קלאסיות לפתור את הבעיה שאני מעלה עכשיו, הן הבאות. הראשונה היא להשתמש בייצוג הבינארי של המספר ואז לרוץ עד $ 2^n $. (פתרון איטרטיבי)

השנייה היא להשתמש ברקורסיה.

אני אראה דרך שלישית באמצעות anonymous delegates:

ניצור delegate כזה

1
public delegate IEnumerable<string> Concat(IEnumerable<string> elements);

כעת ניצור לו מימוש שמשרשר את $ 0,1 $ רק פעם אחת:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static IEnumerable<string> ConcatOnce
(IEnumerable<string> elements)
{
List<string> result = new List<string>();
for (int i = 0; i <= 1; i++)
{
foreach (string element in elements)
{
result.Add(element + i);
}
}
return result;
}

זה משרשר את $ 0 $ ו$ 1 $ לכל איבר שקיבלנו.

כעת נבצע הרכבה של פונקציות ונרכיב את הפונקציה הזאת עם עצמה הרבה פעמים:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static Concat BinaryStrings(int n)
{
Concat concat = ConcatOnce;
for (int i = 1; i < n; i++)
{
Concat current = concat;
concat =
delegate(IEnumerable<string> elements)
{
return ConcatOnce(current(elements));
};
}
return concat;
}

מה שעשינו בעצם זה פונקציה שמקבלת מספר n ויוצרת פונקציה חדשה שנראית כמו הקריאה

1
ConcatOnce(ConcatOnce(ConcatOnce(...)));

כאשר השרשור הוא $ n$ פעמים.

כעת נוכל לקרוא לה כך:

1
2
3
4
5
6
7
8
9
10
11
Concat concat = BinaryStrings(4);
IEnumerable<string> binaryStrings =
concat(new string[] {""});
// Same as binaryStrings =
// ConcatOnce(ConcatOnce(ConcatOnce(ConcatOnce(new string[]{""}))));
foreach (string binaryString in binaryStrings)
{
Console.WriteLine(binaryString);
}

המשך יום אנונימי טוב

שתף