JS: uniq על מערך של אובייקטים
-
מישהו מוכן להסביר לי מדוע הקוד הבא לא עובד???
כלומר, הוא עובד, אבל מחזיר לי את כל האובייקטים חזרה למרות ששלושה מהם הם כפולים?var array = [{id:15, name: "עברית"},{id:15, name: "עברית"},{id:15, name: "עברית"},{id:16, name: "תורה"}] var uniqArr = array.filter(function (v, i, arr) { return arr.indexOf(v) === i }) console.log(uniqArr)
הנה קישור לקודפן שם אפשר לראות את הקוד בפעולה (בקונסולה כמובן) <!-- s:-) --><img src="{SMILIES_PATH}/icon_e_smile.gif" alt=":-)" title="מחייך" /><!-- s:-) -->
אם התשובה היא בגלל שלעולם כל אובייקט שונה מחברו, גם אם המפתחות (זה המילה בעברית :lol: ) אותו דבר.. אז היש דרך מהירה ופשוטה לעשות uniq על אובייקטים? או שמא צריך לרוץ בלולאה ולנסות להשוות keys??
תודה מראש!
פורסם במקור בפורום CODE613 ב19/07/2017 23:22 (+03:00)
-
בלי שימוש בספריות חיצוניות, אפשר גם כך:
var array = [{id:15, name: "עברית"},{id:15, name: "עברית"},{id:15, name: "עברית"},{id:16, name: "תורה"}] var uniqArr = [array[0]] for(var i =0;i<array.length;i++){ if(!hasEqual(array[i], uniqArr)) uniqArr.push(array[i]) } function hasEqual(obj, arr) { for (i=arr.length;i--;) if (hasObjEqual(obj, arr[i])) return true return false } function hasObjEqual(obj, thisObj) { for (var prop in obj) if (obj[prop] !== thisObj[prop]) return false return true } console.log(uniqArr)
https://codepen.io/avrahamAhituv/pen/VWJgRZ
פורסם במקור בפורום CODE613 ב20/07/2017 00:06 (+03:00)
-
איזה שאלה יפה, והקוד שלה גם מוצלח ומבריק. ולמה זה לא עובד? כי indexOf עובד באובייקטים לפי זהות (האם זה האובייט הזה ממש עם אותו מצביע בזיכרון, מה שנקרא בדוטנט RefrenceEqual) ולא לפי השוואה ערכי המאפיינים. וזה גם טוב וברור שכך זה אמור להיות.
קודם כל הבעיה הקלאסית זה כשרוצים לפי מאפיין בודד שמהווה זיהוי של האובייקט, גם זה די קשה.
יש מתודה חדשה () שמה findIndex שמקבלת כפרמטר פונקציה שהיא פרדיקייט למה שמחפשים, למשל
[{"a": "bla bla bla"}, {"a": "bla bla"}].findIndex(x => x.a == "bla bla bla").
לפני כן מה שהיו עושים זה שימוש בmap שיוצר עותק של המערך עם חילוץ/יצירה שלערך עבור כל ערך מקור בהתאמה, ואז בוחרים לחילוץ את הערך הרצוי (למשל תז) או קומבינציה של הרצויים (זה יכול להיות אפי' כולם ע"י JSON.stringify) ואז את החיפוש עושים מול המערך השני:var arrObj = [{"a": "bla bla bla"}, {"a": "bla bla"}]; var arrForSearch = arrObj.map(x => x.a); //["bla bla bla", "bla bla"] var foundedIndex = arrForSearch.indexOf("bla bla bla");
זה פותר את הבעיה של זהות פר מאפיין אבל עדיין לא השוואה מול כלל המאפיינים והקוד שהבאת עושה זאת מצויין אם כי אני חושב שזה מצב שעדיף למנוע וגם זה לא מכסה כל מקרה למשל מאפיינים לא זהים שבאחד יש ובשני אין. הfinsIndex/map+indexOf בשילוב עם JSON.stringify זה מכסה הכל אבל זה מחריד לעבוד ככה..
פורסם במקור בפורום CODE613 ב20/07/2017 01:41 (+03:00)
-
אגב תחשוב, מה היית עושה עם linq? גם שם זה לא פשוט. השוואה לפי כלל המאפיינים קיימת בהשוואת struct, אבל במחלקות אין ברירה חוץ מרפלקשיין. כמובן תמיד ניתן להכין באובייקט מתודת השוואה ו/או לדרוס את הGetHashCode או להשתמש בIComparer וכו'.
אגב ראיתי כשמונה ספריות פופולריות של linq בjs ולבטח יש עוד הרבה...פורסם במקור בפורום CODE613 ב20/07/2017 02:06 (+03:00)
-
@דוד ל.ט.
אגב תחשוב, מה היית עושה עם linq? גם שם זה לא פשוט. השוואה לפי כלל המאפיינים קיימת בהשוואת struct, אבל במחלקות אין ברירה חוץ מרפלקשיין. כמובן תמיד ניתן להכין באובייקט מתודת השוואה ו/או לדרוס את הGetHashCode או להשתמש בIComparer וכו'.
אגב ראיתי כשמונה ספריות פופולריות של linq בjs ולבטח יש עוד הרבה...עוד לא יצא לי שהייתי צריך להשתמש בדבר כזה..
פשוט אני מקבל דטה שהיא מערך של שיעורים, ואני צריך להגיע למצב של ליסט רק של השיעורים שלא כפולים, מאידך אני צריך גם את שאר המידע שיש על כל שיעור, כגון המזהה שלו (לצורך החיפוש אח"כ) ועוד פרמטרים.
לפי מה שכתבת אז אני חושב שהיה אפשר גם לכתוב ככה, כדי להשיג אח"כ גם את התוכן:
var array = [{id:15, name: "עברית"},{id:15, name: "עברית"},{id:15, name: "עברית"},{id:16, name: "תורה"}] var uniq = array.map(x=>x.name).filter((v,i,arr) => arr.indexOf(v) ===i ) var uniqArr = uniq.map(x => array[array.findIndex(y => y.name === x.name)]) console.log(uniq) console.log(uniqArr)
אבל משום מה זה לא עובד..
כלומר הוא יוצר לי מערך ייחודי רק של הname. אולם, אח"כ הוא מחזיר לי כאילו השם הזה לא קיים במערך המקורי. -1
אא"כ אני כותב לו את השם המפורש דהיינו =='עברית' ואז אכן הוא מחזיר לי את האינדקס.. אלא שכמובן שאני לא יודע מה יהיה אותו תוכן..פורסם במקור בפורום CODE613 ב20/07/2017 10:26 (+03:00)
-
אם אתה משתמש בfindIndex אז אתה יכול לשכוח מהmap.
בשורה שתיים עשית מצויין אבל א. זה מסנן רק לפי הname ב. אתה צריך להוציא את האינדקסים של המקור, והmap מפסיד אותם לגמרי כפי שכתבת.
אם אתה מחפש רק לפי השם או אפי' אם לפי שתי המאפיינים ולא "איך אני משווה את כל המאפיינים" אז נורא פשוט. עם findIndex:var array = [{id:15, name: "עברית"},{id:15, name: "עברית"},{id:15, name: "עברית"},{id:16, name: "תורה"}]; var uniqArr = array.filter((item, index) => array.findIndex(x => x.name == item.name) == index); console.log(uniqArr);
ואם ע"י הmap, ובהנחה שדרוש רק השוואת מאפיין name, אז ככה:
var array = [{id:15, name: "עברית"},{id:15, name: "עברית"},{id:15, name: "עברית"},{id:16, name: "תורה"}]; var arrForIndexs = array.map(x => x.name); var uniqArr = array.filter((item, index) => arrForIndexs.indexOf(item.name) == index); console.log(uniqArr);
פורסם במקור בפורום CODE613 ב20/07/2017 14:03 (+03:00)
-
אגב, מי שעושה כאלה מוניפולציות בקוד יותר מפעם אחת חייב חייב להשתמש בunderscore.
לא בשביל חיסכון הזמן (כי בכל מקרה שורפים זמן איך לעשות בunserscore...) אלא בשביל נאמנות הקוד וקלות תחזוקו.
אני יודע שנמאס להוסיף עוד ועוד תלויות אבל underscore נחוץ כל שתי שורות קוד שקשורים למערך.פורסם במקור בפורום CODE613 ב20/07/2017 14:21 (+03:00)
-
תבדוק את זה:
https://codepen.io/anon/pen/WOVRBg?editors=1112var array = [{id:15, name: "עברית"}, {id:15, name: "עברית"}, {id:15, name: "עברית"}, {id:16, name: "תורה"}] var uniq = {}; array.forEach(function(i) { uniq[i.id] = i; }); console.log(uniq);
פורסם במקור בפורום CODE613 ב20/07/2017 16:13 (+03:00)
-
רחמים פתרון יפה, אבל אם כבר לפי איבר מסויים אז הבנתי שזה הname רק שצריך את המאפיין גם של הID. אז אם ככה uniq[i.name].
וavr416 פעם הבאה חוץ מהבעיה תסביר מה המטרה הרצוייה, והם שייך מקרה של id שונה ושם זהה ומה הדין בכהאי גונא ומדועפורסם במקור בפורום CODE613 ב20/07/2017 17:41 (+03:00)
-
הפתרון שכתבתי לעיל עדיף משמעותית מבחינת מהירות [פי 20] על פני שימוש במערך רגיל.
ראה ניסוי כאן
http://jsben.ch/GYK6Pפורסם במקור בפורום CODE613 ב20/07/2017 19:05 (+03:00)
-
@דוד ל.ט.
רחמים פתרון יפה, אבל אם כבר לפי איבר מסויים אז הבנתי שזה הname רק שצריך את המאפיין גם של הID. אז אם ככה uniq[i.name].
וavr416 פעם הבאה חוץ מהבעיה תסביר מה המטרה הרצוייה, והם שייך מקרה של id שונה ושם זהה ומה הדין בכהאי גונא ומדוערחמים כל הכבוד על הפתרון הפשוט!!
דוד, תודה ענקית על ההסברים, אם כי עדיין לא הבנתים לגמרי.אבאר קצת יותר, אני בונה דוח שמאפשר למשתמש להציג את התוצאות של כל המבחנים שהתקיימו בתקופה מסויימת, במקצוע מסויים לבחירתו.
אני מאפשר לו לבחור עבור אילו כיתות או שכבות הוא רוצה להציג את התוצאות, ואני רוצה להציג את המקצוע רק כאשר קיימים עבורו מבחנים באותה תקופה.
לכן, אני בודק בדטה בייס, באילו מקצועות התקיימות מבחנים בתאריכים הללו, לאותם כיתות, ורק אז אני מציג את שם המקצוע בסלקטבוקס.
הענין הוא שכיון שהוא יכול לבחור כמה כיתות, אז הנתונים שאני מקבל מהדטה בייס שונים בהרבה פרטים שלא כ"כ משמעותיים כרגע עבורי, ומה שמעניין אותי זה רק באיזה מקצוע התקיים המבחן, דהיינו הID של המקצוע, וגם השם שלו (כדי להציג למשתמש).
הID והName הם תמיד זהים, ולא משתנים. מה שכן משתנה זה הרבה דברים אחרים, למשל איזה כיתה, איזה מורה וכו'.@דוד ל.ט.
אם אתה משתמש בfindIndex אז אתה יכול לשכוח מהmap.
בשורה שתיים עשית מצויין אבל א. זה מסנן רק לפי הname ב. אתה צריך להוציא את האינדקסים של המקור, והmap מפסיד אותם לגמרי כפי שכתבת.
אם אתה מחפש רק לפי השם או אפי' אם לפי שתי המאפיינים ולא "איך אני משווה את כל המאפיינים" אז נורא פשוט. עם findIndex:מעולה!! באמת לא הבנתי כ"כ איך התכוונת עם הfindIndex הרי לא מעניין אותי כ"כ האינדקס, אלא מעניין אותי שייתן לי את המערך הuniq.
למה הmap הורס את האינדקסים של המערך המקורי? הרי הוא לא משנה את המקור, הוא בסה"כ מחזיר מערך חדש ע"פ התנאי שאני מעביר לו, לא?
אז אם כן, מה הבעיה שתוך כדי הmap הוא גם ירוץ על המקורי ויחזיר לי את האינדקס שלו??תודה רבה רבה!
פורסם במקור בפורום CODE613 ב20/07/2017 20:47 (+03:00)
-
הפתרון שכתבתי לעיל עדיף משמעותית מבחינת מהירות [פי 20] על פני שימוש במערך רגיל.
ראה ניסוי כאן
http://jsben.ch/GYK6Pאין ספק שהפתרון שלך יעיל יותר. במקום לסרוק, דורסים לפי אינדקס שלא מתבצע ע"י סריקה אלא ע"י הצבה לאינדקס של אובייקט (שממומש ע"י מילון של hash table). את זה יודעים בלי שום טסט (שאגב עם המון פגמים).
אבל איך עושים אם רוצים להשוות לפי שתי מאפיינים? אולי אפשר פשוט לשרשר אותם, לדעתי זה גם יהיה מהר יותר כשהמערך גדול.פורסם במקור בפורום CODE613 ב20/07/2017 21:09 (+03:00)
-
יאהוו הארכת. במילים אחרות באובייקטים יחודיים לפי הID. נקודה.
הפתרון של רחמים הוא בהחלט המתאים, אם כי הקוד בשאלה היה יפה ומבריק וטרחתי להעמידו על רגליו.
לא מבין מה לא הבנת כי שמתי שפע דוגמאות קוד, ובנוגע לmap הוא מחזיר לך מערך חדש, אבל איללו חשוב לך האיברים המקוריים אין מי שיעשה לך את ההקשר בין האיבר המקורי לאיבר שנשלף ע"י הmap.
כמובן כל זה לולי שידעתי שהקובע הוא אך ורק הid.פורסם במקור בפורום CODE613 ב20/07/2017 21:13 (+03:00)
-
@דוד ל.ט.
@רחמים
הפתרון שכתבתי לעיל עדיף משמעותית מבחינת מהירות [פי 20] על פני שימוש במערך רגיל.ראה ניסוי כאן
http://jsben.ch/GYK6Pאין ספק שהפתרון שלך יעיל יותר. במקום לסרוק, דורסים לפי אינדקס שלא מתבצע ע"י סריקה אלא ע"י הצבה לאינדקס של אובייקט (שממומש ע"י מילון של hash table). את זה יודעים בלי שום טסט (שאגב עם המון פגמים).
אבל איך עושים אם רוצים להשוות לפי שתי מאפיינים? אולי אפשר פשוט לשרשר אותם, לדעתי זה גם יהיה מהר יותר כשהמערך גדול.זהו שלא היה ברור לי כלל שאובייקט בJS מאחורי הקלעים הוא בעצם טבלת האש ולא סתם מערך, הניסוי הנ"ל נבנה כדי לפשוט ספק זה.
אגב, איזה פגמים מצאת בניסוי?פורסם במקור בפורום CODE613 ב20/07/2017 21:19 (+03:00)
-
לא המון, אלא משהו משמעותי שקפץ לי. הרי אתה רצית להשוות את הסריקה של מערך לבין סריקה של אובייקט, כלומר:
arr.indexOf(x) VS object[x]
אז דבר ראשון בטסט שלך מאוד משמעותי מה קורה אחרי הסריקה. בתצורת המערך אז מכניסים למערך איבר, זה נקודה יקרה מאוד אבל רצית להמחיש את פחיתותו של הפתרון הראשון אבל זה מטשטש את מה שאתה רצית לדעת.
שנית, בעוד שבתצורת המערך העתקת את האובייקטים (כל אחד שמופיע בout מופיע בנפרד גם במקור), אז בתצורת האובייקט השמת ערך בולאני שהוא פרמיטיבי וחוסך מצביע וכתיבת האובייקט כולו.פורסם במקור בפורום CODE613 ב20/07/2017 21:29 (+03:00)
-
דוד הנה תיקון לטסט. וזה לא משנה את התוצאה.
http://jsben.ch/Iqxceפורסם במקור בפורום CODE613 ב21/07/2017 08:49 (+03:00)