אינדקס קודם בLINQ
-
-
נתנאל הקוד בסטאק הוא בדיוק בשבילך הן לקריאות והן לביצועים.
הרעיון הוא:- להימנע מלעבור על הפסקאות יותר מפעם אחת כי זו הפעולה היקרה
- לאפשר מוניפולציות מחוץ לקוד המעבר אבל שכן יבוצעו בשעת המעבר בשביל לחסוך "לשמור" וגם לבדוק דברים פעמיים.
מחר אראה לך דוגמה בס"ד.
פורסם במקור בפורום CODE613 ב22/12/2016 00:55 (+02:00)
-
הנה הדוגמה. הקלאס PrgClass מייצג פסקה, החלף את זה למה שרלוונטי לעניינך.
IEnumerable<Entry> GetAll() { PrgClass a = null; PrgClass b = null; PrgClass c = null; foreach (var element in xyz) //פה הלולאה על הפסקאות של וורד { a = b; b = c; c = element; yield return new Entry { Before = a, Current = b, After = c } ; } } class Entry { public PrgClass Before { get; set; } public PrgClass Current { get; set; } public PrgClass After { get; set; } }
מה המעלה בקוד הזה?
שאחרי זה אם אני כותב:var coll = GetAll(); coll = coll.Where (x => x.After == null || x.After.x > 5); coll = coll.Where(x => x.Before != null && x.Before.y == 6); coll = coll.OrderBy (x => x.Current.y); var result = coll.First();
אפי' שהעברתי תנאים בכמה שלבים, ואחרי זה בוצע סידור כל זה מתבצע במעבר אחד על הפסקאות.
כלומר הקוד הזה מתבצע כאילו כתבו את כל ההתניות והפרמטרים בגוף הלולאה, אף שזה מופרד וממילא הרב יותר נוח.
במה זה יותר טוב מאשר לייצא ידנית לליסט? א. יעילות עיבוד - אפי' שאפשר להוסיף כמה התניות בקטעי קוד שונים אין סריקה מחדש של האלמנטים - הכל מתבצע באותה הסריקה. ב. יעילות זיכרון. שכל אלמנט שכבר אין בו צורך (תוצאה שלילית על אחד מכל התנאים שיהיו) אזי הוא "נשכח" ונאסף ע"י אוסף הזבל אפי' תוך כדי הלולאה.
במה זה טוב מהתניות בגוף הלולאה? הרבה יותר נוח בגלל שאפשר לפצל את הקוד שמייצר את האלמנטים לבין ההתניות שאפשר להוסיף במקומות שונים עד לייצוא.פורסם במקור בפורום CODE613 ב23/12/2016 12:17 (+02:00)
-
תודה רבה! אני עדין באמצע לעכל את כל העושר התכנותי שמתעופף כאן בנדיבות.
כפי מה שהבנתי מהקוד, כל הענין בנוי על yeild שהוא בעצם מאפשר לנו להפסיק כל לולאה או כל אינומרבל ברגע שאנו לא צריכים אותו יותר, וכן ליצור אותו רק כשצריך. כמו כן זה עובד על זה שחלק מהאופרטורים של לינק עובדים על זה ג"כ שהם לא נקראים עד שהם נצרכים ובינהם Where. לכן נוצר מצב בו אני יכול להצהיר על אינוברבל, במקרה הזה הפסקאות עצמם, והיצירה שלו תהיה "באוויר" עד שיצטרכו לעשות איתו משהו, וזה ההפעלה של התנאים, ואז שעוברים עליו בפעם הראשונה, בכלל זה "לוקחים" בחשבון את התנאים, ולכן יוצא שהמעבר הוא רק פעם אחת.
אבל אם כן, לא הבנתי, למה צריך את yeild בפעם הראשונה. הרי בין כה וכה נצטרך לעבור על כל הפסקאות כדי לעבור עליהם כדי לראות אם הם מקימים את התנאים, ונמצא באמת אף פעם לא מופסק באמצע היצירה של האינמרבל. אבל אם היה תנאי עצירה למשל של רק כמה פסקאות במספר מסוים אז אני מבין מדוע היה צריך את זה כדי שלא נעבור על יותר ממה שצריך.
וגם, מה יהיה הדין אם נצטרך להשתמש באופרטורים "חמדניים" כמו ספירה או מינמום, אז באמת הכל יצטרך להתבצע מיד, ותנאים נוספים יתנו תוצאות שקריות מכיון שהאוסף עבר מניפולציה כלשהיא.List<string> ms = new List<string>() { "1","2","545","5445", "reewe" ,"qerwqerqwewrrwer"}; IEnumerable<string> result = ms.Where(o => o.Length > 3); int num = ms.Where(o => o.Length > 3).Count(); ms.Add("erwereeweweweeeeeeeesdsddddddddd"); foreach (var t in result) { Console.WriteLine(t); } Console.WriteLine(num);
הקוד הזה יחזיר 4 תוצאות, אבל ספירה של 3.
ובר מן דין , אין לי אפשרות להעביר את אוסף הפסקאות למשהו אינמרבל, כיון שהוא לא מקיים את האינטרפיס, ועל כן אני חייב לעשות את ההמרה הראשונה לאינמברל ידני לליסט.
וענין הפרדת הקוד סריקה והקוד תנאים, אני משתמש בדיקצנרי, שצד אחד שלו זה קלט מהתמשמש, סטרינגי בדרך כלל והצד השני שלו זה Func.
לכל תנאי כזה יש דיקצנרי משלו עם הפעולות שלו, ולחלקם יש פשוט return true אם לא נבחרה אופרציה מסוימת. אחרי זה אני משרשר אותם יחד ומפעיל. וככה זה יותר מופרד יחסית, כי בסריקה אני רק מציין לדלגציה שמופיעה בדיצקנרי. (וכדי לחסך בזמן אני רוצה שטעינה של המילונים תהיה אסנכורנית).פורסם במקור בפורום CODE613 ב24/12/2016 22:14 (+02:00)
-
כפי מה שהבנתי מהקוד, כל הענין בנוי על yeild שהוא בעצם מאפשר לנו להפסיק כל לולאה או כל אינומרבל ברגע שאנו לא צריכים אותו יותר, וכן ליצור אותו רק כשצריך. כמו כן זה עובד על זה שחלק מהאופרטורים של לינק עובדים על זה ג"כ שהם לא נקראים עד שהם נצרכים ובינהם Where. לכן נוצר מצב בו אני יכול להצהיר על אינוברבל, במקרה הזה הפסקאות עצמם, והיצירה שלו תהיה "באוויר" עד שיצטרכו לעשות איתו משהו, וזה ההפעלה של התנאים, ואז שעוברים עליו בפעם הראשונה, בכלל זה "לוקחים" בחשבון את התנאים, ולכן יוצא שהמעבר הוא רק פעם אחת.
נכון.
אבל אם כן, לא הבנתי, למה צריך את yeild בפעם הראשונה. הרי בין כה וכה נצטרך לעבור על כל הפסקאות כדי לעבור עליהם כדי לראות אם הם מקימים את התנאים, ונמצא באמת אף פעם לא מופסק באמצע היצירה של האינמרבל. אבל אם היה תנאי עצירה למשל של רק כמה פסקאות במספר מסוים אז אני מבין מדוע היה צריך את זה כדי שלא נעבור על יותר ממה שצריך.
- הרבה פעמים מחליטים "לעצור" בפעם הראשונה. למשל Take. או First, Any ועוד.
- השתלת כל התנאים והמיונים באותו המעבר - הראשון. אם אתה מחזיק איבר ביד, למה שלא תבדוק אם הוא עונה כבר על התנאים?
- חיסכון החזקת האיברים באיזה מקום - זיכרון. למשל קוד שיקרא רשימת קבצי טקסט ויחזיר אותם בyield וקוד אחר יבקש את שם האיבר/קובץ שמופיע בו מילה מסויימת - בשום שלב הזיכרון לא צריך "לסבול" יותר מתוכן של קובץ בודד.
וגם, מה יהיה הדין אם נצטרך להשתמש באופרטורים "חמדניים" כמו ספירה או מינמום, אז באמת הכל יצטרך להתבצע מיד, ותנאים נוספים יתנו תוצאות שקריות מכיון שהאוסף עבר מניפולציה כלשהיא.
לIEnumerable יש כמה מתודות שמחזירות ערך יחיד (Scalar Value) למשל מזכרוני:
Count, Sum, Avarage, Any, All, First, Last, Single,
כל אלה מפעילות את המניה מיידית - ולמעשה מסיימות את שרשרת הIEnumerable.
לא שייך תוצאות שקריות כי ממילא א"א לשרשר אחריהם - הם לא מחזירים אוסף.כמו"כ יש כמה מתודות ייצוא שמשמעותיות למקרה ויידרשו לגשת כמה פעמים לאוסף ואז יש לחסוך במניה (שיכולה להיות מתורגמת לפעולות רציניות מאוד כמו קריאה מDB וכל מה שיכול לעלות בדעתו של מספק האוסף) מיותרת:
ToArray, ToList, ToDictionary, ToLookupובר מן דין , אין לי אפשרות להעביר את אוסף הפסקאות למשהו אינמרבל, כיון שהוא לא מקיים את האינטרפיס, ועל כן אני חייב לעשות את ההמרה הראשונה לאינמברל ידני לליסט.
ממש ממש לא. אם המפתח שמספק את האוסף נתן לך List אז אין מה להמיר.
ואם הוא נתן אוסף שאינו מממש את הIEnumerabl הרי אפשר עדיין לעבור עליו בforeach ושם לשים את הyield. אכן ייתכן שאין הבדל בעלות של הפעלת הforeach ועצירתו מיידית לבין הפעלותו עדך הסוף - זה בהתאם לרמת המפתח שסיפק את האוסף.אגב הyield הוא צעיר מלינק. לפני קיומו היו כותבים מחלקה קצת ארוכה שהוציאה את החשק להשקיע.
גם כיום הקומפיילר מתרגם כל מתודה שמחזירה yield למחלקה בפני עצמה שיורשת מIEnumerable ובתוכה מחלקה של IEnumerator.
לIEnumerator יש מאפיין בשם Current שמחזיר איבר נוכחי, ומתודה בשם MoveNext שקוראת לאיבר הבא (ומחזירה שלילי בסיום). למעשה זה כל תורת לינק - לתת לסורק האוסף את השליטה מתי לזוז ומתי לפרוש.פורסם במקור בפורום CODE613 ב25/12/2016 01:15 (+02:00)
-
ישר כח גדול!
אין אפשרות לגשת ישירות לאוסף הפסקאות בלינק. משהו כזה לא עובד:var test = doc.Paragraphs.where(p=> p.Text=="tt")
וגם אי אפשר להמיר את זה לליסט לא ידני - אבל בהחלט לעבור על זה בforeach עובד גם עובד! (היה מונח לי בראש משום מה שרק מה שאפשר לעבור עליו עם foreach סובל לינק, והפוך, אבל אני מבין כרגע שזה לא תלוי זה בזה לגמרי).
ושוב תודה!פורסם במקור בפורום CODE613 ב25/12/2016 01:31 (+02:00)
-
אכן, אתה צודק ואני טעיתי כמה טעויות...
אין foreach בלי IEnumerable, ואכן הParagraps של וורד כן מממש IEnumerable.
אלא שמתודות ההרחבה של IEnumerable (כמו Wher וכל היתר) עובדים רק במימוש IEnumerable<T> כלומר הגירסה הגנרית.
ופה הטעות הגדולה: צריך סה"כ להמיר מIEnumerable לגירסה הגנרית, ובשביל זה אתה בכלל לא צריך לכתוב את הforeach עם yield.
יש שתי מתודות שממירים IEnumerable מסוג כלשהוא או ללא סוג כלל כמו אצלנו לאוסף גנרי מסוג אחר. אחד Cast והשני TypeOf.
שניהם ממומשים פשוט עם לולאה על האוסף עם yield, הCast פשוט מציב סוגריים לפני האיבר ומנסה לעשות קאסטינג - כלומר הוא מניח שזה בודאי מהסוג שציינו. ואילו הTypeOf בודק על כל איבר באמצעות is אם זה הסוג שצויין ומחזיר רק איבר שהתשובה לגביו חיובית.
במקרה שלך Cast זה מעולה. אתה פשוט כותב ככה:var test = doc.Paragraphs.Cast<Microsoft.Office.Interop.Word.Paragraph>(); test = test.Where (t => t.Range.Text.Length > 200);
פורסם במקור בפורום CODE613 ב25/12/2016 10:44 (+02:00)
-
מצוין, זה ממש פתח כפתחו של אולם. מרחיב לחלוטין את לינק. אבל במקרה שלנו אני אהיה חייב לא להתשמש באוסף כמות שהוא אפ' שאפשר לעשות עליו לינק, מכיון שאני רוצה ליצור ממנו טיפוס חדש שמכמס בתוכו את הפסקה לפני ואחריו וnull אם אין משהו לפני או אחרי, וא"כ הגם שforeach עם yeild יתן לי את האוסף כמו שהוא, אבל עדיין לא יהיה לי גישה לאברים לפני ואחרי(אא"כ סקיפ וכו'), ולכן אני משתמש בFor, גם עם yeild , וככה יש לי גם גישה לאינדקס, ולהכניס לקונסטרקטור את הפסקאות לפני ואחרי, וגם את האקטסנשיין, מכיון שהם לא פועלות על הפסקאות עצמם אלא על הטיפוס החדש, צריך לכתוב על הטיפוס החדש ולא על טיפוס הפסקאות של וורד.
פורסם במקור בפורום CODE613 ב25/12/2016 11:12 (+02:00)
-
מצוין, זה ממש פתח כפתחו של אולם. מרחיב לחלוטין את לינק. אבל במקרה שלנו אני אהיה חייב לא להתשמש באוסף כמות שהוא אפ' שאפשר לעשות עליו לינק, מכיון שאני רוצה ליצור ממנו טיפוס חדש שמכמס בתוכו את הפסקה לפני ואחריו וnull אם אין משהו לפני או אחרי, וא"כ הגם שforeach עם yeild יתן לי את האוסף כמו שהוא, אבל עדיין לא יהיה לי גישה לאברים לפני ואחרי(אא"כ סקיפ וכו'), ולכן אני משתמש בFor, גם עם yeild , וככה יש לי גם גישה לאינדקס, ולהכניס לקונסטרקטור את הפסקאות לפני ואחרי, וגם את האקטסנשיין, מכיון שהם לא פועלות על הפסקאות עצמם אלא על הטיפוס החדש, צריך לכתוב על הטיפוס החדש ולא על טיפוס הפסקאות של וורד.
קוראים לזה projecting.
גם זה אפשר בlinq. המתודה Select (ולמעשה עוד כמה מתודות) מחזירים טיפוס חדש. למעשה זה בדיוק קיצור למה שעשית - foreach עם yield.
דוגמה לselect:var coll = new int[] { 1, 2, 23, 4}; var query = coll.Select(c => new MyClass(c));
אבל בשביל הקודם והבא לדעתי אתה צריך לכתוב לולאה מפורשת כמו שכתבתי בכמה אופנים.
כל הדוגמאות של שימוש במתודות ההרחבה נקראות Fluent Syntax, בשביל הקריאות כדאי לפעמים להשתמש בquery syntax הנה דוגמה:
var collAsMyclass = from item in coll where item > 2 select new MyClass(item); var filtered = from item in collAsMyclass where item.MyProperty > 15 select item;
פורסם במקור בפורום CODE613 ב25/12/2016 12:22 (+02:00)
-
מה שכתבתי על אסקטנשיינס, התכוונתי לטיפוס החדש שעשיתי, שלא אצטרך כל פעם להכניס אותו אישית לפונקציה כדי לבדוק אותו. לא בסינטקס של לינק. אבל עדיין עם foreach אין לי גישה ללפני ואחרי מבחינת האינדקס. וגם הכתיבה צורכת התניות של ראשון ואחרון. עם for אני יכול לגשת לקונסטקטור בשקט בשורה אחת. אם זה היה עם foreach לא היתה לי גישה ישירה כפרפרוטי לקודם.
public IEnumerable<mypara> GetAllParas(word.Document doc) { for (int i = 1; i <= doc.Paragraphs.Count; i++) { yield return new mypara(doc.Paragraphs[i], i == 1 ? null : doc.Paragraphs[i - 1], i == doc.Paragraphs.Count ? null : doc.Paragraphs[i + 1]); }
ובאמת הוא אותו דבר, שגם "נצרך" על ידי המערכת רק ברגע הסינון הסופי של התניות.
פורסם במקור בפורום CODE613 ב25/12/2016 12:43 (+02:00)