כיצד להשתמש בביטויים רגולריים להחלפת אסימונים במיתרים בג'אווה

1. סקירה כללית

כאשר אנו צריכים למצוא או להחליף ערכים במחרוזת ב- Java, אנו בדרך כלל משתמשים בביטויים רגולריים. אלה מאפשרים לנו לקבוע אם מחרוזת כלשהי או כולה תואמת לתבנית. אנו עשויים בְּקַלוּת החל את אותה החלפה על מספר אסימונים במחרוזת עם החלף הכל שיטה בשניהם שידוך ו חוּט.

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

נבחן גם כמה טריקים לכוונון הביטויים הרגולריים שלנו כדי לזהות אסימונים בצורה נכונה.

2. עיבוד התאמות באופן אישי

לפני שנוכל לבנות את אלגוריתם החלפת האסימונים שלנו, עלינו להבין את ה- API של Java סביב ביטויים רגולריים. בואו נפתור בעיית התאמה מסובכת באמצעות קבוצות לכידת ולא לכידה.

2.1. דוגמה למקרה כותרת

בואו נדמיין שאנחנו רוצים לבנות אלגוריתם שיעבד את כל מילות הכותרת במחרוזת. מילים אלה מתחילות בתו אחד באותיות רישיות ואז מסתיימות או ממשיכות בתווים קטנים בלבד.

הקלט שלנו יכול להיות:

"שלוש מילות הון ראשונות! ואז 10 TLAs, מצאתי"

מההגדרה של מילת כותרת, זה מכיל את ההתאמות:

  • ראשון
  • עיר בירה
  • מילים
  • אני
  • מצאתי

וביטוי קבוע לזהות דפוס זה יהיה:

"(? <= ^ | [^ A-Za-z]) ([A-Z] [a-z] *) (? = [^ A-Za-z] | $)"

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

[A-Z]

יזהה אות גדולה.

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

[a-z] *

מזהה אפס או יותר אותיות קטנות.

במקרים מסוימים, שתי מחלקות התווים הנ"ל יספיקו כדי לזהות את האסימונים שלנו. למרבה הצער, בטקסט לדוגמא שלנו, יש מילה שמתחילה במספר אותיות גדולות. לָכֵן, עלינו להביע כי האות הגדולה היחידה שנמצא חייבת להיות הראשונה שתופיע לאחר אותיות.

באופן דומה, ככל שאנו מאפשרים מילה אחת באות גדולה, עלינו להביע כי האות הגדולה היחידה שנמצא אינה יכולה להיות הראשונה במילה באות גדולה.

הביטוי [^ A-Za-z] פירושו "ללא אותיות". שמנו אחד כזה בתחילת הביטוי בקבוצה שאינה תופסת:

(? <= ^ | [^ A-Za-z])

הקבוצה הלא לוכדת, החל מ (?<=, עושה א הסתכל מאחור כדי להבטיח שההתאמה תופיע בגבול הנכון. מקבילו בסוף עושה את אותה עבודה עבור הדמויות הבאות.

עם זאת, אם מילים נוגעות בתחילת המחרוזת או בסוף שלה, עלינו להתחשב בכך, שם הוספנו ^ | לקבוצה הראשונה כדי שזה אומר "התחלת המחרוזת או כל תו שאינו אותיות", והוספנו | $ בסוף הקבוצה האחרונה שאינה תופסת כדי לאפשר לסוף המחרוזת להיות גבול .

דמויות שנמצאו בקבוצות שאינן לוכדות אינן מופיעות במשחק כשאנחנו משתמשים למצוא.

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

2.2. בודקים את הדוגמה שלנו

עם הטקסט לדוגמא שלנו בקבוע שנקרא EXAMPLE_INPUT והביטוי הקבוע שלנו בא תבנית שקוראים לו TITLE_CASE_PATTERNבואו נשתמש למצוא על שידוך בכיתה כדי לחלץ את כל המשחקים שלנו במבחן יחידה:

התאמת התאמה = TITLE_CASE_PATTERN.matcher (EXAMPLE_INPUT); התאמות רשימה = ArrayList חדש (); בעוד (matcher.find ()) {matches.add (matcher.group (1)); } assertThat (תואם). contains בדיוק ("First", "Capital", "Words", "I", "Found");

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

בכל פעם למצוא החזרות נָכוֹן, ה שידוך מצב האובייקט מוגדר לייצג את ההתאמה הנוכחית. אנחנו יכולים לבדוק את כל המשחק קבוצה (0)או לבדוק קבוצות לוכדות מסוימות באמצעות האינדקס מבוסס 1 שלהן. במקרה זה, יש קבוצה לוכדת סביב היצירה שאנחנו רוצים, ולכן אנו משתמשים קבוצה 1) כדי להוסיף את ההתאמה לרשימה שלנו.

