بررسی وجود چند کلیدواژه در یک رشته با جاوا — به زبان ساده

۳۳۴ بازدید
آخرین به‌روزرسانی: ۱۶ اردیبهشت ۱۳۹۸
زمان مطالعه: ۴ دقیقه
بررسی وجود چند کلیدواژه در یک رشته با جاوا — به زبان ساده

در این راهنمای مقدماتی با روش تشخیص وجود چند کلمه درون یک رشته متنی (String) آشنا می‌شویم. بدین منظور الگوریتم‌های یافتن کلیدواژه در یک رشته را در زبان جاوا طراحی و بررسی خواهیم کرد.

فرض کنید رشته زیر را داریم:

1String inputString = "hello there، Baeldung";

وظیفه ما این است که ببینیم آیا inputString شامل کلمات hello و Baeldung است یا خیر؛ بنابراین کلیدواژه‌های خود را در یک آرایه قرار می‌دهیم:

1String[] words = {"hello"، "Baeldung"};

به علاوه، ترتیب کلمات نیز مهم نیست و نوع مطابقت با در نظر گرفتن حروف کوچک/بزرگ است.

استفاده از متد ()String.contains

در آغاز، روش استفاده از متد ()String.contains برای رسیدن به هدف خود را بررسی می‌کنیم. به این منظور باید حلقه‌ای روی آرایه کلیدواژه‌ها تعریف کنیم و رخداد هر آیتم را درون inputString بررسی کنیم:

1public static boolean containsWords(String inputString, String[] items) {
2    boolean found = true;
3    for (String item : items) {
4        if (!inputString.contains(item)) {
5            found = false;
6            break;
7        }
8    }
9    return found;
10}

متد ()contains در صورتی مقدار true بازگشت می‌دهد که رشته inputString شامل آیتم مفروض باشد. هنگامی که هیچ کدام از کلیدواژه‌ها درون رشته نباشد، می‌توانیم فرایند کار را متوقف کرده و بی‌درنگ مقدار false بازگشت دهیم. علی‌رغم این واقعیت که نیاز به نوشتن کد بیشتر وجود دارد، این راه‌حل برای کاربردهای ساده سریع است.

استفاده از متد ()String.indexOf

همانند راه‌حل مطرح شده در بخش قبلی می‌توان اندیس‌های کلیدواژه‌ها را با استفاده از متد ()String.indexOf نیز بررسی کرد. به این منظور باید یک متد داشته باشیم که inputString و فهرستی از کلیدواژه‌ها را بپذیرد.

1public static boolean containsWordsIndexOf(String inputString, String[] words) {
2    boolean found = true;
3    for (String word : words) {
4        if (inputString.indexOf(word) == -1) {
5            found = false;
6            break;
7        }
8    }
9    return found;
10}

متد ()indexOf اندیس هر واژه را درون inputString بازگشت می‌دهد. هنگامی که کلمه مورد نظر در متن نباشد، اندیس برابر با 1- خواهد بود.

استفاده از عبارت‌های منظم

روش دیگری که برای نیل به مقصود مطرح شده در ابتدای این مطلب می‌توان به خدمت گرفت، استفاده از «عبارت‌های منظم» (Regular Expressions) برای تطبیق کلمه‌ها است. به این منظور از کلاس Pattern استفاده می‌کنیم.

ابتدا عبارت رشته‌ای را تعریف می‌کنیم. از آنجا که باید دو کلیدواژه را تطبیق دهم، ابتدا باید قاعده regex را با دو چشم‌انداز بسازیم:

1Pattern pattern = Pattern.compile("(?=.*hello)(?=.*Baeldung)");

در مورد کاربرد عمومی می‌توان از کد زیر استفاده کرد:

1StringBuilder regexp = new StringBuilder();
2for (String word : words) {
3    regexp.append("(?=.*").append(word).append(")");
4}

سپس از متد ()matcher برای پیدا کردن رخدادهای کلیدواژه استفاده می‌کنیم:

1public static boolean containsWordsPatternMatch(String inputString, String[] words) {
2 
3    StringBuilder regexp = new StringBuilder();
4    for (String word : words) {
5        regexp.append("(?=.*").append(word).append(")");
6    }
7 
8    Pattern pattern = Pattern.compile(regexp.toString());
9 
10    return pattern.matcher(inputString).find();
11}

اما عبارت‌های منظم هزینه عملیاتی بالایی دارند اگر بخواهیم به دنبال چند واژه بگردیم، عملکرد این راه‌حل ممکن است بهینه نباشد.

استفاده از جاوا 8 و List

