האשים ומבנים מעורבים#

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

הפרק הזה עובר על שלוש הצורות הנפוצות ביותר. התבניות מתחברות: ברגע שיש לכם האש של מערכים ומערך של האשים, יש לכם ״שורות של רשומות עם שדות רבי־ערכים״ בחינם.

האש של מערכים - קיבוץ ערכים תחת מפתח#

הדוגמה המניעה: אתם קוראים שורות מהצורה city, country ורוצים לאסוף, עבור כל מדינה, את רשימת עריה.

Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA

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

my %cities_by_country;
while (<>) {
    chomp;
    my ($city, $country) = split /, /;
    push @{$cities_by_country{$country}}, $city;
}

for my $country (sort keys %cities_by_country) {
    my @cities = sort @{$cities_by_country{$country}};
    print "$country: ", join(', ', @cities), ".\n";
}

שני דברים שכדאי לשים לב אליהם:

  • push @{$cities_by_country{$country}}, $city עובד כבר באיטרציה הראשונה עבור כל מדינה חדשה. רשומת ה-hash עדיין אינה קיימת, ולכן פרל יוצר עבורה מערך אנונימי, שומר הפניה, ודוחף לתוכה את העיר. זוהי שוב אוטוויויפיקציה

    • ומדוע כמעט אף פעם אינך זקוק ל-$h{$k} = [] unless exists $h{$k} כשורה נפרדת.

  • ה־dereference @{$cities_by_country{$country}} הוא מכאני: ״המערך שעליו ערך ההאש הזה מצביע.״ בכל מקום שבו הייתם כותבים @cities אם הוא היה מערך בעל שם, אתם כותבים את הצורה המסולסלת במקום.

הפלט הוא:

Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.

מערך של האשים - טבלה של רשומות#

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

my @people = (
    { name => 'Ada',   born => 1815, field => 'computing' },
    { name => 'Hedy',  born => 1914, field => 'signals'   },
    { name => 'Grace', born => 1906, field => 'compilers' },
);

$people[0] היא הפניית האש; $people[0]->{name} או (עם קיצור החץ־בין־subscripts) $people[0]{name} הוא 'Ada'.

פעולות טיפוסיות:

# Sort by a field
my @by_year = sort { $a->{born} <=> $b->{born} } @people;

# Select rows
my @early = grep { $_->{born} < 1900 } @people;

# Project one field out
my @names = map { $_->{name} } @people;

# Mutate a record in place
$people[1]{field} = 'radio';

בתוך sort, grep ו־map הרשומה היא הפניית האש, אז אתם מגיעים לשדותיה דרך החץ. $a ו־$b בתוך משווה sort הם הפניות־האש כאן, לא האשים.

האש של האשים - טבלאות חיפוש המפתחות לפי ערכים מובנים#

כאשר כל רשומה מפותחת לפי מזהה ייחודי, האש של האשים בדרך כלל נחמד יותר ממערך של האשים:

my %people = (
    ada   => { born => 1815, field => 'computing' },
    hedy  => { born => 1914, field => 'signals'   },
    grace => { born => 1906, field => 'compilers' },
);

print $people{ada}{born};             # 1815
$people{grace}{field} = 'COBOL';      # mutate one record

בצעו איטרציה בסדר מוגדר על־ידי מיון המפתחות:

for my $key (sort keys %people) {
    my $rec = $people{$key};
    print "$key: born $rec->{born}, $rec->{field}\n";
}

קישור ההפניה הפנימית ללקסיקלי בעל שם ($rec) הוא הרגל ששווה לפתח - הוא הופך את הקוד לקריא מלמעלה למטה במקום לחזור על $people{$key}{field} חמש פעמים.

מעורב - רשומות עם שדות רבי־ערכים#

שימו את הכל יחד: כל רשומה היא האש, ואחד משדותיה הוא הפניה למערך.

