Skip to content

03. კარელი - სავარჯიშოები

6.1.4: Decorate the Fence

კარელი ემზადება ჰელოუინისთვის და ალაგებს ჯეკის ფარნებს ისე, რომ ფარანი იყოს კედლის გვერდით, არ უნდა ბლოკავდეს კარსა და ფანჯარას. დაეხმარე კარელს ასეთი ადგილების მოძებნასა და ფარნების განთავსებაში.

კარელი თავდაპირველად დგას ქვედა მარცხენა კუთხეში და იყურება აღმოსავლეთისკენ. საბოლოოდ კი უნდა აღმოჩნდეს ყველაზე ზემოთ მდებარე კედელთან და იყურებოდეს ჩრდილოეთისკენ.

1. Top Level

მოდი ყველაზე ზედა დონის დეკომპოზიცია გავაკეთოთ და ამოცანის რეალური პრობლემა გამოვყოთ.

function start() {
    goToFence();
    turnLeft();
    decorateFence();
}


function goToFence() {
    while (frontIsClear()) {
        move();
    }
}

ახლა ჩვენი ამოცანაა decorateFence() ფუნქციის დაწერა. ამ ფუნქციის დასაწყისში კარელი ჩრდილოეთით იყურება და ღობე, რომელიც უნდა შეამოწმოს, მის მარჯვნივაა.

2. simple solution

დასაწყისისთვის უბრალოდ ახალი ფუნქციის გამოყენება დავტესტოთ ერთ უჯრაზე.

function decorateFence() {
    if (rightIsBlocked()) {
        putBall();
    }
}

3. while rightIsBlocked

ინტუიციამ შეიძლება გვიკარნახოს, რომ if-ის მაგივრად while ჩავწეროთ, ერთი უჯრით გადასვლა დავამატოთ ყოველ ციკლზე და შეიძლება ეს საკმარისი იყოს ამოხსნისთვის. რა მოხდება კოდის გაშვების შემთხვევაში?

function decorateFence() {
    while (rightIsBlocked()) {
        putBall();
        move();
    }
    putBall();
}
ამ ამოხსნაში OBOB-ის ასარიდებლად მეორე putBall() არ დაგვავიწყდა, მაგრამ სამაგიეროდ კარელი პირობას შეცდომით ასრულებს. შეგიძლია, ორი პრობლემა გამოყო?

პასუხის ჩვენება 1. while-ის შემდგომ putBall()-ის დროს არ ვაკეთებთ rightIsBlocked() პირობის შემოწმებას 2. while ციკლი პირველივე უღობო უჯრაზე გაჩერდება და არ გააგრძელებს

4. ამოხსნა

როგორც ჩანს, სხვა გზის პოვნაა საჭირო. დავუბრუნდეთ ისევ ერთი უჯრის იმპლემენტაციას. რეალურად, ერთ უჯრაზე სწორად ვაკეთებთ ყველაფერს, და ეს რაღაც ქუჩის ყველა უჯრაზე უნდა გავაკეთოთ. ყველა უჯრაზე გაკეთების "პატერნი" უკვე ნასწავლი გვაქვს (ყველა უჯრაზე გადასვლა, ყველა უჯრაზე ბურთის დადება)

function decorateFence() {
    while (frontIsClear()) {
        decorateSingle();
        move();
    }
    decorateSingle();
}

function decorateSingle() {
    if (rightIsBlocked()) {
        putBall();
    }     
}

5. ალტერნატიული ამოხსნა

ახლა სხვა გზით წავიდეთ და if/else გამოვიყენოთ

function decorateFence() {
    while (frontIsClear()) {
        if (rightIsBlocked()) {
            putBall();
            move();
        } else {
            move();
        }
    }    
}
ზოგისთვის შეიძლება ასე დაწერა უფრო ლოგიკური იყოს - "თუ მარჯვნივ დაბლოკილია, ბურთი დადე და გადადი, თუ არა და მხოლოდ გადადი". რა თქმა უნდა, თუ შენთვის მოსახერხებელია, შეგიძლიათ ასე დაწერო. ან კიდევ ერთი ვარიანტია - დაწერო თავიდან ისე, როგორც შენთვის მოსახერხებელია და შემდეგ დაფიქრდე კოდის გაუმჯობესებაზე. რა მინუსი აქვს ამ ამოხსნას? move() ორივე შემთხვევაში ხდება (თან ბოლოს) და კოდი ეკვივალენტური დარჩება, თუ if/else-ის გარეთ გავიტანთ.

while (frontIsClear()) {
        if (rightIsBlocked()) {
            putBall();
        } else {
        }
        move();
    }   

ასეთ შემთხვევაში else აღარ არის საჭირო, მასაც რომ წაშლი, წინა ნაწილში დაწერილ ფუნქციას მიიღებ :) ოღონდ ამჯერად decorateSingle() ფუნქციაში გატანის მაგივრად, იქვე წერია ის ორი ხაზი. ამიტომ, უფრო "სუფთა" ამოხსნა შეგეძლო პირდაპირ მოგეფიქრებინა - ან შენთვის უფრო კომფორტულით დაგეწყო და შემდეგ გაგეუმჯობესებინა

7.1.4: Random Hurdles

დაწერე პროგრამა, რომელიც კარელს წაიყვანს პირველი ქუჩის ბოლოში. გაითვალისწინეთ, რომ კარელს რანდომ ლოკაციებზე შეხვდება დაბრკოლებები, რომლებსაც უნდა გადაახტეს. (კარელი უნდა ახტეს მხოლოდ და მხოლოდ მაშინ, როდესაც დაბრკოლება შეხვდება)

კარელი თავდაპირველად დგას ქვედა მარცხენა კუთხეში და იყურება აღმოსავლეთისკენ. საბოლოოდ კი უნდა აღმოჩნდეს ქვედა მარჯვენა კუთხეში და იყურებოდეს აღმოსავლეთისკენ. სამყაროს სიგრძე ყოველთვის 14-ია. აუცილებელია დაწერო ფუნქცია სახელად jumHurdle()

1.

ამ პირობის ჩაღრმავებას სანამ დავიწყებდეთ, სცადე ერთი წინაღობის გადახტომისთვის კოდის დაწერა.

function jump() {
    turnLeft();
    move();
    turnRight();
    move();
    turnRight();
    move();
    turnLeft();
}

ახლა უნდა მოვიფიქროთ, რა პირობა შევამოწმოთ. რა მოხდება, თუ ბევრს არ ვიფიქრებთ და უბრალოდ ქუჩის ბოლომდე გასვლის "პატერნს" გამოვიყენებთ?

while (frontIsClear()) {
    jump()
}

შეიძლება უკვე მიხვდით რა არის ამ პირობის მთავარი სირთულე - ჩვეულებრივ წინ დაბლოკვის შემოწმებას ვიყენებთ ხოლმე ქუჩის ბოლომდე გასვლისთვის, ახლა კი ეს არ გამოდგება.

2.

მოდი სხვა გზით წავიდეთ. ჯერ გამოკვეთილი არ არის, რა მოგვიგვარებს ამ პრობლემას, მაგრამ უბრალოდ ვცადოთ დასრულებაზე ფიქრის გარეშე ამუშავება. ასეთ სამყაროში მოძრაობის მარტივი ალგორითმი ასე წარმომიდგენია - ყოველ ჯერზე ვიპოვოთ შემდეგი წინაღობა და გადავახტეთ მას.

დააკვირდით, რომ findNextHurdle() ფუნქცია რეალურად ქუჩის ბოლომდე გასვლის ფუნქციაა, მაგრამ ამ ამოცხსნაში უფრო მარტივი აღსაქმელი რომ იყოს მისი როლი, სახელი შევუცვალე (ისევე, როგორც goToFence()-ს წინა ამოცანაში)

findNextHurdle();
jump();
while(frontIsClear()) {
    findNextHurdle();
    jump();
}

function findNextHurdle() {
    while (frontIsClear()) {
        move();
    }
}

სანამ კოდს გაუშვებდეთ, შეეცადე წარმოიდგინოთ, რა მოხდება - ყველაფერი მზადაა თუ კარელი რამეს დაეჯახება, თუ უსასრულო ციკლში გაიჭედება?

