الكلية الجامعية للعلوم التطبيقيةبرمجة 1
English
المحاضرة 15 · 120 دقيقة

النصوص والمحارف

كيف يفكك البرنامج ما يكتبه المستخدم ويفهمه؟

في نهاية هذه المحاضرة ستكون قادرًا على

  1. تشرح النص بوصفه سلسلة من المحارف، لكل منها موقع، وتربطه بالمصفوفات
  2. تستخدم دوال String للبحث والقص والمقارنة والتقسيم
  3. تشرح لماذا لا تتغير النصوص، وتحفظ نتيجة كل دالة في متغير
  4. تمر على النص بحلقة for لتعدّ محارفه وتعكسه وتفحصه
  5. تحوّل النص إلى عدد، وتتعامل مع NumberFormatException
  6. تبني النص داخل الحلقات باستخدام StringBuilder
محتويات المحاضرة
  1. معظم ما يكتبه المستخدم نص، وعلى البرنامج أن يفككه
  2. النص سلسلة محارف مرقّمة من 0، مثل المصفوفة تمامًا
  3. آخر موقع هو length() - 1، وما بعده يوقف البرنامج
  4. النص العربي يعمل بالطريقة نفسها، وlength تعدّ المحارف
  5. دوال البحث تجيب أين النص الجزئي، وهل هو موجود
  6. النص لا يتغير أبدًا: كل دالة ترجع نصًا جديدًا
  7. قارن النصوص بالدوال لا بالعامل ==
  8. split تقطع النص إلى مصفوفة نصوص
  9. حلقة for تزور كل حرف، كما تزور كل عنصر في المصفوفة
  10. بناء النص المعكوس يقودنا إلى فحص الكلمات المتناظرة
  11. الصنف Character يجيب عن أسئلة كل محرف
  12. parseInt وparseDouble تحولان النص إلى عدد
  13. StringBuilder يبني النص في مكانه داخل الحلقات
  14. split مع parseInt يحولان السطر إلى بيانات: عدّ الكلمات وCSV
  15. فحص كل محرف يصنع شيفرة قيصر ومدقق أسماء المستخدمين
  16. أسئلة المراجعة
الجزء الأول: دوال النصوص

معظم ما يكتبه المستخدم نص، وعلى البرنامج أن يفككه

اسم المستخدم

هل طوله مقبول؟ هل يبدأ بحرف؟ هل فيه رموز ممنوعة؟

مربع البحث

يجد الموقع java حتى لو كتبت JAVA وتركت مسافة بعدها.

ملف الدرجات

السطر Sara,80,95 نص واحد، وعلى البرنامج أن يستخرج منه اسمًا وعددين.

النص سلسلة محارف مرقّمة من 0، مثل المصفوفة تمامًا

name.charAt(0)'S'char
name.charAt(1)'a'char
name.charAt(2)'r'char
name.charAt(3)'a'char
العمليةالمصفوفةالنص
العددa.lengths.length()
عنصر عند موقعa[i]s.charAt(i)
العنصر الأخيرa[a.length - 1]s.charAt(s.length() - 1)
تغيير عنصرa[0] = 9;غير ممكن

اقرأ النص حرفًا حرفًا

توقّع الأسطر الأربعة قبل التشغيل، ثم ضع اسمك مكان Sara وتوقّع مرة أخرى.

public class Positions {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.length());
        System.out.println(name.charAt(0));
        System.out.println(name.charAt(1));
        System.out.println(name.charAt(name.length() - 1));
    }
}
الناتج
4
S
a
a

النص ليس مصفوفة في الكتابة

استخدمنا الأقواس المربعة كما في المصفوفة. أصلح السطر ليطبع الحرف الأول.

public class FirstLetter {
    public static void main(String[] args) {
        String name = "Sara";
        char first = name[0];
        System.out.println(first);
    }
}
رسالة المترجمarray required, but String found
أظهر التصحيح
public class FirstLetter {
    public static void main(String[] args) {
        String name = "Sara";
        char first = name.charAt(0);
        System.out.println(first);
    }
}
الناتج
S

لماذا؟ يشبه النص المصفوفة في الفكرة فقط. الأقواس [] للمصفوفات، أما النص فنطلب منه الحرف بالدالة charAt.

length في النص دالة

public class NameLength {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.length);
    }
}
رسالة المترجمcannot find symbol
أظهر التصحيح
public class NameLength {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.length());
    }
}
الناتج
4

لماذا؟ a.length في المصفوفة بلا أقواس، وs.length() في النص بأقواس لأنها دالة. يبحث المترجم عن متغير باسم length داخل String فلا يجده.

آخر موقع هو length() - 1، وما بعده يوقف البرنامج

الخطأ نفسه الذي رأيناه في المصفوفات: النص Sara طوله 4، فمواقعه من 0 إلى 3. الموقع 4 غير موجود.

اقرأ رسالة الخطأ

شغّل البرنامج. يُطبع السطر الأول، ثم يتوقف البرنامج عند الموقع 4. أصلحه بكتابة name.length() - 1.

public class OutOfRange {
    public static void main(String[] args) {
        String name = "Sara";
        System.out.println(name.charAt(3));
        System.out.println(name.charAt(name.length()));
        System.out.println("This line never runs");
    }
}
الناتج
a
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 4
هذا ما يطبعه JDK 8. الرقم الأخير هو الموقع الذي طلبته.

النص "Gaza" طوله 4. أي استدعاء يوقف البرنامج؟

النص العربي يعمل بالطريقة نفسها، وlength تعدّ المحارف

تخزن Java كل محرف برقمه في Unicode، فالدوال نفسها تعمل على "سارة" كما تعمل على "Sara"، ويظهر النص صحيحًا على هذا الموقع.

اسم بالعربية

ضع اسمك بالعربية مكان سارة وتوقّع الطول قبل التشغيل.

public class ArabicText {
    public static void main(String[] args) {
        String name = "سارة";
        System.out.println(name);
        System.out.println(name.length());
        System.out.println(name.charAt(0));
        System.out.println(name.contains("ار"));
        System.out.println(name + " تتعلم Java");
    }
}
الناتج
سارة
4
س
true
سارة تتعلم Java

تبدو الكلمتان واحدة في القراءة. ماذا يطبع البرنامج؟

public class ArabicLength {
    public static void main(String[] args) {
        System.out.println("محمد".length());
        System.out.println("مُحمد".length());
    }
}
أظهر الناتج
الناتج
4
5

لماذا؟ تعدّ length المحارف لا البايتات، ولذلك يبلغ طول محمد أربعة. والضمة محرف مستقل في النص، فتزيد الطول واحدًا.

دوال البحث تجيب أين النص الجزئي، وهل هو موجود

الدالةترجعs = "Java"
s.substring(i, j)النص من الموقع i حتى ما قبل js.substring(1, 3) → "av"
s.substring(i)النص من الموقع i حتى النهايةs.substring(2) → "va"
s.indexOf(x)موقع أول ظهور، أو -1s.indexOf("a") → 1
s.contains(x)booleans.contains("av") → true
s.startsWith(x)booleans.startsWith("Ja") → true
s.endsWith(x)booleans.endsWith("a") → true

افحص بريدًا إلكترونيًا

عدّ مواقع المحارف حتى @ على الورق، ثم شغّل البرنامج. هل طابقت إجابات الصف في بداية المحاضرة ما تفعله هذه الدوال؟

public class EmailSearch {
    public static void main(String[] args) {
        String email = "sara.ali@ucas.edu.ps";
        int at = email.indexOf("@");
        System.out.println(at);
        System.out.println(email.substring(0, at));
        System.out.println(email.substring(at + 1));
        System.out.println(email.contains("ucas"));
        System.out.println(email.startsWith("sara"));
        System.out.println(email.endsWith(".com"));
        System.out.println(email.indexOf("#"));
    }
}
الناتج
8
sara.ali
ucas.edu.ps
true
true
false
-1

ماذا يطبع؟ اكتب مواقع الحروف على الورق أولًا.

public class Cut {
    public static void main(String[] args) {
        String word = "Programming";
        System.out.println(word.substring(3, 7));
    }
}
أظهر الناتج
الناتج
gram

لماذا؟ يدخل موقع البداية 3 في النتيجة، ولا يدخل موقع النهاية 7. والطول دائمًا 7 − 3 = 4 محارف.

ماذا ترجع "Java".indexOf("x")؟

اسم المستخدم والنطاق

اقرأ سطرًا فيه بريد إلكتروني. إذا لم يحتوِ على @ فاطبع Invalid. وإلا فاطبع ما قبل @ بعد User: ، ثم ما بعدها بعد Domain: في سطر ثانٍ.

