Java: ספירת מספר התרחשות מילים במחרוזת

צומת המקור: 1719850

מבוא

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

במדריך זה תלמדו כיצד לספור את מספר מופעי המילים במחרוזת ב-Java:

String searchText = "Your body may be chrome, but the heart never changes. It wants what it wants.";
String targetWord = "wants";

נחפש את מספר המופעים של ה- targetWordבאמצעות String.split(), Collections.frequency() וביטויים רגולריים.

ספירת מילים במחרוזת עם String.split ()

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

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

String[] words = searchText.replaceAll("p{Punct}", "").split(" ");

int wordCount = 0;
for (int i=0; i < words.length; i++)
    if (words[i].equals(targetWord))
        wordCount++;
System.out.println(wordCount);

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

2

ספירת מילים במחרוזת עם Collections.frequency()

השמיים Collections.frequency() השיטה מספקת יישום הרבה יותר נקי, ברמה גבוהה יותר, אשר מרחיק פשטות for לולאה, ובודק את הזהות של שתיהן (אם אובייקט is אובייקט אחר) ושוויון (האם אובייקט שווה לאובייקט אחר, תלוי בתכונות האיכותיות של אותו אובייקט).

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


searchText = searchText.replaceAll("p{Punct}", "");

int wordCount = Collections.frequency(Arrays.asList(searchText.split(" ")), targetWord);
System.out.println(wordCount);

כאן, המרנו את המערך שהתקבל ממנו split() לתוך ג'אווה ArrayList, באמצעות העוזר asList() השיטה של Arrays מעמד. פעולת ההפחתה frequency() מחזירה מספר שלם המציין את התדירות של targetWord ברשימה, והתוצאות ב:

2

התרחשויות מילים במחרוזת עם התאמה (ביטויים רגולריים - RegEx)

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

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

Pattern pattern = Pattern.compile("b%s(?!w)".format(targetWord));

Pattern pattern = Pattern.compile("bwants(?!w)");
Matcher matcher = pattern.matcher(searchText);

int wordCount = 0;
while (matcher.find())
    wordCount++;

System.out.println(wordCount);

זה גם מביא ל:

2

Benchmark של יעילות

אז מה הכי יעיל? בואו נריץ מדד קטן:

int runs = 100000;

long start1 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithSplit(searchText, targetWord);
}

long end1 = System.currentTimeMillis();
System.out.println(String.format("Array split approach took: %s miliseconds", end1-start1));

long start2 = System.currentTimeMillis();
  for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithCollections(searchText, targetWord);
}

long end2 = System.currentTimeMillis();
System.out.println(String.format("Collections.frequency() approach took: %s miliseconds", end2-start2));

long start3 = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
    int result = countOccurencesWithRegex(searchText, targetWord);
}

long end3 = System.currentTimeMillis();
System.out.println(String.format("Regex approach took: %s miliseconds", end3-start3));

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

Array split approach took: 152 miliseconds
Collections.frequency() approach took: 140 miliseconds
Regex approach took: 92 miliseconds

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

List possibleWords = Arrays.asList("hello", "world ");
StringBuffer searchTextBuffer = new StringBuffer();

for (int i = 0; i < 100; i++) {
    searchTextBuffer.append(String.join(" ", possibleWords));
}
System.out.println(searchTextBuffer);

זה יוצר מחרוזת עם התוכן:

hello world hello world hello world hello ...

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

עכשיו, אם היינו מחפשים "שלום" או "עולם" - היו הרבה יותר התאמות מהשניים מלפני כן. איך השיטות שלנו מסתדרות עכשיו במדד?

Array split approach took: 606 miliseconds
Collections.frequency() approach took: 899 miliseconds
Regex approach took: 801 miliseconds

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

עֵצָה: נסה את השיטות על הטקסט שלך, שים לב לזמנים ובחר את השיטות היעילה והאלגנטיות ביותר עבורך.

סיכום

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

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

בול זמן:

עוד מ Stackabuse