my %library = (
    'The Road'           => {
        author  => 'Cormac McCarthy',
        year    => 2006,
        genres  => ['fiction', 'post-apocalyptic'],
    },
    'Thinking in Systems' => {
        author  => 'Donella Meadows',
        year    => 2008,
        genres  => ['non-fiction', 'systems'],
    },
);

for my $title (sort keys %library) {
    my $rec = $library{$title};
    my $genres = join ', ', @{$rec->{genres}};
    print "$title ($rec->{year}, $rec->{author}): $genres\n";
}

@{$rec->{genres}} היא אותה תבנית כמו קודם: הגיעו לתוך ההאש, קבלו את הפניית המערך, בצעו dereference עליה עם @{...}.

שדה יכול באותה קלות להחזיק הפניה לקוד או hash חיפוש מקונן. דבר באופן הגישה אינו משתנה; אתה עוקב אחר אותו החץ אל תוך מה שכל שדה מצביע עליו:

my $rec = {
    name     => 'lookup',
    CALLBACK => sub { uc $_[0] },
    LOOKUP   => { red => '#f00', green => '#0f0' },
};

$rec->{CALLBACK}->('hi');     # call the code ref - 'HI'
$rec->{LOOKUP}{red};          # nested hash lookup - '#f00'

$rec->{CALLBACK} הוא הפניה לקוד, ולכן ה-->($arg) שבסוף קורא לו. $rec->{LOOKUP} הוא הפניה ל-hash, ולכן {red} מאנדקס דרכו (החץ שבין שני האינדקסים מושמט).

העתקת מערך של הפניות מעתיקה את המערך, לא את המוצבעים#

כאשר רשומה מחזיקה מערך של הפניות, העתקת המערך הזה עם [ @list ] יוצרת מערך חדש שאיבריו הם אותן ההפניות. העותק והמקור חולקים את המוצבעים שלהם, כך ששינוי דרך נתיב אחד נראה דרך כל נתיב שמחזיק את אותן הפניות איברים:

my %TV = (
    simpsons => {
        kids => [ { name => 'Bart', age => 10 },
                  { name => 'Lisa', age => 8 } ],
    },
);

my @also = @{ $TV{simpsons}{kids} };   # array copied; hashes shared

$TV{simpsons}{kids}[0]{age}++;         # mutate through one path
print $also[0]{age};                   # 11 - the other path sees it too

@also הוא מערך נפרד, ולכן דחיפה אליו אינה מגדילה את המקור. אך איבריו הם בדיוק אותן הפניות ל-hash, כך ששינוי של age דרך $TV{simpsons}{kids}[0] משנה את ה-hash שגם $also[0] מצביע עליו. כדי לקבל רשומות עצמאיות עליך להעתיק רמה אחת עמוק יותר, ולבנות hashים טריים במקום לעשות שימוש חוזר בהפניות שלהם.

בדיקה של מה שיש לכם#

לפני שמבצעים פעולה על חריץ מקונן, לעיתים שימושי לדעת אם החריץ באמת שם. השתמשו ב־exists ולא ב־defined כאשר אתם רוצים להימנע מ־autovivify של מבני ביניים ריקים:

if (exists $library{$title} and exists $library{$title}{genres}) {
    push @{$library{$title}{genres}}, 'essential';
}

קריאת $library{$title}{genres} בהקשר lvalue - צד שמאל של השמה, או כארגומנט של push - מבצעת autovivify על רמות חסרות. קריאתה בהקשר rvalue אינה. בדיקות ה־exists למעלה שומרות שהסריקה תהיה לקריאה בלבד.

לבדיקת סוג הפניה מבלי למשוך ממנה ערכים, השתמשו ב־ref:

ref $library{'The Road'}              # HASH
ref $library{'The Road'}{genres}      # ARRAY

לאן ממשיכים מכאן#

  • הפניות אנונימיות - הבנאים [...] / {...} המשמשים לאורך הפרק הזה, ומתי משתנה בעל שם הוא הבחירה הטובה יותר.

  • הפניות לתת־שגרה - טעם ההפניה הנותר, הנחוץ לטבלאות callbacks ו־dispatch.