დამატებითი კითხვის ჩვენება შეგიძლია თქვა, რომელმა ხაზმა გამოიწვია შეჯახება?

3.

ყოველთვის, როდესაც კარელი რამეს ეჯახება, ნიშნავს, რომ move() გამოვიძახეთ შემოწმების გარეშე. საჭიროა ყოველთვის შევამოწმოთ გადასვლის წინ? რა თქმა უნდა არა, მაგალითად jump()-ში ვიცით, რომ ზემოთ ასვლისას (პირველი move()) და უკან დაბრუნებისას (მესამე move()) წინ თავისუფალი უჯრაა, რადგან ღობეები მხოლოდ წინ არის და ამ დროს ზემოთ/ქვემოთ ვართ შეტრიალებული). თუმცა, იგივე არ ითქმის მეორე move()-ზე. შეიძლება შეცდომაში იმან შეგვიყვანოს, რომ jump()-ის გამოძახებამდე while-ში ვამოწმებთ, არის თუ არა წინ თავისუფალი, მაგრამ ამ შემოწმების შემდეგ findNextHurdle()-ს ვიძახებთ და კიდევ რამდენიმე ხაზია.

ზოგადად, დაიმახსოვრეთ, რომ თუ შემოწმების მიუხედავად move()-ზე მაინც ერორი ხდება, ე.ი ან სხვა move() გამოიძახეთ მანამდე, ან მიმართულება შეიცვალეთ.

ამიტომ, დავამატოთ საჭირო შემოწმება და ვნახოთ რა მოხდება

function jump() {
    turnLeft();
    move();
    turnRight();
    if (frontIsClear()) {
        move();
    }
    turnRight();
    move();
    turnLeft();
}

რას ფიქრობ, საკმარისია ეს ამოხსნა? ნებისმიერ სამყაროში იმუშავებს? თუ უბრალოდ wifi-ს გაფუჭების გამო მობილური ინტერნეტი მითავდებოდა და სემინარი ადრე დავამთავრეთ?

4.

როდესაც კოდი ზოგიერთ სამყაროში არ მუშაობს, მნიშვნელოვანია პრობლემის მკაფიოდ ჩამოყალიბება.

  • რა ხდება? კარელი ზოგ სამყაროში შუა გზაზე ჩერდება
  • როდის ხდება? ჩერდება პირველივე ისეთ შემთხვევაზე, როდესაც ერთ ღობეს ეგრევე მოსდევს მეორე ღობე
  • რატომ ხდება? კოდის რა ნაწილში?
    • კარელი ჩერდება - ანუ ციკლი დროზე ადრე წყდება
    • პროგრამაში ორი ციკლი გვაქვს
      • findNextFence() ფუნქციის ციკლი როდესაც წყდება, კარელი მაინც აგრძელებს მთავარი ციკლის შესრულებას, ანუ ამ ფუნქციაში არაა პრობლემა
    • თუ jump()-ის შემდეგ ეგრევე ღობეა, მთავარი while (frontIsClear()) პირობა აღარ შესრულდება და კარელი დაასრულებს მუშაობას.

გამოსავალი არის სხვანაირი პირობის შემოწმება. მაგალითად, თუ გადახტომისას მეორე უჯრაზე კედელი დაგხვდა, შეგვიძლია დატრიალება და უკან წასვლა საერთოდ არ გავაკეთოთ, რა შემთხვევაშიც შემდეგ ციკლზე კარელის მიმართულება ჩრდილოეთი იქნება.

function start() {
    while(facingEast()) {
        findNextHurdle();
        jump();
    }
    // ამ ციკლის დასრულებისას კარელი 
    // არის ბოლო უჯრის მეორე რიგში,
    // იყურება ჩრდილოეთით
    turnAround();
    move();
    turnLeft();
}

function jump() {
    turnLeft();
    move();
    if (rightIsClear()) {
        turnRight();
        move();
        turnRight();
        move();
        turnLeft();
    }
}

5. მარტივი ამოხსნა

თუ სამყაროს ზომა არ იცვლება

function start() {
    for (var i = 0; i < 12; i++) {
        if (frontIsClear()) {
            move();
        } else {
            jump();
        }
    }
}