import java.util.Scanner;

public class SplitEmail {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String email = input.nextLine();
        // Find the @, then cut the text around it

    }
}
stdin
sara@ucas.edu.ps
الناتج المتوقع
User: sara
Domain: ucas.edu.ps
تلميح 1

خزّن ناتج indexOf("@") في متغير، وقارنه بالرقم -1.

تلميح 2

substring(0, at) لما قبلها، وsubstring(at + 1) لما بعدها.

الحل
import java.util.Scanner;

public class SplitEmail {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String email = input.nextLine();
        int at = email.indexOf("@");
        if (at == -1) {
            System.out.println("Invalid");
        } else {
            System.out.println("User: " + email.substring(0, at));
            System.out.println("Domain: " + email.substring(at + 1));
        }
    }
}

النص لا يتغير أبدًا: كل دالة ترجع نصًا جديدًا

الدالةترجعمثال
toUpperCase()نسخة بأحرف كبيرة"Ali" → "ALI"
toLowerCase()نسخة بأحرف صغيرة"Ali" → "ali"
trim()نسخة بلا مسافات في الطرفين" a b " → "a b"
replace(a, b)نسخة استُبدل فيها كل ظهور"banana" → "bonono"

هذا أشهر فخ في النصوص. ماذا يطبع السطران؟

public class Immutable {
    public static void main(String[] args) {
        String name = "sara";
        name.toUpperCase();
        System.out.println(name);
        name = name.toUpperCase();
        System.out.println(name);
    }
}
أظهر الناتج
الناتج
sara
SARA

لماذا؟ يرجع السطر الثاني نصًا جديدًا فلا يحفظه أحد، ويبقى name كما هو. أما السطر الرابع فيضع النص الجديد في الصندوق، فيتغير المتغير لا النص القديم.

نظّف مدخلات المستخدم

أضفنا القوسين [ ] لتظهر المسافات. لاحظ أن raw في السطر الأخير لم يتغير رغم كل ما سبق. جرّب تبديل ترتيب الدوال في السطر الثالث.

public class CleanText {
    public static void main(String[] args) {
        String raw = "  Hello World  ";
        System.out.println("[" + raw.trim() + "]");
        System.out.println("[" + raw.trim().toLowerCase() + "]");
        System.out.println("[" + raw.replace("World", "Java") + "]");
        System.out.println("banana".replace("a", "o"));
        System.out.println("[" + raw + "]");
    }
}
الناتج
[Hello World]
[hello world]
[  Hello Java  ]
bonono
[  Hello World  ]

ماذا يطبع هذا الكود؟

String s = "abc";
s.replace("a", "z");
System.out.println(s);

قارن النصوص بالدوال لا بالعامل ==

الدالةالسؤالترجع
a.equals(b)هل المحارف نفسها تمامًا؟boolean
a.equalsIgnoreCase(b)هل هي نفسها مع إهمال حالة الأحرف؟boolean
a.compareTo(b)أيهما يأتي أولًا في ترتيب القاموس؟سالب إذا سبق a، و0 إذا تساويا، وموجب إذا تأخر

ثلاث طرق للمقارنة

تقارن compareTo رقم المحرف في Unicode عند أول اختلاف، والأحرف الكبيرة قبل الصغيرة. ولهذا يأتي Zed قبل apple.

public class CompareStrings {
    public static void main(String[] args) {
        System.out.println("java".equals("Java"));
        System.out.println("java".equalsIgnoreCase("Java"));
        System.out.println("apple".compareTo("banana"));
        System.out.println("banana".compareTo("apple"));
        System.out.println("apple".compareTo("apple"));
        System.out.println("Zed".compareTo("apple"));
    }
}
الناتج
false
true
-1
1
0
-7

النصان يحملان المحارف نفسها. ماذا يطبع السطران؟

public class SameText {
    public static void main(String[] args) {
        String first = "hello";
        String part = "hel";
        String second = part + "lo";
        System.out.println(first == second);
        System.out.println(first.equals(second));
    }
}
أظهر الناتج
الناتج
false
true

لماذا؟ يسأل == هل هما النص نفسه في الذاكرة، وequals هل محارفهما متطابقة. أنشأ الدمج نصًا جديدًا في مكان آخر، فاختلف جواب ==. والنصوص القادمة من المستخدم تقع في الفخ نفسه.

تريد ترتيب الأسماء أبجديًا. ماذا يعني أن a.compareTo(b) أكبر من صفر؟

الترتيب بالفقاعات مع النصوص

هذه خوارزمية المحاضرة السابقة، تغيّر فيها شرط المقارنة فقط. اضغط «تتبّع سطرًا سطرًا» وراقب التبديل، ثم جرّب Arrays.sort(names) مكان الحلقتين.

import java.util.Arrays;

public class SortNames {
    public static void main(String[] args) {
        String[] names = {"Sara", "Ali", "Huda"};
        for (int i = 0; i < names.length - 1; i++) {
            for (int k = 0; k < names.length - 1 - i; k++) {
                if (names[k].compareTo(names[k + 1]) > 0) {
                    String temp = names[k];
                    names[k] = names[k + 1];
                    names[k + 1] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(names));
    }
}
الناتج
[Ali, Huda, Sara]

split تقطع النص إلى مصفوفة نصوص

هنا يلتقي النص بالمصفوفات: تعطي split فاصلًا، فترجع مصفوفة String[] تمر عليها بكل ما تعلمته في المحاضرتين السابقتين.

من سطر إلى قطع

اضغط «تتبّع سطرًا سطرًا» وانظر إلى المصفوفة parts. غيّر الفاصل إلى ; في النص وفي split.

public class SplitBasics {
    public static void main(String[] args) {
        String line = "Sara,19,3.75";
        String[] parts = line.split(",");
        System.out.println(parts.length);
        for (int i = 0; i < parts.length; i++) {
            System.out.println(i + ": " + parts[i]);
        }
    }
}
الناتج
3
0: Sara
1: 19
2: 3.75

فاصلتان متتاليتان، وفاصلتان في النهاية. ماذا يطبع؟

public class SplitEmpty {
    public static void main(String[] args) {
        String[] parts = "one,,three,,".split(",");
        System.out.println(parts.length);
        System.out.println("[" + parts[1] + "]");
    }
}
أظهر الناتج
الناتج
3
[]

لماذا؟ تبقى القطعة الفارغة في الوسط، فتكون القطعة الثانية نصًا فارغًا. أما القطع الفارغة في النهاية فتحذفها split، ولذلك يبقى ثلاث قطع.

الفراغات والنقطة

السطر الأول يعطي 7 قطعًا لأن المسافات الزائدة تصنع قطعًا فارغة. والحل في السطر الثاني: trim ثم "\\s+".

public class SplitPatterns {
    public static void main(String[] args) {
        String text = "  one   two three ";
        System.out.println(text.split(" ").length);
        System.out.println(text.trim().split("\\s+").length);
        System.out.println("3.75".split(".").length);
        System.out.println("3.75".split("\\.").length);
    }
}
الناتج
7
3
0
2
الجزء الثاني: المرور على النص حرفًا حرفًا

حلقة for تزور كل حرف، كما تزور كل عنصر في المصفوفة

for (int i = 0; i < text.length(); i++) {
    char c = text.charAt(i);
    // use c
}
النمط الذي ستكتبه كثيرًا. الشرط < وليس <=.

عدّ حروف العلة

اضغط «تتبّع سطرًا سطرًا» وراقب c وcount. لماذا حوّلنا النص إلى أحرف صغيرة أولًا؟ غيّر النص وتوقّع العدد.

public class CountVowels {
    public static void main(String[] args) {
        String text = "Programming in Java".toLowerCase();
        int count = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                count++;
            }
        }
        System.out.println("Vowels: " + count);
    }
}
الناتج
Vowels: 6

ماذا يحدث إذا كتبت الشرط i <= text.length()؟

عدّ حروف العلة في سطر

اقرأ سطرًا واطبع عدد حروف العلة a e i o u، سواء كانت كبيرة أو صغيرة. اطبع الرقم فقط.

import java.util.Scanner;

public class VowelCounter {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine();
        // Count the vowels and print the count

    }
}
stdin
Hello World
الناتج المتوقع
3
تلميح 1

حوّل النص كله بالدالة toLowerCase، وخزّن الناتج في متغير.

تلميح 2

قارن char بعلامة ==، فالمحرف نوع بدائي وليس نصًا.

الحل
import java.util.Scanner;

public class VowelCounter {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine().toLowerCase();
        int count = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') {
                count++;
            }
        }
        System.out.println(count);
    }
}

بناء النص المعكوس يقودنا إلى فحص الكلمات المتناظرة

اعكس نصًا

نمشي من الموقع الأخير إلى الموقع 0. اضغط «تتبّع سطرًا سطرًا» وراقب reversed يكبر. لاحظ أن كلمة رادار تُقرأ من الطرفين بالطريقة نفسها.

public class ReverseText {
    public static String reverse(String text) {
        String reversed = "";
        for (int i = text.length() - 1; i >= 0; i--) {
            reversed = reversed + text.charAt(i);
        }
        return reversed;
    }

    public static void main(String[] args) {
        System.out.println(reverse("Java"));
        System.out.println(reverse("رادار").equals("رادار"));
    }
}
الناتج
avaJ
true

هنا نمشي إلى الأمام، لكن الحرف يوضع قبل النص. ماذا يطبع؟

public class Prepend {
    public static void main(String[] args) {
        String word = "abc";
        String out = "";
        for (int i = 0; i < word.length(); i++) {
            out = word.charAt(i) + out;
        }
        System.out.println(out);
    }
}
أظهر الناتج
الناتج
cba

لماذا؟ تأخذ out القيم a ثم ba ثم cba، لأن كل حرف جديد يوضع في المقدمة. وترتيب طرفي + مهم في النصوص.

هل الكلمة متناظرة؟

اقرأ كلمة. اطبع Palindrome إذا كانت تُقرأ من الطرفين بالطريقة نفسها مع إهمال حالة الأحرف، وإلا فاطبع Not palindrome. اكتب دالة isPalindrome ترجع boolean.

import java.util.Scanner;

public class PalindromeCheck {
    // Write isPalindrome here

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String word = input.next();
        // Call your method and print the answer

    }
}
stdin
level
الناتج المتوقع
Palindrome
تلميح 1

اعكس الكلمة كما في البرنامج أعلاه، ثم قارن بالدالة equalsIgnoreCase.

تلميح 2

طريقة أخرى: قارن الموقع i بالموقع length() - 1 - i حتى منتصف الكلمة.

الحل
import java.util.Scanner;

public class PalindromeCheck {
    public static boolean isPalindrome(String word) {
        String reversed = "";
        for (int i = word.length() - 1; i >= 0; i--) {
            reversed = reversed + word.charAt(i);
        }
        return word.equalsIgnoreCase(reversed);
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String word = input.next();
        if (isPalindrome(word)) {
            System.out.println("Palindrome");
        } else {
            System.out.println("Not palindrome");
        }
    }
}

الصنف Character يجيب عن أسئلة كل محرف

الدالةالسؤال أو العملمثال
Character.isDigit(c)هل هو رقم؟isDigit('7') → true
Character.isLetter(c)هل هو حرف؟ والعربية حروف أيضًا.isLetter('س') → true
Character.toUpperCase(c)يرجع الحرف الكبيرtoUpperCase('a') → 'A'

صنّف المحارف

اضغط «تتبّع سطرًا سطرًا» وتابع العدادات الثلاثة. بدّل النص بآخر فيه أحرف عربية.

public class CharKinds {
    public static void main(String[] args) {
        String text = "Room 12B!";
        int letters = 0;
        int digits = 0;
        int others = 0;
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (Character.isLetter(c)) {
                letters++;
            } else if (Character.isDigit(c)) {
                digits++;
            } else {
                others++;
            }
        }
        System.out.println("Letters: " + letters);
        System.out.println("Digits: " + digits);
        System.out.println("Others: " + others);
    }
}
الناتج
Letters: 5
Digits: 2
Others: 2

المحرف في الحقيقة رقم. ماذا يطبع؟ رقم a في Unicode هو 97.

public class CharMath {
    public static void main(String[] args) {
        char c = 'a';
        System.out.println(c + 1);
        System.out.println((char) (c + 1));
        System.out.println(Character.toUpperCase(c) + "bc");
    }
}
أظهر الناتج
الناتج
98
b
Abc

لماذا؟ char + int يعطي int، فيطبع 98. والتحويل (char) يعيد الرقم إلى حرف. وفي السطر الأخير طرف نصي، فيكون + دمجًا. سنحتاج إلى هذه الفكرة في شيفرة قيصر.

محرف واحد أم نص؟