2.3. בודק שידוך קצת יותר

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

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

בעוד (matcher.find ()) {System.out.println ("התאמה:" + matcher.group (0)); System.out.println ("התחל:" + matcher.start ()); System.out.println ("סוף:" + matcher.end ()); }

קוד זה יראה לנו היכן נמצא כל משחק. זה מראה לנו גם את קבוצה (0) התאמה, שהיא כל מה שנתפס:

משחק: התחלה ראשונה: 0 סיום: 5 משחק: התחלה בירה: 8 סיום: 15 התאמה: מילים התחלה: 16 סיום: 21 משחק: אני מתחיל: 37 סיום: 38 ... עוד

כאן אנו יכולים לראות שכל התאמה מכילה רק את המילים להן אנו מצפים. ה הַתחָלָה המאפיין מציג את האינדקס מבוסס האפס של ההתאמה בתוך המחרוזת. ה סוֹף מראה את אינדקס הדמות רק אחרי. זה אומר שנוכל להשתמש substring (התחלה, סוף התחלה) כדי לחלץ כל התאמה מהמחרוזת המקורית. זה בעצם איך ה קְבוּצָה השיטה עושה זאת עבורנו.

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

3. החלפת גפרורים אחד אחד

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

"3 מילות ראשוניות ראשונות! ואז 10 TLAs, מצאתי"

ה תבנית ו שידוך מחלקה לא יכולה לעשות זאת עבורנו, ולכן עלינו לבנות אלגוריתם.

3.1. האלגוריתם החלופי

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

  • התחל עם מחרוזת פלט ריקה
  • לכל משחק:
    • הוסף לפלט כל מה שהגיע לפני המשחק ואחרי כל משחק קודם
    • עיבוד ההתאמה הזו והוסף אותה לפלט
    • המשך עד לעיבוד כל ההתאמות
    • הוסף כל מה שנשאר לאחר ההתאמה האחרונה לפלט

עלינו לציין שמטרת האלגוריתם הזה היא מצא את כל האזורים שאינם תואמים והוסף אותם לפלטוכן הוספת ההתאמות המעובדות.

3.2. ה- Token Replacer בג'אווה

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

המרת מחרוזת סטטית פרטית (אסימון מחרוזת) {return token.toLowerCase (); }

כעת נוכל לכתוב את האלגוריתם שיחזור על ההתאמות. זה יכול להשתמש ב- StringBuilder לפלט:

int lastIndex = 0; פלט StringBuilder = StringBuilder חדש (); התאמת התאמה = TITLE_CASE_PATTERN.matcher (מקורי); בעוד (matcher.find ()) {output.append (original, lastIndex, matcher.start ()) .append (convert (matcher.group (1))); lastIndex = matcher.end (); } אם (lastIndex <original.length ()) {output.append (original, lastIndex, original.length ()); } להחזיר output.toString ();

עלינו לציין זאת StringBuilder מספק גרסה שימושית של לְצַרֵף שיכולים לחלץ משטחים. זה עובד טוב עם סוֹף רכוש של שידוך לתת לנו לאסוף את כל הדמויות שאינן מתאימות מאז המשחק האחרון.

4. הכללת האלגוריתם

כעת, לאחר שפתרנו את הבעיה של החלפת כמה אסימונים ספציפיים, מדוע שלא נמיר את הקוד לטופס שבו הוא יכול לשמש למקרה הכללי? הדבר היחיד שמשתנה בין יישום אחד למשנהו הוא הביטוי הרגיל לשימוש, וההיגיון להמרת כל התאמה להחלפה שלה.

4.1. השתמש בקלט פונקציה ותבנית

אנחנו יכולים להשתמש בג'אווה פוּנקצִיָה התנגד לאפשר למתקשר לספק את ההיגיון לעיבוד כל התאמה. ונוכל לקחת קלט שנקרא tokenPattern למצוא את כל האסימונים:

// זהה לקודם בזמן (matcher.find ()) {output.append (original, lastIndex, matcher.start ()) .append (converter.apply (matcher)); // כמו מקודם

כאן, הביטוי הרגולרי כבר לא מקודד. במקום זאת, מֵמִיר הפונקציה ניתנת על ידי המתקשר והיא מוחלת על כל התאמה בתוך ה- למצוא לוּלָאָה.

4.2. בדיקת הגרסה הכללית

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

assertThat (החלף טוקנים ("3 מילות ראשונות ראשונות! ואז 10 TLAs, מצאתי", TITLE_CASE_PATTERN, התאמה -> match.group (1). toLowerCase ())). isEqualTo ("3 מילות הון ראשונות! ואז 10 TLAs, מצאתי ");

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

עכשיו יש לנו מחליף אסימונים, אז בואו ננסה מקרי שימוש אחרים.

5. מקרי שימוש מסוימים

5.1. בריחה מדמויות מיוחדות

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

אם אנו יכולים לבטא את התבנית שמשמעותה "דמות ביטוי רגולרי", קל להשתמש באלגוריתם שלנו כדי לברוח מכולם:

תבנית regexCharacters = Pattern.compile ("[]"); assertThat (להחליף טוקנים ("תו regex כמו [", regexCharacters, match -> "\" + match.group ())) .isEqualTo ("דמות regex כמו \ [");

עבור כל משחק אנו מקדימים את ה- \ אופי. כפי ש \ הוא תו מיוחד במיתרי ג'אווה, הוא נמלט עם אחר \.

ואכן, דוגמה זו מכוסה בתוספת \ תווים כמעמד הדמויות בתבנית עבור regex דמויות צריך לצטט רבים מהדמויות המיוחדות. זה מראה את מנתח הביטוי הרגולרי שאנו משתמשים בהם בכדי להתכוון למילוליהם, ולא כתחביר של ביטוי רגיל.

5.2. החלפת מצייני מקום

דרך נפוצה להביע מציין מיקום היא להשתמש בתחביר כמו $ {name}. בואו ניקח בחשבון מקרה שימוש שבו התבנית "שלום $ {name} ב- $ {company}" צריך לאכלס ממפה שנקראת ערכי מקום:

מפה placeholderValues ​​= HashMap חדש (); placeholderValues.put ("שם", "ביל"); placeholderValues.put ("חברה", "באלדונג");

כל מה שאנחנו צריכים זה ביטוי קבוע טוב למצוא את ${…} אסימונים:

"\ $ \ {(? [A-Za-z0-9 -_] +)}"

היא אפשרות אחת. עליו לצטט את $ ואת הסד המתולתל הראשוני כפי שאחרת היו מתייחסים אליו כאל תחביר של ביטוי רגיל.

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

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

assertThat (החלף טוקנים ("שלום $ {name} ב- $ {company}", "\ $ \ {(? [A-Za-z0-9 -_] +)}", התאמה -> placeholderValues.get (match .group ("מציין מיקום")))) .isEqualTo ("היי ביל בבלדונג");

כאן אנו יכולים לראות כי הוצאת הערך של הקבוצה בשם שידוך רק כרוך בשימוש קְבוּצָה עם השם כקלט ולא את המספר.

6. מסקנה

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

ואז יצרנו והכללנו אלגוריתם כדי לאפשר לנו החלפת אסימונים אחר אסימונים.

לבסוף, בחנו כמה מקרי שימוש נפוצים לבריחה מדמויות ואכלוס תבניות.

כמו תמיד, ניתן למצוא את דוגמאות הקוד ב- GitHub.