אופרטורי טווח ו־flip-flop#

שני אופרטורים עם שני תפקידים שונים לחלוטין בהתאם להקשר: בהקשר רשימה הם מייצרים טווחים, ובהקשר סקלר הם ביטויים בוליאניים ״flip-flop״ עם מצב להתאמת טווחי שורות בעיבוד זרמים.

אופרטור

תפקיד בהקשר רשימה

תפקיד בהקשר סקלר

..

טווח כולל

flip-flop (כניסה בבדיקת־שקר)

...

טווח כולל

flip-flop (כניסה בבדיקת־אמת)

.. בהקשר רשימה - טווחים#

בהקשר רשימה, .. מייצר את הסדרה הכוללת בין נקודות הקצה שלו:

0..9                      # (0,1,2,3,4,5,6,7,8,9)
'a'..'z'                  # ('a','b',...,'y','z')      26 elements
'aa'..'zz'                # ('aa','ab',...,'zy','zz')  676 elements
'A'..'F'                  # ('A','B','C','D','E','F')

for my $i (1..100) { ... }                       # numeric for-loop
for my $name ('alpha'..'omega') { ... }          # alphabetic, magic-incremented

עבור נקודות קצה מספריות התוצאה היא סדרת המספרים השלמים (עם סדר קפדני - 5..1 הוא הרשימה הריקה, לעולם לא הפוך). עבור נקודות קצה מחרוזתיות נעשה שימוש בהגדלה־הקסומה מ־++, שמעניקה לכם את הפריסה האלפביתית הטבעית (ועל 'aa' עד 'zz' את 676 הצירופים בני שתי האותיות הצפויים).

ניב Perl נפוץ:

my @hex = ('0'..'9', 'a'..'f');           # 16 hex digits as strings
my @rev = reverse 1..10;                   # (10,9,8,...,1)
@arr[ 5..9 ]                               # array slice, indexes 5..9
@arr[ -3..-1 ]                             # last three elements

טווח כל־השלמים יכול להיות גדול מאוד - 1..1_000_000_000 הוא רשימה בת מיליארד אלמנטים. Perl 5 מייצר טווחי לולאה בעצלתיים ב־for, כך ש־for my $i (1..1_000_000_000) בטוח, אך my @x = 1..1_000_000_000 מקצה את המערך כולו. PetaPerl פועל לפי אותו כלל.

נקודות קצה הפוכות#

5..1 ריק. אין צורת ״טווח הפוך״; אם אתם רוצים סדר יורד, ייצרו סדר עולה ובצעו reverse:

1..5                    # (1,2,3,4,5)
5..1                    # ()        - empty, no warning
reverse 1..5            # (5,4,3,2,1)

נקודות קצה מטיפוסים מעורבים#

אם אחת מנקודות הקצה נראית מספרית, .. מבצע טווח מספרי:

'1'..'5'                # (1,2,3,4,5)            - numeric
'1'..'10'               # (1,2,3,4,5,6,7,8,9,10) - numeric

'a'..'5'                # ('a')                  - magic-increment from "a",
                        #                         stops because it can't reach "5"

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

0+'1' .. 0+'5'          # numeric, no doubt
sprintf("%d", 1) .. sprintf("%d", 5)

בפועל, כתבו טווחי מספרים שלמים עם ליטרלים שלמים חשופים וטווחי תווים עם אותיות מחרוזת־מצוטטת חשופות; ההבחנה בין קסם־לבין־אריתמטיקה נובעת אוטומטית.

.. ו־... בהקשר סקלר - flip-flops#

בתוך הקשר בוליאני (if, while, ?:, הצד הימני של &&/||///), .. הופך לביטוי בעל מצב שזוכר אם הוא כרגע בין התאמה בצד שמאל לבין התאמה בצד ימין.

while (<$fh>) {
    if (/^BEGIN/ .. /^END/) {     # true from BEGIN line through END line
        print;                     # print everything in between, inclusive
    }
}

הסמנטיקה:

  1. ה־flip-flop הוא בתחילה שקר.

  2. כשתנאי השמאל (/^BEGIN/) הופך לאמת, ה־flip-flop עובר לאמת, והצד הימני מוערך מיד באותה איטרציה.

  3. ה־flip-flop נשאר אמת באיטרציות הבאות עד שתנאי הימין (/^END/) הופך לאמת. באיטרציה ההיא הוא מחזיר אמת ואז עובר חזרה לשקר.

  4. כך ששורת ה־BEGIN ושורת ה־END שתיהן נכללות.

כשה־flip-flop הוא אמת, הוא מחזיר מספר סידורי (1 באיטרציית הכניסה, 2 בבאה, …). באיטרציה הסוגרת הוא מחזיר את הספירה ולאחריה E0 - כך שהערך הוא אמת (מחרוזת לא־ריקה, לא ה־"0" השקרי) אך מומר למחרוזת כמספר 0 לשם בהירות קוסמטית. בדרך כלל תוכלו להתייחס אליו כבוליאני רגיל.

... נבדל מ־.. בפרט אחד:

  • .. (שתי נקודות) - כשהצד השמאלי הופך לאמת, הצד הימני נבדק באותה איטרציה. כך שאותה שורה יכולה לפתוח ולסגור את הטווח.

  • ... (שלוש נקודות) - כשהצד השמאלי הופך לאמת, הצד הימני אינו נבדק עד האיטרציה הבאה. תמיד טווח רב־שורתי.

while (<$fh>) {
    print if /<head>/ .. /<\/head>/;     # one-line <head>...</head> works
    print if /<head>/ ... /<\/head>/;    # forces multi-line; same-line ignored
}

מתי תשתמשו ב־flip-flop#

ה־flip-flop זוהר בסינון טווחי־שורות בסגנון awk - שליפת קטעים מקובץ יומן, בידוד בלוק קוד תחום מ־markdown, עיבוד בלוק תצורה התחום במילות מפתח. הוא ידוע כלא־אהוב על מבקרי קוד מפני שה־statefulness שלו מובלע.

שתי חלופות מעשיות שחלקם מעדיפים:

# Explicit state variable - equivalent semantics, easier to grep for:
my $in_block = 0;
while (<$fh>) {
    $in_block = 1 if /^BEGIN/;
    print if $in_block;
    $in_block = 0 if /^END/;
}

# A simple two-step iterator:
while (<$fh>) {
    if (/^BEGIN/ .. /^END/) { print }
}

ה־flip-flop מנצח בתמציתיות; הצורה המפורשת מנצחת בבהירות במחיר של שלוש שורות נוספות. בחרו לפי הקהל.

ראו גם#

  • reverse, grep, map - אופרטורי רשימה הנכתבים בדרך כלל בצירוף עם טווחים.

  • מציין־מקום - פרוסות מערך @arr[1..3] מסתמכות על טווח־בהקשר־רשימה.

  • קדימות - .. ו־... יושבים בשורה 17, בין ||/// ל־?:. כמעט תמיד הקיפו בסוגריים בעת שילוב עם לוגיקה.