public class Grade {
    public static void main(String[] args) {
        char grade = "A";
        System.out.println(grade);
    }
}
رسالة المترجمincompatible types: String cannot be converted to char
أظهر التصحيح
public class Grade {
    public static void main(String[] args) {
        char grade = 'A';
        System.out.println(grade);
    }
}
الناتج
A

لماذا؟ العلامتان " " تصنعان نصًا حتى لو كان حرفًا واحدًا. والمحرف يُكتب بين ' '.

parseInt وparseDouble تحولان النص إلى عدد

القطع التي تخرج من split نصوص، حتى لو بدت أرقامًا. قبل أي حساب يجب تحويلها.

من نص إلى عدد

public class ParseNumbers {
    public static void main(String[] args) {
        String age = "19";
        System.out.println(age + 1);
        int years = Integer.parseInt(age);
        System.out.println(years + 1);
        double gpa = Double.parseDouble("3.75");
        System.out.println(gpa * 2);
    }
}
الناتج
191
20
7.5

ماذا يطبع السطران؟

public class ParseOrJoin {
    public static void main(String[] args) {
        String a = "5";
        String b = "3";
        System.out.println(a + b);
        System.out.println(Integer.parseInt(a) + Integer.parseInt(b));
    }
}
أظهر الناتج
الناتج
53
8

لماذا؟ بين نصين تعني + الدمج. وبعد التحويل يصبح الطرفان عددين، فتعني الجمع.

نص ليس عددًا

شغّله واقرأ الرسالة: For input string: "abc". يتوقف البرنامج عند السطر الذي فيه parseInt.

public class ParseCrash {
    public static void main(String[] args) {
        System.out.println("Before");
        int n = Integer.parseInt("abc");
        System.out.println(n);
    }
}
الناتج
Before

لمحة: try وcatch يلتقطان الاستثناء

إذا ظهر الاستثناء داخل try انتقل التنفيذ إلى catch بدل أن يتوقف البرنامج. هذه لمحة فقط تكفي لهذا الاستثناء، وستدرسها بعمق في برمجة 2. لاحظ أن " 7" يفشل بسبب المسافة، فاستخدم trim قبل التحويل.

public class ParseTry {
    public static void main(String[] args) {
        String[] inputs = {"42", "3.5", "abc", " 7", ""};
        for (String text : inputs) {
            try {
                int n = Integer.parseInt(text);
                System.out.println("OK: " + n);
            } catch (NumberFormatException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
الناتج
OK: 42
For input string: "3.5"
For input string: "abc"
For input string: " 7"
For input string: ""

ماذا يحدث عند تنفيذ Integer.parseInt("3.5")؟

StringBuilder يبني النص في مكانه داخل الحلقات

بما أن النص لا يتغير، فإن كل s = s + c في حلقة تنشئ نصًا جديدًا وتنسخ كل ما سبق. لا يهم ذلك مع كلمة قصيرة، لكنه بطيء مع نص طويل. أما StringBuilder فصندوق نص قابل للتعديل، ينمو دون نسخ كل مرة.

الدالةتعمل
new StringBuilder()صندوق فارغ، مثلما تنشئ Scanner بالكلمة new
sb.append(x)تضيف نصًا أو حرفًا أو عددًا في النهاية
sb.reverse()تعكس المحتوى في مكانه
sb.length()عدد المحارف حتى الآن
sb.toString()ترجع String عاديًا، لاستخدام دواله

ابنِ سطرًا في حلقة

اضغط «تتبّع سطرًا سطرًا» وراقب line يكبر. لماذا لا تظهر فاصلة بعد الرقم الأخير؟

public class BuilderLine {
    public static void main(String[] args) {
        StringBuilder line = new StringBuilder();
        for (int i = 1; i <= 5; i++) {
            line.append(i);
            if (i < 5) {
                line.append(", ");
            }
        }
        String result = line.toString();
        System.out.println(result);
        System.out.println(result.length());
    }
}
الناتج
1, 2, 3, 4, 5
13

لا يحفظ هذا البرنامج ما ترجعه أي دالة. ماذا يطبع؟

public class BuilderVsString {
    public static void main(String[] args) {
        StringBuilder box = new StringBuilder("abc");
        box.append("d");
        System.out.println(box);
        String text = "abc";
        text.concat("d");
        System.out.println(text);
        box.reverse();
        System.out.println(box);
    }
}
أظهر الناتج
الناتج
abcd
abc
dcba

لماذا؟ StringBuilder يتغير هو نفسه، فلا حاجة إلى حفظ الناتج. أما concat فهي مثل +، ترجع نصًا جديدًا يضيع هنا.

احذف حروف العلة

اقرأ سطرًا واطبعه دون حروف العلة الصغيرة والكبيرة a e i o u، مع إبقاء كل ما سواها. استخدم StringBuilder.

import java.util.Scanner;

public class RemoveVowels {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine();
        StringBuilder result = new StringBuilder();
        // Append every character that is not a vowel

        System.out.println(result);
    }
}
stdin
Programming
الناتج المتوقع
Prgrmmng
تلميح 1

حوّل كل محرف إلى صغير بالدالة Character.toLowerCase لتقارنه مرة واحدة، لكن أضف المحرف الأصلي.

الحل
import java.util.Scanner;

public class RemoveVowels {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String text = input.nextLine();
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            char low = Character.toLowerCase(c);
            if (low != 'a' && low != 'e' && low != 'i' && low != 'o' && low != 'u') {
                result.append(c);
            }
        }
        System.out.println(result);
    }
}
الجزء الثالث: برامج كاملة

split مع parseInt يحولان السطر إلى بيانات: عدّ الكلمات وCSV

عدّ الكلمات

اقرأ سطرًا واطبع عدد الكلمات فيه. قد توجد مسافات زائدة في أي مكان، وقد يكون السطر فارغًا فيكون الجواب 0.

import java.util.Scanner;

public class WordCount {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine();
        // Clean the line, split it, and print the number of pieces

    }
}
stdin
Java is fun
الناتج المتوقع
3
تلميح 1

