האשים#

האש הוא אוסף לא־מסודר של זוגות מפתח-ערך שבהם המפתחות הם מחרוזות והערכים הם סקלרים. ה־sigil הוא % עבור ההאש כולו, @ עבור פרוסות ערך ופרוסות מפתח/ערך, ו־$ עבור ערך אחד:

my %user = (
    name  => 'John',
    age   => 30,
    email => 'john@example.com',
);

%user                # the whole hash (six elements: key1, val1, ...)
$user{name}          # one value          - 'John'
@user{qw(name age)}  # value slice        - ('John', 30)
%user{qw(name age)}  # key/value slice    - (name => 'John', age => 30)
keys %user           # the list of keys

מפתחות האש תמיד מומרים בכפייה למחרוזות. אחסון תחת מפתח המספר השלם 42 וקריאה תחת מפתח המחרוזת "42" מאחזרים את אותו חריץ - הם אותו מפתח.

אתחול: זוגות, פסיק שמן, ו־%h = LIST#

האש מאותחל מרשימה באורך זוגי, בסדר מפתח/ערך:

my %h = ('a', 1, 'b', 2);                        # legal but ugly
my %h = (a => 1, b => 2);                        # idiomatic

הפסיק השמן => הוא פסיק שגם מצטט אוטומטית bareword בצד שמאל הנראה כמזהה. כך ש־a => 1 הוא בדיוק 'a', 1. ציטוט אוטומטי דורש שה־bareword יהיה מזהה פשוט - 2.0 => 'x' מפורסר כמספר 2, לא כמחרוזת "2.0":

my %h = (a => 1);                # ('a', 1)         - auto-quoted
my %h = ('a' => 1);              # ('a', 1)         - same
my %h = (2.0 => 'x');            # (2, 'x')         - not ('2.0', 'x')!
my %h = ("2.0" => 'x');          # ('2.0', 'x')     - explicit quote

אם מפתח מופיע יותר מפעם אחת ברשימת המאתחל, המופע האחרון מנצח. הניב התקני עבור ”merge with overrides“ מנצל זאת:

my %config = (%defaults, %overrides);
# %config has every default key, with %overrides values where they collide

גישה, עם ארבע צורות ה־sigil#

קריאה וכתיבה של חריץ אחד הן ב־sigil $:

my $name = $user{name};               # read
$user{city} = 'NYC';                  # write
$user{age}++;                         # arithmetic on a hash value
delete $user{email};                  # remove the slot entirely

שלוש צורות המנוי האחרות תואמות למה שאתם רוצים בחזרה:

@user{qw(name age city)}              # ('John', 31, 'NYC')           - values
%user{qw(name age)}                    # (name => 'John', age => 31)   - pairs
keys %user                             # ('name', 'age', 'city')        - keys
values %user                           # ('John', 31, 'NYC')            - values

ראו מנוי עבור מטריצת שתים־עשרה־הדרכים כולה וכלל הציטוט־האוטומטי־של־bareword בתוך {}.

exists מול defined מול אמיתיוּת#

שלוש שאלות נבדלות לגבי מפתח:

exists  $h{key}      # is the slot present at all?
defined $h{key}      # is the slot present AND non-undef?
        $h{key}      # is the value true (non-empty, non-zero, non-"0")?

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

$h{a} = undef;
$h{b} = 0;
delete $h{c};

exists  $h{a}        # TRUE  - slot exists, value is undef
defined $h{a}        # FALSE - value is undef
        $h{a}        # FALSE - undef is false

exists  $h{b}        # TRUE
defined $h{b}        # TRUE
        $h{b}        # FALSE - 0 is false

exists  $h{c}        # FALSE - never set, or deleted
defined $h{c}        # FALSE
        $h{c}        # FALSE

בחרו לפי כוונה: exists עבור נוכחות, defined עבור ערך-שאינו-undef, אמיתיוּת עבור ערך משמעותי. הושטת יד ל־if ($h{key}) כאשר התכוונתם ל־exists היא מקור באגים - 0 ו־"" הם ערכים תקפים שהבדיקה דוחה.

איטרציה: keys, values, each, while (each)#

for my $k (keys %user) {
    print "$k = $user{$k}\n";
}

for my $v (values %user) { ... }            # values only

while (my ($k, $v) = each %user) {          # keys + values, one pair per call
    print "$k = $v\n";
}

