בפוסט הקודם ראינו כל מיני דרכים להתמודד עם מספרים אקראיים.
הפעם ניקח את זה צעד קדימה ונשאל את עצמנו איך אנחנו רוצים את האקראיות שלנו?
בואו נתחיל בדוגמה פשוטה. נגיד לרגע שאנחנו רוצים להגריל אחת משתי אפשרויות, כאשר לאף אחת מהן אין משמעות מיוחדת, נניח "עץ" או "פלי". הדבר דומה לשק שיש בו שני פתקים עם ערכים שונים. בכל פעם נוציא פתק באקראי ונחזיר לשק.
const coin = ['heads', 'tails']
console.log(coin[Math.floor(Math.random() * coin.length)])
דוגמה אחרת היא הגרלה עם זוכה. במצב זה יהיה לנו שק עם מספר פתקים ובכל אחד מהם יהיה ערך כלשהו. נוציא את הפתקים אחד אחרי השני ובסופו של דבר יהיה פתק אחד שבו הזוכה. הפעם לא נחזיר את הפתקים לשק, אחרת בתיאוריה ניתן יהיה לשחק במשחק זה בלי סוף ללא זוכה! משחק המבוסס על עיקרון זה הוא בינגו. המספרים יוצאים בסדר אקראי, אבל שום מספר לא יחזור על עצמו פעמיים.
איך ניישם את העיקרון הזה? אפשרות אחת היא לייצר מערך שייצג את השק שלנו. בכל פעם נמשוך ערך תוך כדי מחיקתו מהשק.
const arr = [1,2,3,4,5]
while (arr.length) {
const randomPosition = Math.floor(Math.random() * arr.length)
console.log(arr.splice(randomPosition, 1))
}
(שאלה למחשבה, למה בחרתי ב-while ולא ב-for loop?)
הבעיה היא שלא תמיד אנחנו רוצים שהמערך באמת יתרוקן. מה אם נרצה למחזר אותו?
יש כמובן יותר מדרך אחת. אפשר לייצר מערך שני ולהעביר אליו את הערכים בסדר אקראי, ואז אפשר יהיה להשתמש בו. אבל אנחנו נערבב את המערך שלנו במקום זאת.
אבל איך?
יש כל מיני דרכים. אפשר לעבור על המערך הרבה פעמים ולהחליף באקראי מיקומים של זוגות צמודים, אבל אני חושש ששיטת הערבוב הזאת עדיין עלולה להידמות לרשימה המקורית, אלא אם נערבב הרבה מאד פעמים.
נשתמש בטכניקה פשוטה מאד:
- נרוץ כמה פעמים על המערך
- נתלוש איבר אחד מהמערך במיקום אקרא
- נחזיר את האיבר אל המערך בסוף
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
for (let i = 0; i < 10; i++) {
const randomElement = arr.splice(Math.floor(Math.random() * arr.length), 1)
arr = [...arr, ...randomElement]
}
console.log(arr)
כמובן שככל שהלולאה תהיה ארוכה יותר, כך גם הערבוב יהיה יותר גדול.
וכמובן שמתבקש שנהפוך את העסק לפונקציית-עזר.
function scramble(arr, numIterations) {
for (let i = 0; i < numIterations; i++) {
const randomElement = arr.splice(Math.floor(Math.random() * arr.length), 1)
arr = [...arr, ...randomElement]
}
return arr;
}
let arr = [1,2,3,4,5, 6, 7, 8, 9, 0]
console.log(scramble(arr, 10))
בואו נתרגל עם צבעים
אני יודע שיש אנשים שמתרגשים עד עמקי נשמתם לראות מספרים אקראיים.
אני לא. אני מוזר. אני מעדיף לראות משהו צבעונים בעיניים. אז בואו נעשה תרגיל.
בואו נצייר רצף שטוח של אריחים, שכל אחד מהם בצבע אקראי, אבל אחר מקודמו. באופן זה לא יהיו שני אריחים באותו הצבע. תחשבו שניה על אסטרטגיה לתקוף את הבעיה.
בפתרון שלי אני משתמש בטבלה, כי זה מעצבן את המתכנתים. אף אחד לא אוהב טבלאות. אז נה נה נה נה. הטבלה שלי מכילה רק שורה אחת. יש לי רשימה של צבעים. עבור כל תא אני תולש באקראי צבע מהרשימה ומשאיר אותו בחוץ עד לבחירה הבאה, כדי שהבחירה הבאה לא תכיל את הצבע הזה בתור אפשרות.
תובנה: אפשר היה לפתור את זה בלי משתנה נפרד בכלל! איך?
ועכשיו בואו ניקח את זה מימד אחד קדימה. השטיח שלנו עכשיו "אמיתי" בדו-מימד. אסור לצבע לחזור על עצמו אחד אחרי השני. נשמע דומה, אבל הפעם אנחנו צריכים לוודא שאף משבצת לא בצבע זהה למשבצת צמודה אליה (אלכסון זה בסדר).
הנטייה הראשונה תהיה לבדוק את (מקסימום) 4 המשבצות שמקיפות את המשבצת עליה עובדים כרגע. אבל האמנם?
שימו לב לפתרון שלי. אני משתמש במערך מתווך carpet כדי להימנע מבדיקת הצבע דרך ה-classList של כל תא. זה הרבה יותר אמין, כי בתיאורה יש יותר מ-class אחד לתא בטבלה.
מיותר כמעט לציין שהפתרון הזה לא מספיק גנרי. גודל הטבלה, רשימת הצבעים וכל הבדיקות שלי מבוססים על ערכים סטטיים. קחו לכם אתגר נחמד והיפכו את כל העסק הזה לדינמי לגמרי. ציירו את הטבלה בעזרת הקוד וטפלו בה לפי מידות שתקבלו בתור פרמטרים.
לסיכום
עברנו על אפשרויות שונות לייצר רצפים אקראיים ולשלוף מתוכם עם ובלי החזרות.
בפוסט הבא נעלה את רמת המורכבות ונדון בנושאים הקשורים בקריפטוגרפיה.