استخدم trim ثم split("\\s+").

تلميح 2

"".split(...) ترجع مصفوفة فيها قطعة واحدة فارغة، فافحص السطر الفارغ بعد trim قبل التقسيم.

الحل
import java.util.Scanner;

public class WordCount {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine().trim();
        if (line.length() == 0) {
            System.out.println(0);
        } else {
            System.out.println(line.split("\\s+").length);
        }
    }
}

قارئ أسطر CSV

CSV هو تنسيق جداول Excel حين تُحفظ نصًا: كل سطر صف، والفاصلة تفصل الأعمدة. اضغط «تتبّع سطرًا سطرًا» وانظر إلى parts. أضف سطرًا لطالب جديد إلى المصفوفة.

public class CsvParser {
    public static void main(String[] args) {
        String[] lines = {"Sara Ali, 19, 3.75", "Omar, 20, 2.5"};
        for (String line : lines) {
            String[] parts = line.split(",");
            String name = parts[0].trim();
            int age = Integer.parseInt(parts[1].trim());
            double gpa = Double.parseDouble(parts[2].trim());
            System.out.printf("%-10s %3d %5.2f%n", name, age + 1, gpa);
        }
    }
}
الناتج
Sara Ali    20  3.75
Omar        21  2.50

سطر درجات

اقرأ سطرًا بالشكل name,mark1,mark2,mark3، وقد توجد مسافات حول الفواصل. اطبع Name: مع الاسم، ثم Total: مع المجموع، ثم Average: مع المعدل بمنزلتين. وإذا لم يكن في السطر أربع قطع بالضبط، فاطبع Bad line فقط.

import java.util.Scanner;

public class CsvLine {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine();
        // Split, check the number of pieces, convert, print

    }
}
stdin
Sara,80,90,100
الناتج المتوقع
Name: Sara
Total: 270
Average: 90.00
تلميح 1

استدعِ trim على كل قطعة قبل parseInt.

تلميح 2

احذر قسمة الأعداد الصحيحة: total / 3.0.

الحل
import java.util.Scanner;

public class CsvLine {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String line = input.nextLine();
        String[] parts = line.split(",");
        if (parts.length != 4) {
            System.out.println("Bad line");
        } else {
            int total = 0;
            for (int i = 1; i < parts.length; i++) {
                total += Integer.parseInt(parts[i].trim());
            }
            System.out.println("Name: " + parts[0].trim());
            System.out.println("Total: " + total);
            System.out.printf("Average: %.2f%n", total / 3.0);
        }
    }
}

فحص كل محرف يصنع شيفرة قيصر ومدقق أسماء المستخدمين

