האשים#
האש הוא אוסף לא־מסודר של זוגות מפתח-ערך שבהם המפתחות הם מחרוזות והערכים הם סקלרים. ה־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 וארבע צורות הפרוסה.
sort- עבור איטרציה מסודרת.tie- הפכו האש למגובה במשהו אחר מאשר טבלת ההאש שבזיכרון (קובץ DBM, מסד נתונים עצל, …).