each נושא מצב איטרטור על ההאש עצמו; קריאה ל־keys (או ל־values) על ההאש מאפסת את האיטרטור הזה. ערבוב השניים מייצר באגי לולאה שקשה לנפות:

while (my ($k, $v) = each %h) {
    if (some_condition($k)) {
        print "size: ", scalar keys %h, "\n";   # resets each() iterator!
        # next iteration of while() starts over from the top
    }
}

תיקון: או צברו את הרשימה עם keys פעם אחת בראש, או הימנעו מ־each לחלוטין. את רוב איטרציות ההאש מוטב לכתוב עם keys:

for my $k (sort keys %h) {            # bonus: defined ordering
    ...
}

סדר מפתחות#

מפתחות האש יוצאים בסדר insertion-order-perturbed-by-hash-randomisation - כלומר אין סדר מובטח. שתי ריצות של אותה תוכנית עם אותו קלט עשויות לעבור על המפתחות בסדרים שונים. זוהי תכונת אבטחה (היא מונעת התקפות מורכבות־אלגוריתמית כנגד פונקציית ההאש); היא גם מקור חוזר לבדיקות שבירות:

my %h = (a => 1, b => 2, c => 3);
print "$_ " for keys %h;        # output order is undefined

אם אתם זקוקים לסדר יציב, מיינו:

print "$_ " for sort keys %h;          # alphabetical
print "$_ " for sort { $h{$a} <=> $h{$b} } keys %h;   # by value

הפניות האש#

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

my %h = (a => 1, b => 2);

my $href = \%h;                # reference to the existing %h
my $anon = { x => 1, y => 2 }; # anonymous hash reference

$href->{a}                     # access through the arrow      - 1
${$href}{a}                    # fully bracketed deref         - 1
keys %$href                    # deref then keys
%{$href}                       # deref to flat key/value list

ראו הפניות לתמונה המלאה.

דוגמה אמיתית: ספירה וקיבוץ#

ספירת תדירות היא הדוגמה הקטנה מספר הלימוד עבור האשים:

my @words = qw(apple banana apple cherry apple banana);
my %count;
$count{$_}++ for @words;
# %count = (apple => 3, banana => 2, cherry => 1)

קיבוץ הוא הצעד הבא מעלה - בניית האש שערכיו הם arrayrefs:

my @people = (
    { name => 'Alice',   dept => 'eng' },
    { name => 'Bob',     dept => 'sales' },
    { name => 'Carol',   dept => 'eng' },
    { name => 'Dan',     dept => 'sales' },
);

my %by_dept;
push @{ $by_dept{$_->{dept}} }, $_->{name} for @people;
# %by_dept = (eng => ['Alice', 'Carol'], sales => ['Bob', 'Dan'])

השורה push @{ $by_dept{$_->{dept}} }, ... עושה autovivify: כאשר המפתח eng עדיין אינו שם, חריץ ההאש נוצר ומערך אנונימי נכנס אליו, ואז הערך נדחף. ראו הפניות לסיפור ה־autoviv.

פרוסת האש כ־get/set רב־ערכי#

my %config = (host => 'localhost', port => 80, debug => 0);

my ($h, $p) = @config{'host', 'port'};       # ('localhost', 80)

@config{qw(host port debug)} = ('elsewhere', 8080, 1);   # set three at once

שלבו עם פרוסת ה־%h{...} עבור ”extract a record“:

my %fields = %config{qw(host port)};
# %fields = (host => 'localhost', port => 80)

זוהי הדרך הקנונית להטיל האש למטה לתת־קבוצה של מפתחותיו. בלי %h{...} הייתם מגלגלים זאת ידנית עם לולאה או עם map.

ראו גם#

  • מערכים - כאשר האינדקס הוא מספר שלם.

  • הפניות - autovivification, hash-of-arrays, hash-of-hashes; הצורה $h->{a}{b}.

  • אופרטורי מנוי ופרוסה - כלל הציטוט־האוטומטי־של־bareword וארבע צורות הפרוסה.

  • exists, defined, delete - שלישיית הנוכחות/ערך/הסרה.

  • keys, values, each - איטרציית ההאש־כולו.

  • sort - עבור איטרציה מסודרת.

  • tie - הפכו האש למגובה במשהו אחר מאשר טבלת ההאש שבזיכרון (קובץ DBM, מסד נתונים עצל, …).