شيفرة قيصر للأحرف الصغيرة

c - 'a' يعطي رقم الحرف من 0 إلى 25، و% 26 يعيدنا إلى البداية بعد z. اضغط «تتبّع سطرًا سطرًا» وتوقف عند الحرف z لترى ذلك. لفك الشيفرة نزيح بالمقدار 26 - key.

public class CaesarLab {
    public static String encrypt(String text, int key) {
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c >= 'a' && c <= 'z') {
                out.append((char) ('a' + (c - 'a' + key) % 26));
            } else {
                out.append(c);
            }
        }
        return out.toString();
    }

    public static void main(String[] args) {
        String secret = encrypt("lazy zebra", 3);
        System.out.println(secret);
        System.out.println(encrypt(secret, 26 - 3));
    }
}
الناتج
odcb cheud
lazy zebra

شيفرة قيصر كاملة

اقرأ المفتاح في السطر الأول (من 0 إلى 25) والرسالة في السطر الثاني. اطبع الرسالة مشفرة: تُزاح الأحرف الصغيرة والكبيرة وتبقى على حالتها، ولا يتغير أي محرف آخر.

import java.util.Scanner;

public class Caesar {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int key = input.nextInt();
        // Careful: what does nextInt leave behind?

    }
}
stdin
3
abc xyz
الناتج المتوقع
def abc
تلميح 1

بعد nextInt استدعِ nextLine مرة لتجاوز نهاية السطر الأول، ثم مرة ثانية لقراءة الرسالة.

تلميح 2

أضف فرعًا ثانيًا للأحرف الكبيرة يبدأ من 'A'.

الحل
import java.util.Scanner;

public class Caesar {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int key = input.nextInt();
        input.nextLine();
        String text = input.nextLine();
        StringBuilder out = new StringBuilder();
        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c >= 'a' && c <= 'z') {
                out.append((char) ('a' + (c - 'a' + key) % 26));
            } else if (c >= 'A' && c <= 'Z') {
                out.append((char) ('A' + (c - 'A' + key) % 26));
            } else {
                out.append(c);
            }
        }
        System.out.println(out);
    }
}

مدقق اسم المستخدم

اقرأ سطرًا وافحصه بهذا الترتيب، واطبع أول خطأ فقط: طوله من 5 إلى 12 وإلا Bad length، ويبدأ بحرف وإلا Bad start، وكل محارفه حروف أو أرقام أو _ وإلا Bad character. وإذا نجحت كل الفحوص فاطبع Valid. اكتب دالة ترجع الرسالة.

import java.util.Scanner;

public class UsernameValidator {
    public static String check(String name) {
        // Return the first problem, or "Valid"
        return "Valid";
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String name = input.nextLine();
        System.out.println(check(name));
    }
}
stdin
sara_99
الناتج المتوقع
Valid
تلميح 1

افحص الطول أولًا، فهو يحميك من charAt(0) على نص فارغ.

تلميح 2

return توقف الدالة فورًا، فلا تحتاج إلى else بعد كل فحص.

الحل
import java.util.Scanner;

public class UsernameValidator {
    public static String check(String name) {
        if (name.length() < 5 || name.length() > 12) {
            return "Bad length";
        }
        if (!Character.isLetter(name.charAt(0))) {
            return "Bad start";
        }
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (!Character.isLetter(c) && !Character.isDigit(c) && c != '_') {
                return "Bad character";
            }
        }
        return "Valid";
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String name = input.nextLine();
        System.out.println(check(name));
    }
}

أسئلة المراجعة

أي كتابة صحيحة لطول النص s ولطول المصفوفة a؟

أي شرط صحيح لفحص كلمة المرور pass المقروءة من المستخدم؟

متى تختار StringBuilder بدل +؟

عدة دوال في برنامج واحد. ماذا يطبع؟

public class ReviewStrings {
    public static void main(String[] args) {
        String line = " Ali,Sara ";
        String[] names = line.trim().split(",");
        System.out.println(names[1].toUpperCase().charAt(0));
        System.out.println(names[0].length() + names[1].length());
        System.out.println(line.length());
    }
}
أظهر الناتج
الناتج
S
7
10

لماذا؟ names[1] هو Sara، وأول حرف كبير منه S. والطولان 3 و4 عددان فيجمعان 7. أما line فلم يتغير رغم trim، فما زال طوله 10.