diff --git a/problem-set/FillRaw.java b/problem-set/FillRaw.java index b7292e1..3f37f9e 100644 --- a/problem-set/FillRaw.java +++ b/problem-set/FillRaw.java @@ -1,31 +1,32 @@ -/* - * File: FillRaw.java - * ---------------------------- - * FillRaw დაეხმარება კარელს, შეავსოს ქუჩა ბიპერებით, - * რომელზედაც ის ამჟამად იმყოფება. - * - * ამოცანის პირობა: - * კარელი დგას 1x1 უჯრაზე, შეავსებინეთ მას პირველი ქუჩა ბრილიანტებით. ანუ პირველი - * ქუჩის ყველა უჯრაზე უნდა იდოს ზუსტად ერთი ბრილიანტი. ჩათვალეთ რომ საწყის - * სამყაროში ბრილიანტები არსად არ დევს. - */ - -import stanford.karel.*; - -public class FillRaw extends SuperKarel { - - public void run() { - // Karel must follow the street until it faces the wall. - // So the execution must be run while front is clear. - while(frontIsClear()) { - // While it moves through the street, it places the beepers on the road. - putBeeper(); - // After placing the beeper, it needs to move to the next box. - move(); - } - // Keep in mind the Off-by-one error - // Take a look at /problem-set/FillRaw.md for further explanation - putBeeper(); - } - -} +@@ -1,31 +0,0 @@ +/* +* File: FillRaw.java + * ---------------------------- + * FillRaw დაეხმარება კარელს, შეავსოს ქუჩა ბიპერებით, + * რომელზედაც ის ამჟამად იმყოფება. + * + * ამოცანის პირობა: + * კარელი დგას 1x1 უჯრაზე, შეავსებინეთ მას პირველი ქუჩა ბრილიანტებით. ანუ პირველი + * ქუჩის ყველა უჯრაზე უნდა იდოს ზუსტად ერთი ბრილიანტი. ჩათვალეთ რომ საწყის + * სამყაროში ბრილიანტები არსად არ დევს. + */ + +import stanford.karel.*; + +public class FillRaw extends SuperKarel { + + public void run() { + // Karel must follow the street until it faces the wall. + // So the execution must be run while front is clear. + while(frontIsClear()) { + // While it moves through the street, it places the beepers on the road. + putBeeper(); + // After placing the beeper, it needs to move to the next box. + move(); + } + // Keep in mind the Off-by-one error + // Take a look at /problem-set/FillRaw.md for further explanation + putBeeper(); + } + +} \ No newline at end of file diff --git a/problem-set/FillRaw.md b/problem-set/FillRaw.md index 792b9f3..266c300 100644 --- a/problem-set/FillRaw.md +++ b/problem-set/FillRaw.md @@ -1,105 +1,104 @@ -# FillRaw - -პრობლემა: -``` -კარელი დგას 1x1 უჯრაზე, შეავსებინეთ მას პირველი ქუჩა ბრილიანტებით. ანუ პირველი ქუჩის ყველა უჯრაზე უნდა იდოს ზუსტად ერთი ბრილიანტი. ჩათვალეთ რომ საწყის სამყაროში ბრილიანტები არსად არ დევს. -``` - - - -## პრობლემის გადაჭრის გზა -პირველ რიგში ჩვენი პრობლემა დავყოთ ორ ნაწილად -* კარელმა უნდა გაიაროს მთლიანი ქუჩა -* სიარულთან ერთად, კარელმა პარალელურად უნდა განათავსოს ბრილიანტები ქუჩაზე - ---- - -### მთლიანი ქუჩის გავლის პრობლემას გადავჭრით შემდეგი საშაულებით -1. გამოვიყენოთ კარელისათვის ცნობილი `frontIsClear()` მეთოდი. -2. გამოვიყენოთ `while` ციკლი. -3. წინაღობის შემოწმების შემდგომ, განვახორციელოთ 1 უჯრით წინ გადანაცვლება `move()` მეთოდის საშუალებით, რათა შევძლოთ მთლიანი ქუჩის გავლა. - - -შესაბამისად, მივიღებთ კოდს: -```java -while(frontIsClear()) { - move(); -} -``` - -> *როდესაც კარელის წინ აღმოჩნდება კედელი, `frontIsClear()` მეთოდი `while` ციკლს შეატყობინებს, რომ კარელის წინ დაბრკოლებაა და კარელი შეწყეტს სვლას (ციკლი დასრულდება).* - ---- - -### სიარულის პარალელურად, ბრილიანტების განთავსება ქუჩაზე -ახლა, როდესაც კარელი წარმატებით გადის ქუჩას, მნიშვნელოვანია ბრილინატების განთავსება, რომელსაც `putBeeper()` მეთოდის საშუალებით გავაკეთებთ. მისი `while` ციკლში მოთავსებით კოდი მიიღებს შემდეგ სახეს -```java -while(frontIsClear()) { - putBeeper(); - move(); -} -``` -ამ ეტაპისთვის გვგონია, რომ პრობლემა გადაჭრილია და არანაირ მოდიფიკაციას არ საჭიროებს, თუმცა კოდის წარმატებით გაშვების შემდგომ მივიღებთ სამყაროს, სადაც კარელმა წარმატებით შეძლო მისი ქუჩის უჯრებზე მხოლოდ 1 ბრილიანტის განთავსება, **გარდა 1 უჯრისა.** - -ეს უჯრა, სწორედ ისაა, რომელზედაც იგი მუშაობის დასასრულს იმყოფება. მოდით, განვიხილოთ თუ რას შეიძლებოდა გამოეწვია მსგავსი სახის პრობლემა. -* ამ შეცდომას პროგრამირებაში [Off-By-One Error](https://stackoverflow.com/questions/2939869/what-is-an-off-by-one-error-and-how-do-i-fix-it)-ად მოიხსენიებენ და თავისი სახელიდან გამომდინარე მივხვდებით, რომ შეცდომა ძირითადად **იტერირების** დროს შეიძლება წარმოიშვას. -* განვიხილოთ კარელის შემთხვევა (სამყარო 10x10-ზე) - * კარელი დგას მე-9 უჯრაზე. - * დავდეთ ბრილიანტი. - * გამოვიძახეთ `move()` მეთოდი და გადავინაცვლეთ მე-10 უჯრაზე. - * მე-10 უჯრაზე გადასვლის შემდგომ კარელი ამოწმებს, აქვს თუ არა მას წინაღობა. იგი იძახებს`frontIsClear()` მეთოდს, რაზეც იგი იღებს პასუხს, რომ მის წინ მდებარეობს კედელი. შესაბამისად, კარელი ასრულებს `while` ციკლს და ვეღარ ახერხებს `putBeeper()` მეთოდის გამოძახებას. - -![Image Of Karel Standing on 10th Box](/problem-set/images/XQRF1oc.png) - -ამ ყველაფრის შემდგომ, პრობლემის გადასაჭრელად, დაგვჭირდება `putBeeper()` მეთოდის ჩამატება ჩვენს კოდში: -```java -while(frontIsClear()) { - putBeeper(); - move(); -} -// დებს ბრილიანტს, ბოლო უჯრაზე -putBeeper(); -``` ---- - -## რატომ იმუშავებს კოდი ნებისმიერი სამყაროს ზომისათვის? -* განვიხილოთ სამყარო, რომლის სიგრძეც 1-ის ტოლია (სიმაღლეს მნიშვნელობა არ აქვს, ვინაიდან კარელი მოძრაობს მარცხნიდან მარჯვნივ, მხოლოდ). - * ჩვენს მიერ დაწერილი კოდი, პირველ რიგში შეამოწმებს, არის თუ არა კარელის წინ კედელი. - * `frontIsClear()` მეთოდი დააბრუნებს პასუხს, რომ კარელს გზა დაბლოკილი აქვს და ვერ შეძლებს წინ წასვლას, შესაბამისად კარელი გამოტოვებს `while` ციკლს - * კარელი გადაინაცვლებს `while` ციკლის შემდგომ ბრძანებაზე და დახვდება `putBeeper()` მეთოდი, რაც უზრუნველყოფს ბრილიანტის წარმატებით დადებას სამყაროში. შესაბამისად, ჩვენი პრობლემაც გადაჭრილია და კარელი იდგება ქუჩაზე, რომლის ყველა უჯრაზე მხოლოდ 1 ბრილიანტი დევს - - ![Karel in 1x10 World](/problem-set/images/RwZB99V.png) - -* ნებისმიერი სხვა სამყაროსთვის, რომლის სიგრძეც > 1, ზუსტად იგივე ლოგიკით იმუშავებს, როგორც ზემოთხსენებული 10x10 სამყაროზე იმიტომ, რომ კარელი ამოწმებს წინ არსებულ დაბრკოლებას და მხოლოდ ამის შემდგომ იღებს გადაწყვეტილებას, გააგრძელოს თუ დაასრულოს სვლა. - ---- - -## შესაძლო ხარვეზები ამოხსნის იმპლემენტაციისას -პირველ რიგში, მინდა მოგილოცოთ პროგრამის წარმატებით დასრულება და იმპლემენტაცია, თუმცა არსებობს 2 დეტალი, რისი გათვალისწინებაც საკმაოდ მნიშვნელოვანია ამ ამოცანის გადაჭრისას - -1. გასათვალისწინებელია ზემოთ ნახსენები [Off-By-One Error](https://stackoverflow.com/questions/2939869/what-is-an-off-by-one-error-and-how-do-i-fix-it) -2. ასევე, საინტერესოა, რა მოხდება თუ `while` ციკლის ტანში ადგილებს გავუცვლით `move()`-სა და `putBeeper()` მეთოდებს. ერთი შეხედვით თითქოს ისევ სწორად უნდა იმუშაოს პროგრამამ, ვინაიდან ჩვენ მხოლოდ 2 ბრძანებას გავუცვალეთ ადგილი. - 1. მივიღებთ კოდს - ```java - while(frontIsClear()) { - move(); - putBeeper(); - } - putBeeper(); - ``` - 2. თუ კარგად დავაკვირდებით და გავიაზრებთ კოდს, შევამჩნევთ, რომ კარელის ქმედებები შეიცვლება. - * კოდის გაშვების შემდეგ კარელი ჯერ გადაინაცვლებს მეორე უჯრაზე, ხოლო ამის შემდეგ დადებს ბრილიანტს. - * თავისთავად ჩვენს კოდში წარმოიშვა ხარვეზი, რაც მოიაზრებს შემდეგს: კარელის სამყაროში პირველი უჯრა ყოველთვის ცარიელი დარჩება, ხოლო დამთავრების ადგილას იგივე განათავსებს 2 ბრილინატს. - - ![Karel in 1x10 World](/problem-set/images/karel_bug_0.png) - - 3. ამ პრობლემას 1 მარტივი გადაჭრის გზა აქვს. - 1. ავიტანოთ `while` ციკლის გარეთ მყოფი `putBeeper()` მეთოდი და გამოვიძახოთ `while` ციკლის გამოძახებამდე. - 2. ახალი კოდი - ```java - putBeeper(); - while(frontIsClear()) { - move(); - putBeeper(); - } - ``` +@@ -1,105 +0,0 @@ +# FillRaw +პრობლემა: +``` +კარელი დგას 1x1 უჯრაზე, შეავსებინეთ მას პირველი ქუჩა ბრილიანტებით. ანუ პირველი ქუჩის ყველა უჯრაზე უნდა იდოს ზუსტად ერთი ბრილიანტი. ჩათვალეთ რომ საწყის სამყაროში ბრილიანტები არსად არ დევს. +``` + + + +## პრობლემის გადაჭრის გზა +პირველ რიგში ჩვენი პრობლემა დავყოთ ორ ნაწილად +* კარელმა უნდა გაიაროს მთლიანი ქუჩა +* სიარულთან ერთად, კარელმა პარალელურად უნდა განათავსოს ბრილიანტები ქუჩაზე + +--- + +### მთლიანი ქუჩის გავლის პრობლემას გადავჭრით შემდეგი საშაულებით +1. გამოვიყენოთ კარელისათვის ცნობილი `frontIsClear()` მეთოდი. +2. გამოვიყენოთ `while` ციკლი. +3. წინაღობის შემოწმების შემდგომ, განვახორციელოთ 1 უჯრით წინ გადანაცვლება `move()` მეთოდის საშუალებით, რათა შევძლოთ მთლიანი ქუჩის გავლა. + + +შესაბამისად, მივიღებთ კოდს: +```java +while(frontIsClear()) { + move(); +} +``` + +> *როდესაც კარელის წინ აღმოჩნდება კედელი, `frontIsClear()` მეთოდი `while` ციკლს შეატყობინებს, რომ კარელის წინ დაბრკოლებაა და კარელი შეწყეტს სვლას (ციკლი დასრულდება).* +--- + +### სიარულის პარალელურად, ბრილიანტების განთავსება ქუჩაზე +ახლა, როდესაც კარელი წარმატებით გადის ქუჩას, მნიშვნელოვანია ბრილინატების განთავსება, რომელსაც `putBeeper()` მეთოდის საშუალებით გავაკეთებთ. მისი `while` ციკლში მოთავსებით კოდი მიიღებს შემდეგ სახეს +```java +while(frontIsClear()) { + putBeeper(); + move(); +} +``` +ამ ეტაპისთვის გვგონია, რომ პრობლემა გადაჭრილია და არანაირ მოდიფიკაციას არ საჭიროებს, თუმცა კოდის წარმატებით გაშვების შემდგომ მივიღებთ სამყაროს, სადაც კარელმა წარმატებით შეძლო მისი ქუჩის უჯრებზე მხოლოდ 1 ბრილიანტის განთავსება, **გარდა 1 უჯრისა.** + +ეს უჯრა, სწორედ ისაა, რომელზედაც იგი მუშაობის დასასრულს იმყოფება. მოდით, განვიხილოთ თუ რას შეიძლებოდა გამოეწვია მსგავსი სახის პრობლემა. +* ამ შეცდომას პროგრამირებაში [Off-By-One Error](https://stackoverflow.com/questions/2939869/what-is-an-off-by-one-error-and-how-do-i-fix-it)-ად მოიხსენიებენ და თავისი სახელიდან გამომდინარე მივხვდებით, რომ შეცდომა ძირითადად **იტერირების** დროს შეიძლება წარმოიშვას. +* განვიხილოთ კარელის შემთხვევა (სამყარო 10x10-ზე) + * კარელი დგას მე-9 უჯრაზე. + * დავდეთ ბრილიანტი. + * გამოვიძახეთ `move()` მეთოდი და გადავინაცვლეთ მე-10 უჯრაზე. + * მე-10 უჯრაზე გადასვლის შემდგომ კარელი ამოწმებს, აქვს თუ არა მას წინაღობა. იგი იძახებს`frontIsClear()` მეთოდს, რაზეც იგი იღებს პასუხს, რომ მის წინ მდებარეობს კედელი. შესაბამისად, კარელი ასრულებს `while` ციკლს და ვეღარ ახერხებს `putBeeper()` მეთოდის გამოძახებას. + +![Image Of Karel Standing on 10th Box](/problem-set/images/XQRF1oc.png) + +ამ ყველაფრის შემდგომ, პრობლემის გადასაჭრელად, დაგვჭირდება `putBeeper()` მეთოდის ჩამატება ჩვენს კოდში: +```java +while(frontIsClear()) { + putBeeper(); + move(); +} +// დებს ბრილიანტს, ბოლო უჯრაზე +putBeeper(); +``` +--- + +## რატომ იმუშავებს კოდი ნებისმიერი სამყაროს ზომისათვის? +* განვიხილოთ სამყარო, რომლის სიგრძეც 1-ის ტოლია (სიმაღლეს მნიშვნელობა არ აქვს, ვინაიდან კარელი მოძრაობს მარცხნიდან მარჯვნივ, მხოლოდ). + * ჩვენს მიერ დაწერილი კოდი, პირველ რიგში შეამოწმებს, არის თუ არა კარელის წინ კედელი. + * `frontIsClear()` მეთოდი დააბრუნებს პასუხს, რომ კარელს გზა დაბლოკილი აქვს და ვერ შეძლებს წინ წასვლას, შესაბამისად კარელი გამოტოვებს `while` ციკლს + * კარელი გადაინაცვლებს `while` ციკლის შემდგომ ბრძანებაზე და დახვდება `putBeeper()` მეთოდი, რაც უზრუნველყოფს ბრილიანტის წარმატებით დადებას სამყაროში. შესაბამისად, ჩვენი პრობლემაც გადაჭრილია და კარელი იდგება ქუჩაზე, რომლის ყველა უჯრაზე მხოლოდ 1 ბრილიანტი დევს + + ![Karel in 1x10 World](/problem-set/images/RwZB99V.png) + +* ნებისმიერი სხვა სამყაროსთვის, რომლის სიგრძეც > 1, ზუსტად იგივე ლოგიკით იმუშავებს, როგორც ზემოთხსენებული 10x10 სამყაროზე იმიტომ, რომ კარელი ამოწმებს წინ არსებულ დაბრკოლებას და მხოლოდ ამის შემდგომ იღებს გადაწყვეტილებას, გააგრძელოს თუ დაასრულოს სვლა. + +--- + +## შესაძლო ხარვეზები ამოხსნის იმპლემენტაციისას +პირველ რიგში, მინდა მოგილოცოთ პროგრამის წარმატებით დასრულება და იმპლემენტაცია, თუმცა არსებობს 2 დეტალი, რისი გათვალისწინებაც საკმაოდ მნიშვნელოვანია ამ ამოცანის გადაჭრისას + +1. გასათვალისწინებელია ზემოთ ნახსენები [Off-By-One Error](https://stackoverflow.com/questions/2939869/what-is-an-off-by-one-error-and-how-do-i-fix-it) +2. ასევე, საინტერესოა, რა მოხდება თუ `while` ციკლის ტანში ადგილებს გავუცვლით `move()`-სა და `putBeeper()` მეთოდებს. ერთი შეხედვით თითქოს ისევ სწორად უნდა იმუშაოს პროგრამამ, ვინაიდან ჩვენ მხოლოდ 2 ბრძანებას გავუცვალეთ ადგილი. + 1. მივიღებთ კოდს + ```java + while(frontIsClear()) { + move(); + putBeeper(); + } + putBeeper(); + ``` + 2. თუ კარგად დავაკვირდებით და გავიაზრებთ კოდს, შევამჩნევთ, რომ კარელის ქმედებები შეიცვლება. + * კოდის გაშვების შემდეგ კარელი ჯერ გადაინაცვლებს მეორე უჯრაზე, ხოლო ამის შემდეგ დადებს ბრილიანტს. + * თავისთავად ჩვენს კოდში წარმოიშვა ხარვეზი, რაც მოიაზრებს შემდეგს: კარელის სამყაროში პირველი უჯრა ყოველთვის ცარიელი დარჩება, ხოლო დამთავრების ადგილას იგივე განათავსებს 2 ბრილინატს. + + ![Karel in 1x10 World](/problem-set/images/karel_bug_0.png) + + 3. ამ პრობლემას 1 მარტივი გადაჭრის გზა აქვს. + 1. ავიტანოთ `while` ციკლის გარეთ მყოფი `putBeeper()` მეთოდი და გამოვიძახოთ `while` ციკლის გამოძახებამდე. + 2. ახალი კოდი + ```java + putBeeper(); + while(frontIsClear()) { + move(); + putBeeper(); + } + ``` \ No newline at end of file diff --git a/problem-set/Pencil.java b/problem-set/Pencil.java new file mode 100644 index 0000000..9b709cd --- /dev/null +++ b/problem-set/Pencil.java @@ -0,0 +1,32 @@ +/* + * File: Pencil.java + * ---------------------------- + * ამოცანის პირობა: + * გააკეთეთ Paint-ის ფანქრის ანალოგიური ხელსაწყო. კერძოდ, მაუსის ყოველ მოძრაობაზე + * ეკრანზე დაამატეთ გაფერადებული წრეწირები იმავე წერტილში სადაც მაუსი მდებარეობს. + */ + +import acm.graphics.*; +import acm.program.*; +import java.awt.*; +import java.awt.event.MouseEvent; + +public class Target extends GraphicsProgram +{ + private static final int DIAMETER = 50; + public void init() { + addMouseListeners(); + } + + public void run() + { + + } + public void mouseMoved(MouseEvent e) { + GOval oval = new GOval(e.getX(),e.getY(),DIAMETER,DIAMETER); + oval.setFillColor(Color.BLUE); + oval.setFilled(true); + add(oval); + } + +} \ No newline at end of file diff --git a/problem-set/Pencil.md b/problem-set/Pencil.md new file mode 100644 index 0000000..c76d2c1 --- /dev/null +++ b/problem-set/Pencil.md @@ -0,0 +1,37 @@ +# Pencil + +პრობლემა: +``` +გააკეთეთ Paint-ის ფანქრის ანალოგიური ხელსაწყო. კერძოდ, მაუსის ყოველ მოძრაობაზე +ეკრანზე დაამატეთ გაფერადებული წრეწირები იმავე წერტილში სადაც მაუსი მდებარეობს. +``` + + + +## პრობლემის გადაჭრის გზა: +ამოცანის ამოსახსნელად გამოვიყენებთ Event-ებს: + *mouseMoved() მეთოდში დავაიმპლემენტირებთ ჩვენი წრეწირის დახატვის ლოგიკას + +--- + +### I. ინიციალიზაციის ეტაპი: + * ვიდრე გამოვიყენებდეთ mouse event-ების მეთოდებს, საჭიროა, რომ პროგრამამ ყურადღება მიაქციოს, ანუ "მოუსმინოს" ჩვენს mouse event-ებს, ამისათვის init()-მეთოდში, რომელიც ერთხელ გამოიძახება run() მეთოდამდე, რათა გარკვეულ ველებს ინიციალიზება გავუკეთოთ, ჩავწეროთ addMouseListeners(), რომ ამის შემდგომ პროგრამას mouse-ის event-ებზე რეაგირება მოახდინოს. + +### II. რომელ mouse event-ებს გამოვიყენებთ: + * რადგან მაუსის ყოველ მოძრაობაზე უნდა დავხატოთ წრეწირი, ამიტომ დავწეროთ წრეწირის დახატვის ლოგიკა mouseMoved() მეთოდში, mouseMoved()-ის გარდა გვაქვს mousePressed() (სრულდება ერთხელ, მაუსის დაჭერის მომენტში), mouseClicked()(სრულდება, მაუსის დაჭერის შემდგომ აშვების შემდეგ), mouseDragged()(სრულდება, როდესაც მაუსი დაჭერილი გვაქვს და თან ვამოძრავებთ, ყოველი მოძრაობისას ხელახლა იძახება ეს მეთოდი), mouseReleased()(სრულდება, როდესაც მაუსს ვუშვებთ). + +### III. MouseEvent კლასი : + * ჩამოთვლილი mouse event-ების მეთოდებს ყველას გადაეცემა პარამეტრად MouseEvent კლასის ტიპის ობიექტი e (სახელი ნებისმიერია). ამ ობიექტის მეშვეობით შეგვიძლია მივიღოთ მრავალი ტიპის ინფორმაცია მომხდარ event-ზე, მაგალითად, თუ რომელ კოორდინატებზე მოხდა ის(e.getX() და e.getY()), რა ობიექტზე მოხდა ეს event (e.getSource(), რომელიც ჩვენ შემთხვევაში კანვასს დაგვიბრუნებს, რადგანაც მაუსს კანვასზე ვამოძრავებთ), დრო თუ როდის მოხდა ეს event(e.getWhen(), რომელიც გვიბრუნებს განვლილ დროს მილიწამებში 1970წლის 1 იანვრის 00:00UTC) და ა.შ. + +### IV. მაუსის გამოძრავებისას წრეწირების დახატვა : + + * როგორც ვიცით, mouseMoved() მეთოდი მაუსის ყოველი გამოძრავებისას იძახება, ამიტომ ჩვენი მთლიანი ამოცანა დავიდა იმაზე, რომ ამ მეთოდში უბრალოდ წრეწირი დავხატოთ და ამის შემდგომ მაუსის ყოველ გამოძრავებაზე ხელახლა დაიხატება წრეწირი. ვქმნით GOval ტიპის ობიექტს, რომელსაც კოორდინატებად გადავცემთ e.getX() და e.getY() მეთოდებს, ხოლო დიამეტრისათვის გლობალურად აღვწეროთ ცვლადი DIAMETER და ის გადავცეთ. იმისათვის, რომ უკეთ გამოჩნდეს ჩვენი დახატული წრეწირები, გავხადოთ იგი ლურჯი ფერის, ამისათვის გამოვიყენოთ setFillColor() მეთოდი და პარამეტრად გადავცეთ Color.BLUE, და არ დაგვავიწყდეს setFilled(true)-ს დაწერა, რათა გაფერადებული იყოს ჩვენი წრეწირი, შედეგად მივიღებთ კოდს : + +```java +public void mouseMoved(MouseEvent e) { + GOval oval = new GOval(e.getX(),e.getY(),DIAMETER,DIAMETER); + oval.setFillColor(Color.BLUE); + oval.setFilled(true); + add(oval); +} +``` \ No newline at end of file diff --git a/problem-set/RemoveDuplicated.java b/problem-set/RemoveDuplicated.java new file mode 100644 index 0000000..9f1a776 --- /dev/null +++ b/problem-set/RemoveDuplicated.java @@ -0,0 +1,46 @@ +/* + * File: RemoveDuplicated.java + * --------------------------------- + * დაწერეთ მეთოდი რომელიც გადაცემული ტექსტიდან შლის დუბლირებულ სიმბოლოებს, + * ანუ თუ ტექსტიში რომელიმე სიმბოლო მეორდება, უნდა დარჩეს მხოლოდ ყველაზე მარცხენა. + */ +import java.util.ArrayList; +import java.util.List; + +import acm.program.*; + + +public class RemoveDuplicated extends ConsoleProgram { + + public void run() { + String str = readLine("Enter string: "); + str = removeDuplicates(str); + println(str); + } + + // returns new string with no duplicates (leaves the left most dupliate) + private String removeDuplicates(String str) { + List usedChars = new ArrayList(); + String res = ""; + for(int i = 0; i ls, char ch) { + for(int i = 0; i usedChars = new ArrayList(); +String res = ""; +``` + +### რატომ Character და არა char? : + + * დავაკვირდეთ, რომ usedChars არის List - ინტერფეისის ტიპის, რომელსაც თემფლეით არგუმენტი აქვს Character და არა სტანდარტული char. Character არის უბრალოდ კლასი, რომელსაც ერთი field გააჩნია char ტიპის. და მიზეზი, თუ რატომ ვიყენებ Character-ს არის ის, რომ თემფლეით არგუმენტი უნდა იყოს reference type განსხვავებით პრიმიტიული valuetype char ტიპისგან. + + +### რატომ List და არა ArrayList? : + * უნდა ითქვას, რომ ამ კონკრეტული ამოცანისთვის, მნიშვნელობა არ ექნებოდა List-ის მაგივრად ArrayList რომ დაგვეწერა, თუმცა უნდა გავითვალისწინოთ, რომ კოდის გადაკეთება რომ მოგვიწიოს, მისი შეცვლა მარტივად უნდა შეგვეძლოს (რამდენადაც ეს შესაძლებელია) ამიტომ ყოველი ობიექტისთვის უმჯოებისა გამოვიყენოთ მისი ის აბსტრაქცია, რომელიც ფარავს ჩვენს მიერ მოთხოვნილ ფუნქცინოლას, ამ შემთხვევაში ArrayList ის ინტერფეისი გახლავთ List და ვიცით, რომ ყოველი კლასი რომელიც იშვილებს ამ List ინტერფეისს ექნება მისი ფუნქციონალი, ხოლო რადგან ჩვენs კოლექციაში მხოლოდ ელემენტის ჩამატება და ინდექსით ამოღება გვსურს, List ინტერფეისი გვთავაზობს ამ ყველაფერს, ამიტომაც usedChars კოლექცია გავხადეთ List ინტერფეის ტიპის, ამის შემდეგ ჩვენი კოდისთვის არ აქვს მნიშვნელობა ArrayList იქნება usedChars კოლექცია თუ Vector ტიპის, უბრალოდ ჩავანაცვლებთ ჩვენი სასურველი კლასით, რომელიც რეალიზებს List ინტერფეისს და კოდი იმუშავებს სწორად, მეტი ჩანაცვლების გაკეთება არსად არ დაგვჭირდება. + +### სიმბოლოების დამატება res ცვლადში: + * იმისათვის, რომ სიმბოლოები დავამატოთ, უნდა გადავუყვეთ ჩვენს მიერ მოცემულ სტრინგს და სათითაოდ ვამატოთ ყველა სიმბოლო, ოღონდ მხოლოდ და მხოლოდ იმ შემთხვევაში, თუკი ეს სიმბოლო უკვე არ გამოგვიყენებია, ანუ არ არის დუბლირებული. ამიტომ for-ის ყოველ იტერაციაზე უნდა შევამოწმოთ არის თუ არა ეს სიმბოლო ჩვენს მიერ გამოყენებულ სიმბოლოთა მასივში, რისთვისაც დაგვეხმარება ჩვენი მეთოდი contains + +### contains მეთოდი: + * ამ მეთოდს გადაეცემა ორი არგუმენტი, List და char ტიპების. ყურადღება მივაქციოთ, რომ ვიყენებთ ისევ List ინტერფეისის ტიპს და არა რაიმე კონკრეტულ კლასს, რათა აბსტრაქცია შევინარჩუნოთ. მეთოდში გადავუყვებით გადმოცემულ მასივს და ვამოწმებთ ყოველ ელემენტს, ტოლი არის თუ არა იგი გადმოცემული ch ცვლადის და თუ კი, მეთოდი აბრუნებს true-ს და წყვეტს მუშაობას. თუკი for ლუპი დამთავრდა, ესეიგი true არ დაბრუნდა და არცერთი ელემენტი არ იყო ch-ის ტოლი, ამიტომ ვაბრუნებთ false-ს. მივიღეთ შემდეგი კოდი : +```java +for(int i = 0; i - ის მაგივრად List გამოგვეყენებინა და ამ შემთხვევაში contains მეთოდში, char-ზე შედარებისას მოგვიწევდა ch+"" სინტაქსის გამოყენება, ამიტომ გვექნებოდა : if(ls.get(i) == ch+"") { ... }, თუმცა ეს კოდი არ იმუშავებდა რადგან String ცვლადები მათი მნიშვნელობებით უნდა შევადაროთ, წინააღმდეგ შემთხვევაში == შეადარებდა მათ ობიექტებს, ამიტომ უნდა გამოვიყენოთ .equals() მეთოდი, ამიტომ იმისათვის, რომ კოდს სწორად ემუშავა უნდა გამოგვეყენებინა +```java +if(ls.get(i).equals(ch+"")) {...} +``` + + +### ალტერნატიული ამოხსნა : + * მოცემული ამოცანის ამოხსნა შეგვეძლო კოლექციებისა და დამხმარე მეთოდის (ჩვენი contains-ის) გამოყენების გარეშეც. ამის შესაძლებლობას გვაძლევს უკვე დაწერილი contains მეთოდი, რომლის გამოძახებაც შეგვიძლია String ტიპის ობიექტზე, ანუ ისევ და ისევ გვექნებოდა String res ცვლადი და როდესაც შემოწმება დაგვჭირდებოდა გამოყენებული არის თუ არა უკვე კონკრეტული სიმბოლო, გამოვიძახებდით res.contains() მეთოდს, ოღონდ ამ მეთოდს პარამეტრად გადაეცემა String ტიპის ობიექტი, ამიტომ მოგვიწევდა დაკასტვა char-ის String-ზე, რასაც ch+""-ის მეშვეობით შევძლებდით, ამიტომ გვექნებოდა შემდეგი კოდი : +```java +String res = ""; +for(int i = 0; i 0){ + for(int i = 1; i < num;i++){ + sum = sum + i; + } + break; + } + } + + println( "Sum of natural numbers untill n is : " + sum); + } + +} \ No newline at end of file diff --git a/problem-set/Sum1N.md b/problem-set/Sum1N.md new file mode 100644 index 0000000..0a9046e --- /dev/null +++ b/problem-set/Sum1N.md @@ -0,0 +1,90 @@ +# Sum1N + +პრობლემა: +``` +მომხმარებელს შეყავს მთელი რიცხვი n, პროგრამამ უნდა დაბეჭდოს 1 დან n მდე რიცხვების +ჯამი +``` + + + +## პრობლემის გადაჭრის გზა: +ამოცანის ამოსახსნელად, საჭიროა, 3 ეტაპი: + *მომხმარებელმა უნდა შემოიტანოს რიცხვი n; + *პროგრამამ უნდა დათვალოს ჯამი 1 დან n მდე ნატურალური რიცხვებისა. + *პროგრამამ უნდა დაპრინტოს ინფორმაცია კონსოლში. + +--- + +### I.მომხმარებლის მიერ შემოტანილი ინფორმაციის წაკითხვა: + * მომხმარებელმა კონსოლში უნდა შემოიტანოს რიცხვი n, რასაც ვაკეთებთ readInt() მეთოდის მეშვეობით. იმისათვის, რომ ჩვენს კონსოლის პროგრამას გასაგები ინტერფეისი ჰქონდეს, readInt()-მეთოდს ფრჩხილებში გადავცემთ არგუმენტს, ანუ იმას თუ რა უნდა დაპრინტოს პროგრამამ, ვიდრე მომხმარებელი შემოიყვანს რიცხვს (n-ს). საბოლოოდ მივიღებთ readInt("Enter n: "); სინტაქსს. readInt მეთოდი გვიბრუნებს იმ რიცხვის (int-ტიპის) მნიშვნელობას, რომელიც მომხმარებელმა შემოიტანა და, შესაბამისად, ვინახავთ მას num ცვლადში. შესაბამისად, მივიღეთ კოდი : int num = readInt("Enter n: "); + +### II.ჯამის დათვლა 1 დან n მდე ნატურალური რიცხვებისა: + + * შეგვიძლია პირდაპირ გადავუყვეთ თითოეულ რიცხვს 1 დან n-მდე და რიგ-რიგობით შევკრიბოთ, ანუ თავდაპირველად შევკრიბოთ პირველი და მეორე რიცხვი, შემდეგ ეს ჯამი სადმე შევინახოთ და ამის შემდეგ ამ ჯამს დავუმატოთ მესამე რიცხვი, შემდეგ მეოთხე და ა.შ. რიცხვებს გადავუყვებით 1 დან n-მდე for-ციკლის მეშვეობით, თუმცა შეგვიძლია იგივეს გაკეთება while-ციკლითაც, ცხადია. ამიტომ გვექნება შემდეგი კოდი: +```java +... +int sum = 0; +for(int i = 1; i < num;i++){ + sum = sum + i; +} +... +``` + + + +### III. მიღებული შედეგის დაპრინტვა კონსოლში: + * კონსოლში შედეგის დასაბეჭდად, ვიყენებთ მეთოდს println()-ს, ხოლო არგუმენტად გადავცემთ დასაპრინტ მესიჯს. როგორც ვხედავთ, მესიჯი არ შეიცავს მხოლოდ გამზადებულ ტექსტს (string-ს), არამედ ჩვენს მიერ გამოყოფილ ცვლადსაც (sum-ს), იმისათვის, რომ ეს მესიჯი გავაერთიანოთ და println-მა მიიღოს ჩვენი არგუმენტი, ვიყენებთ + ოპერატორს და ჯავას კომპილატორი თვითონ ხვდება, რომ sum, რომელიც int ტიპისაა, string-ში გადაიყვანოს. ამიტომ ჩვენი მიღებული მესიჯია: "Sum of natural numbers untill n is: " + sum. ხოლო საბოლოოდ ვიღებთ შემდეგ კოდს : println("Sum of natural numbers untill n is: " + sum); ამის შემდეგ პროგრამა დაბეჭდავს 1 დან მომხმარებლის შეყვანილ რიცხვამდე ნატურალურ რიცხვთა ჯამს. + +--- + +### პრობლემა N1: n-არ არის ნატურალური: + * შევნიშნოთ, რომ ჩვენ მომხმარებლის მიერ შემოტანილი რიცხვის წასაკითხად ვიყენებთ მეთოდს readInt()- რომელიც Int-რიცხვს კითხულობს და თუკი შემოტანილი რიცხვი Int ტიპის არ აღმოჩნდა, ამ შემთხვევაში კონსოლზე დაგვიპრინტავს გაწითლებულად [Illegal numeric format] და სთხოვს მომხმარებელს, რომ თავიდან შემოიტანოს ინფორმაცია. თუმცა გვრჩება კიდევ ერთი შემთხვევა, რომ n ნამდვილად ნატურალური იყოს, ამისათვის უნდა გამოვრიცხოთ არადადებითი რიცხვები, რომლებიც Int ტიპის range-ში (ინტერვალში) შედის. ამ პრობლემის აღმოსაფხვრელად, უნდა შევამოწმოთ თუ რა მნიშვნელობა ჩაიწერა num ცვალდში და მის მიხედვით ვიმოქმედოთ. + ერთი ვარიანტია, რომ if-ის მეშვეობით შევამოწმოთ დადებითია თუ არა num-ცვლადი და თუ არ არის თავიდან ვთხოვოთ მომხმარებელს შემოიტანოს რიცხვი, და გვექნებოდა შემდეგნაირი კოდი : +```java +int num = readInt("Enter n: "); +if(num<=0){ + num = readInt("Enter n: "); +}... +``` + შევნიშნოთ, რომ თუკი მომხმარებელმა ხელმეორედ შემოიტანა არადადებითი რიცხვი, მაშინ ჩვენი პროგრამა num-ს აღარ შეამოწმებს დადებითია, თუ არა, რადგან if- ერთჯერადად იშვება, შესაბამისად, თუ გვინდა, რომ ყოველ შემოტანილ რიცხვზე შემოწმდეს num-ცვლადი მანამ სანამ არადადებითი რიცხვი არ შემოვა, უნდა გამოვიყენოთ while-ციკლი და if-ის while-თი ჩანაცვლებით მივიღებთ შემდეგ კოდს: +```java +int num = readInt("Enter n: "); +while(num<=0){ + num = readInt("Enter n: "); +}... +``` + ამჯერად კოდი იმუშავებს სწორად, თუმცა რომ დავაკვირდეთ, readInt-მეთოდი ორჯერ გვიწერია იგივე დანიშნულებით, ხოლო ამის თავიდან ასაცილებლად, იმის მაგივრად, რომ readInt()-ით წავიკითხოთ მომხმარებლის მიერ შემოტანილი რიცხვი ორჯერ, შეგვიძლია, while(true) ციკლში ვიმუშაოთ და სასურველი შედეგის მიღების შემდგომ break გავუკეთოთ. საბოლოოდ გვექნება შემდეგი კოდი: +```java +int sum = 0; +while(true){ + int num = readInt("Enter n: "); + if(num > 0){ + for(int i = 1; i < num;i++){ + sum = sum + i; + } + break; + } +}... +``` + +### გაფრთხილება : + * უნდა აღვნიშნოთ, რომ n საკმარისად დიდი რიცხვი რომ შემოიყვანოს მომხმარებელმა, და ამასობაში ჩვენ ვკრიბოთ ყველა რიცხვი 1 დან n-მდე, რადგან sum ცვლადი int ტიპისაა და გააჩნია საკუთარი საზღვრები (ანუ მაქსიმალური და მინიმალური მნიშვნელობა), შესაძლოა, გადასცდეს int ტიპის ზღვარს და პასუხად არასწორი რიცხვი მივიღოთ, ამის თავიდან ასარიდებლად, შეგვიძლია, გამოვიყენოთ int ტიპის მაგივრად სხვა უფრო დიდი ინტერვალის მქონე ტიპი, მაგალითად, long , თუმცა ისიც უნდა აღვნიშნოთ, რომ long-საც გააჩნია საკუთარი ზღვარი და გარკვეული n-თვის უკვე არც long-ში არ ჩაეტევა ჩვენი ჯამის მნიშვნელობა. (აღსანიშნავია, რომ თუ n int-ტიპისაა, მაშინ ჯამი 1 დან n-ის მაქსიმალურ შესაძლო მნიშვნელობამდე ჩაეტევა long-ში, რისი დადასტურებაც შეგვიძლია პირდაპირ მაქსიმალური მნიშვნელობების ჩასმით და long-ის მაქსიმალურ მნიშვნელობასთან შედარებით, შესაბამისად, თუ n int-ტიპისაა, მსგავს პრობლემას ვერ შევხვდებით, თუკი sum-ცვლადი long-ტიპისაა). + +## პრობლემის გადაჭრის განსხვავებული ხერხი: + * ცხადია, ჯამის დათვლა რამდენიმენაირად შეგვიძლია, თუმცა ყველაზე მარტივი და ინტუიტიური მაინც ფორმულის გამოყენებაა მათემატიკიდან. 1 დან n-მდე რიცხვების ჯამი მოიცემა ფორმულით ( n*(n+1) ) / 2 -ს, რომლის გამოყვანაც შეგვიძლია არითმეტიკული პროგრესიის ჯამის ფორმულიდან -> (A(1)+A(n))*n /2. ხოლო ჩვენ შემთხვევაში n-ის მაგივრად გვქენება num-ცვლადი და შესაბამისად მიღებულ მნიშვნელობას შევინახავთ sumFormula ცვლადში: int sumFormula = (num*(num+1)) / 2; + +## რატომ იმუშავებს კოდი ნებისმიერი n-თვის? + * პირველ რიგში, როგორც უკვე ვთქვით,საკმარისად დიდი n-თვის კოდი არ იმუშავებს, რადგან მიღებულ ჯამს ვინახავთ int - ტიპის ცვლადში და int ტიპის მაქსიმალური მნიშვნელობის გადაცილების შემდგომ, ცხადია, მივიღებთ არასწორ ჯამს. ამ პრობლემას ვერ გადავჭრით, მაქსიმუმ რაც შეგვიძლია, რომ გავაკეთოთ, არის უფრო დიდი ინტერვალის მქონე ტიპში შევინახოთ, თუმცა ცხადია, ამ ტიპისთვისაც იარსებებს საკმარისად დიდი n-რომელიც გადააჭარბებს მის მაქსიმალურ მნიშვნელობასაც, ამიტომ სიმარტივისთვის ვინახავთ int ტიპში და მხოლოდ და მხოლოდ იმ შემთხვევაში გამოვიყენებთ სხვა ტიპს თუ მართლაც დიდ რიცხვებზე გვიწევს მუშაობა. თვითონ ფორმულის გამოყვანა, როგორც ვნახეთ, შეგვიძლია არითმეტიკული პროგრესიის ჯამის მიხედვით, (A(1)+A(n))*n/2 , ჩვენი არითმეტიკული პროგრესია შემდეგი ტიპისაა : 1,2,3....n, შესაბამისად, A(1)=1 და A(n) = n, ეს მნიშნველობები რომ ჩავსვათ არითმეტიკული პროგრესიის ჯამის ფორმულაში მივიღებთ, რომ ჯამი 1 დან n-მდე(ჩათვლით) ნატურალური რიცხვებისა უდრის (n*(n+1))/2, შესაბამისად ფორმულა მართებულია ნებისმიერი ნატურალური n-სთვის. + +### გაფრთხილება : + * რომ დავაკვირდეთ, while-ციკლის შემდგომ მე-13 ხაზზე გვიწერია შემდეგი კოდი ( num*(num+1) ) / 2; გარეთა ფრჩხილები მეტი გასაგებობისთვისაა გამოყენებული, მიუხედავად იმისა, რომ გარეთა ფრჩხილების მოცილებით კოდი მაინც სწორად იმუშავებს. თუმცა უნდა აღინიშნოს, რომ მათემატიკაში არ აქვს მნიშვნელობა 2-ზე გაყოფას სად დავწერდით და მთლიან ნამრალვს გავყოფდით 2-ზე, თუ ერთ-ერთ რომელიმე წევრს, თუმცა ჯავაში (და ბევრ სხვა ენაში თუ ყველაში არა) ამას მნიშვნელობა აქვს, რადგან რომ დაგვეწერა შემდეგი გამოსახულება num/2 * (num+1)-ზე კენტი num-თვის პროგრამა არ იმუშავებდა რადგან დავუშვათ num=11-ის შემთხვევაში num/2=5 5.5-ის მაგივრად, რადგან ხდება int ტიპზე ოპერაცია, პასუხადაც int ტიპს ვიღებთ, ანუ 5-ს, პროგრამა იმ შემთხვევაში იმუშავებდა სწორად, თუკი ათწილადებსაც შემოვიღებდით num-ის შესაძლო მნიშვნელობებში, მაგალითად double-ს, ხოლო რადგანაც int ტიპებში ვმუშაობთ, ამიტომ მთლიან ნამრავლს ვყოფთ 2-ზე (შევნიშნოთ, რომ num*(num+1) ორი მომდევნო ნატურალური რიცხვის ნამრავლია, ამიტომ აუცილებლად ლუწი იქნება და პასუხი int ტიპისაა). + +### რატომ არის მეორე გზა პირველზე უმჯობესი? + * იმიტომ, რომ პირველი ტიპის ამოხსნას რომ დავაკვირდეთ გვიწევს num ჯერ დავუმატოთ sum- ცვლადს i-ური რიცხვი, განსხვავებით ჩვენი მეორე ამოხსნისგან, სადაც მხოლოდ და მხოლოდ 1 ოპერაციაში (მათემატიკურად 3 ოპერაციაში, ჯამი, შემდეგ ნამრავლი და ბოლოს გაყოფა ორზე) ვიგებთ საბოლოო ჯამს და მიუხედავად იმისა, რომ პატარა num-თვის პროგრამა მაინც საკმაოდ სწრაფად მუშაობს, დაწყებული გარკვეული num-დან ჩვენს პროგრამას საგრძნობლად დიდი უპირატესობა ექნება დროში (ცხადია, თუ გავითვალისწინებთ იმას, რომ num არ გაცდეს შესაბამისი ტიპის საზღვრებს). + +### აღნიშვნა: + * ალბათ, შეამჩნევდით, რომ მიღებული ჯამის sumFormula-ცვლადში შენახვა არ არის საჭირო და შეგვეძლო პირდაპირ println() მეთოდში გადაგვეცა ის პლუსის შემდგომ, თუმცა იმისათვის, რომ კოდი უფრო წაკითხვადი იყოს, ვინახავთ sumFormula-ცვლადში, ამის შემდეგ, რომ დავხედოთ println()-მეთოდში ჩაწერილ არგუმენტს, ვხვდებით თუ რას აკეთებს ჩვენი პროგრამა, განსხვავებით იმ შემთხვევისგან, თუკი გვექნებოდა შემდეგი კოდი : +```java +println("Sum of natural numbers untill n is: " + (num*(num+1))/2); +``` \ No newline at end of file diff --git a/problem-set/images/RwZB99V.png b/problem-set/images/RwZB99V.png index 6b07f04..bbf21ca 100644 Binary files a/problem-set/images/RwZB99V.png and b/problem-set/images/RwZB99V.png differ diff --git a/problem-set/images/XQRF1oc.png b/problem-set/images/XQRF1oc.png index 37d3447..b697725 100644 Binary files a/problem-set/images/XQRF1oc.png and b/problem-set/images/XQRF1oc.png differ diff --git a/problem-set/images/karel_bug_0.png b/problem-set/images/karel_bug_0.png index 66ad2e9..c01ea35 100644 Binary files a/problem-set/images/karel_bug_0.png and b/problem-set/images/karel_bug_0.png differ