מערכים של מערכים#
מערכי Perl מכילים סקלרים, וסקלרים אינם יכולים להכיל ישירות מערכים אחרים. כדי לבנות מערך דו־ממדי - מטריצה, רשת, טבלה של שורות - אתם מאחסנים הפניות למערכים פנימיים כאלמנטים של המערך החיצוני. הפרק הזה מראה איך.
בניית מטריצה#
הצורה הישירה ביותר משתמשת בבנאי מערך אנונימיים ([...], מכוסים בפירוט ב־הפניות אנונימיות) כך שאינכם צריכים לתת שם לכל מערך פנימי:
my @m = (
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
);
ל־@m יש שלושה אלמנטים. כל אלמנט הוא סקלר המכיל הפניה למערך. ארבעה מערכים מעורבים: ה־@m החיצוני ושלושה מערכים פנימיים אנונימיים.
קריאה וכתיבה של אלמנטים#
$m[1] היא ההפניה לשורה השנייה. כדי להגיע לאלמנט השלישי של אותה שורה, החילו את החץ:
$m[1]->[2] # 6
$m[0]->[0] # 1
$m[2]->[2] = 99; # overwrite the bottom-right cell
בין שני subscripts, החץ הוא אופציונלי. הכלל הזה הוא מה שעושה גישה רב־ממדית קריאה:
$m[1][2] # same as $m[1]->[2]
$m[2][2] = 99; # same as $m[2]->[2] = 99
החץ נשאר נדרש בין משתנה סקלר ל־subscript הראשון שלו: $aref->[0] לא ניתן לקצר ל־$aref[0] כי זה אומר ״האלמנט הראשון של @aref״. הקיצור חל רק בתוך שרשרת של subscripts.
לשלושה ממדים זה אותו רעיון:
$cube[2][3][5] # readable
${${$cube[2]}[3]}[5] # the same expression fully spelled out
הצורה השנייה היא מה שהייתם צריכים לכתוב ללא קיצור החץ. אף אחד לא עושה זאת.
איטרציה על מטריצה#
כדי לעבור על שורות, עברו בלולאה על המערך החיצוני; כל איטרציה נותנת לכם הפניה למערך. כדי לעבור על תאים, בצעו dereference על אותה הפניה בתוך הלולאה:
for my $row (@m) {
for my $cell (@{$row}) {
print "$cell ";
}
print "\n";
}
אם אתם צריכים גם את האינדקסים, השתמשו בלולאת ספירה על המערך החיצוני ועל הפנימי לאחר dereference:
for my $i (0 .. $#m) {
for my $j (0 .. $#{$m[$i]}) {
print "m[$i][$j] = $m[$i][$j]\n";
}
}
$#{$m[$i]} הוא אופרטור האינדקס־האחרון המוחל על המערך הפנימי לאחר dereference. זו צורת $#array כאשר {$m[$i]} עומד במקום שם המערך.
הגדלת שורות ועמודות#
הוסיפו לשורה על־ידי push למערך הפנימי דרך הפניה:
push @{$m[0]}, 10; # row 0 is now (1, 2, 3, 10)
הוסיפו שורה שלמה למטריצה על־ידי push של הפניית מערך אל המערך החיצוני:
push @m, [10, 11, 12]; # @m now has four rows
שתי הפעולות משתמשות ב־push - פעם אחת על מערך פנימי לאחר dereference, פעם אחת על המערך החיצוני ישירות.
autovivification#
השמה ל־$m[$i][$j] עובדת אפילו אם $m[$i] עדיין אינו קיים:
my @grid;
$grid[5][7] = 'X';
זה יוצר @grid עם שישה חריצים ריקים והפניה אחת בחריץ 5. אותה הפניה מצביעה על מערך אנונימי רענן שהחריץ השמיני שלו מכיל 'X'. Perl מייצר אוטומטית גם את החריץ החיצוני וגם את המערך הפנימי. זה נקרא autovivification וזו הסיבה שכמעט אף פעם אינכם צריכים לכתוב $m[$i] = [] לפני השימוש בו.
autovivification מופעל בהשמה ובdereference בהקשר lvalue. קריאות רגילות אינן מבצעות autovivify:
my @grid;
my $x = $grid[5][7]; # $x is undef; no inner array created
exists $grid[5]; # false
השתמשו ב־defined כדי להבחין בין המקרה ״מעולם לא היה קיים״ למקרה ״קיים ומחזיק undef״, וב־exists כדי לבדוק את החריץ עצמו ללא autovivify.
טעות נפוצה: שיתוף מערך פנימי בודד#
זה נראה כמו שלוש שורות אך הוא בפועל שורה אחת חוזרת:
my @inner = (0, 0, 0);
my @bad = (\@inner, \@inner, \@inner);
$bad[0][1] = 9;
print "$bad[2][1]\n"; # 9 - they're all the same array
לקבלת שלוש שורות עצמאיות, בנו כל אחת בנפרד. צורת הבנאי האנונימי מטפלת בכך על־ידי הקצאה רעננה תמיד:
my @good = ([0, 0, 0], [0, 0, 0], [0, 0, 0]);
$good[0][1] = 9;
print "$good[2][1]\n"; # 0
לולאה על map נותנת את אותה התוצאה לאורכי שורה משתנים:
my @grid = map { [ (0) x 10 ] } 1 .. 10; # 10×10 of zeros
טעות נפוצה: שמירת המניין במקום האיברים#
השמת מערך לחריץ סקלרי בודד מעריכה את המערך בהקשר סקלרי, שמניב את מניין האיברים שלו, לא את תוכנו:
for my $i (1 .. 10) {
my @array = somefunc($i);
$AoA[$i] = @array; # WRONG - stores the count
}
אחרי הלולאה הזו כל $AoA[$i] הוא מספר. כדי לשמור את האיברים, עטוף אותם בבנאי מערך אנונימי כך שתקבל הפניה לעותק טרי:
$AoA[$i] = [ @array ]; # right - a new array, copied
אם באמת רצית את המניין, אמור זאת באמצעות scalar ושם שמשקף זאת:
$counts[$i] = scalar @array; # explicit - no surprise later
טעות נפוצה: מערך אחד שנעשה בו שימוש חוזר מאחורי כל הפניה#
מלכודת המערך החוזר מהפרק הקודם מופיעה גם כאשר נעשה שימוש חוזר במערך המשותף על פני איטרציות הלולאה:
our @array;
for my $i (1 .. 10) {
@array = somefunc($i);
$AoA[$i] = \@array; # WRONG - same array every time
}
כל \@array הוא הפניה לאותו מערך, כך שכל עשרת החריצים מצביעים בסופו של דבר על מה ש-somefunc החזיר לאחרונה. שוב, [ @array ] מתקן זאת על ידי העתקה.
הצהרת המערך עם my בתוך הלולאה היא בטוחה, לעומת זאת:
for my $i (1 .. 10) {
my @array = somefunc($i);
$AoA[$i] = \@array; # fine - fresh array each iteration
}
my @array בונה מערך חדש בכל מעבר, כך שכל \@array מתייחס למערך נבדל. ההפניה שורדת מעבר לגוף הלולאה משום שהמערך שעליו היא מצביעה נשאר חי כל עוד ההפניה קיימת. צורת הבנאי [ @array ] עדיין הבחירה הברורה יותר; פנה לצורת \@array רק כשאתה מבין מדוע היא עובדת כאן.
לאן ממשיכים מכאן#
האשים ותערובות - האשים של מערכים ומערכים של האשים, שתי הצורות המכסות את רוב הרשומות בעולם האמיתי.
הפניות אנונימיות - הבנאים
[...]/{...}המשמשים לעיל, בפירוט.