روش دیگری که بررسی می‌کنیم API مربوط به Stream در جاوا 8 است. اما قبل از آن باید مقداری تبدیل جزئی روی داده‌های اولیه خود اجرا کنیم:

1List<String> inputString = Arrays.asList(inputString.split(" "));
2List<String> words = Arrays.asList(words);

اینک نوبت آن رسیده است که از Stream API استفاده کنیم:

1public static boolean containsWordsJava8(String inputString, String[] words) {
2    List<String> inputStringList = Arrays.asList(inputString.split(" "));
3    List<String> wordsList = Arrays.asList(words);
4 
5    return wordsList.stream().allMatch(inputStringList::contains);
6}

عملیات فوق در صورتی مقدار true بازگشت می‌دهد که رشته ورودی شامل همه کلیدواژه‌ها باشد. به طور جایگزین می‌توان از متد ()containsAll فریمورک Collections برای رسیدن به نتیجه مطلوب نیز استفاده کرد:

1public static boolean containsWordsArray(String inputString, String[] words) {
2    List<String> inputStringList = Arrays.asList(inputString.split(" "));
3    List<String> wordsList = Arrays.asList(words);
4 
5    return inputStringList.containsAll(wordsList);
6}

با این وجود، این متد صرفاً در مورد کلمات کامل عمل می‌کند. بنابراین تنها در صورتی می‌تواند کلیدواژه‌های مورد نظر را پیدا کند که در متن مربوطه با فاصله از کلمات دیگر جدا شده باشند.

استفاده از الگوریتم Aho-Corasick

الگوریتم Aho-Corasick به بیان ساده برای جستجوی متن با چند کلیدواژه طراحی شده است. پیچیدگی زمانی این الگوریتم (O(n ارتباطی با تعداد کلیدواژه‌هایی که جستجو می‌شوند و یا طول متنی که قرار است جستجو شود ندارد.

ابتدا باید وابستگی الگوریتم Aho-Corasick را در فایل pom.xml خود ذکر کنیم:

1<dependency>
2    <groupId>org.ahocorasick</groupId>
3    <artifactId>ahocorasick</artifactId>
4    <version>0.4.0</version>
5</dependency>

سپس درخت پیشوندی (Trie) را با آرایه‌ای از کلمات کلیدواژه‌ها می‌سازیم. به این منظور از ساختار داده Trie استفاده می‌کنیم:

1Trie trie = Trie.builder().onlyWholeWords().addKeywords(words).build();

پس از آن متد parser را با متن inputString فراخوانی می‌کنیم که می‌خواهیم کلیدواژه‌ها را در آن بیابیم و نتایج را در مجموعه emits ذخیره می‌کنیم:

1Collection<Emit> emits = trie.parseText(inputString);

در نهایت اگر نتایج خود را پرینت کنیم:

1emits.forEach(System.out::println);

در مورد هر کلیدواژه موقعیت آغازین کلیدواژه در متن، موقعیت انتهایی و خود کلیدواژه را خواهیم دید:

10:4=hello
213:20=Baeldung

در نهایت پیاده‌سازی کامل به صورت زیر خواهد بود:

1public static boolean containsWordsAhoCorasick(String inputString, String[] words) {
2    Trie trie = Trie.builder().onlyWholeWords().addKeywords(words).build();
3 
4    Collection<Emit> emits = trie.parseText(inputString);
5    emits.forEach(System.out::println);
6 
7    boolean found = true;
8    for(String word : words) {
9        boolean contains = Arrays.toString(emits.toArray()).contains(word);
10        if (!contains) {
11            found = false;
12            break;
13        }
14    }
15 
16    return found;
17}

در این مثال، ما تنها به دنبال کلمات کامل هستیم. اما اگر بخواهیم نه تنها inputString بلکه helloBaeldung را نیز تطبیق بدهیم، باید خصوصیت ()onlyWholeWords را از محاسبات سازنده Trie حذف کنیم.

به علاوه به خاطر داشته باشید که ما عناصر تکراری را نیز از مجموعه emits حذف کرده‌ایم، چون ممکن است چند مورد مطابقت از کلیدواژه یکسان وجود داشته باشند.

سخن پایانی

در این مقاله با روش‌های مختلف یافتن چند کلیدواژه در یک رشته در زبان برنامه‌نویسی آشنا شدیم. به علاوه مثال‌هایی را با استفاده از JDK مرکزی و همچنین کتابخانه Aho-Corasick ارائه کردیم. برای مشاهده کدهای کامل این مقاله می‌توانید به این ریپوی گیت‌هاب (+) مراجعه کنید.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۳ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
baeldung
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *