Let's Learn it Together !

ِAn educational blog from FCIS'2011 students ..

This is one of my nicest-ever experiences with programming. My cousin asked me about the 3-7-10 problem, do you know it? Most probably yes... I'll remind you. You have 3 unmetered containers with capacities 3 liters, 7 liters and 10 liters. Use them to have 5 liters in the 7-liters one and 5 liters in the 10-liters one. You're only allowed to either fill a container or evacuate it – no fractions of any container can be acquired. You have only 10 liters in the 10-liters container, the other two are vacant. No extra water is allowed.


I solved it quickly, but – as you know – just by trial and error. Then I thought about it a little bit... this is a typical backtracking problem. You proceed with one of the solutions, till it either proves correct or you find your last step irrelevant. Instead of quitting the whole solution, you just throw away your last step and choose another one.

Let's learn together ;-). Once you do a step, what can cause it to be “irrelevant”? The answer is very simple: You've reached this configuration before. A configuration is my term to describe the current state, meaning: [Container_3 contains 2 liters, Container_7 contains 4 liters, Container_10 contains 4 liters]. This is a valid configuration, since the sum of the contents is correctly 10 liters.

Note that if you do a step that reaches a configuration you've seen before, you're not advised to do it again – otherwise you'll keep looping forever, reaching the same configuration over and over again.

There's another obvious configuration that makes you stop: The winning configuration. If the next configuration is what you're seeking (0-5-5), just print the solution vector out.

What's the solution vector? I simply keep the configurations with me as I go. Whenever I advance, I add the last configuration hoping that I'll reach the correct one. Whenever I reach a dead end, I just throw that configuration away (from the solution vector) and pick another. Whenever I reach the winning configuration, I print all what I have.

An important notice: Whenever I reach the winning configuration, I print out the solution then discard the last step (though it was correct)! Why? Because I'm seeking all possible solutions, not just one. So I just go on normally – as if nothing has happened – discarding the last trial and proceeding with the next one… may be the next one will lead me to another solution.

The final thing to know is: Given a configuration we're currently in, what are the allowed steps? This is trivial... why are you asking me!? :-P

The allowed moves are – as you know: Pour Container_3 into  Container_7, Container_3 into  Container_10, Container_7 into  Container_3, Container_7 into  Container_10, Container_10 into  Container_3, or Container_10 into  Container_7. Thus, at each iteration, I try these six in order. That's all...

A final notice: Take care of overflowing while pouring. I mean, when you pour Container_7 that currently contains 4 liters (for example) into Container_3 that currently contains 1 liter, take care to leave Container_7 with 2 liters and Container_3 full. This is the meaning of using min() and max() in my solution.

I've said this was one of my nicest-ever experiences because I coded this problem, pressed Ctrl+F5, and found all the 20 solutions on the screen. No debugging, no tracing, nothing. It just worked from the first trial. Only then that I realized I've finally understood backtracking, since this was my first program to write using backtracking.

That said, my solution is not at all generic. You're encouraged to make it more generic, or – more importantly – to expand it. This idea can work for any given containers and any target configuration. I preferred to let this exercise to you. I even didn't try to optimize the code so as not to obscure it.

I'm sharing the solution with you. I've commented it as much as I can, and I'm willing to discuss with you in case it's not that clear.

#include <vector>
#include <fstream>
#include <algorithm>
using namespace std;

ofstream fout("solutions.txt");
static int counter = 1;

struct configuration {
       int contents_3, contents_7, contents_10;

       configuration(int contents_3, int contents_7, int contents_10) {
              this->contents_3 = contents_3;
              this->contents_7 = contents_7;
              this->contents_10 = contents_10;
       }

       bool const operator==(const configuration& other) const {
              return (this->contents_3 == other.contents_3 && this->contents_7 == other.contents_7 && this->contents_10 == other.contents_10);
       }
};

vector<configuration> solution;

void solve(configuration current) {
       // Firstly... have we reached the required configuration?
       if (current.contents_3 == 0 && current.contents_7 == 5 && current.contents_10 == 5) {
              solution.push_back(current); // Add it in preparation for printing the complete solution.

              // Printing the solution.
              {
                     fout << "Solution " << counter++ << ":" << endl;

                     for (vector<configuration>::const_iterator cit = solution.begin(); cit != solution.end(); cit++)
                           fout << cit->contents_3 << " " << cit->contents_7 << " " << cit->contents_10 << endl;

                     fout << endl;
              }

              solution.pop_back(); // Backtrack to search for other solutions.
       } else { // Not yet, let's continue...
              if (find(solution.begin(), solution.end(), current) != solution.end()) // Have we encountered this configuration before?
                     return; // If yes, return instantly. A configuration can never repeat, because if this happens then we are stuck in an infinite loop.

              solution.push_back(current); // Let's assume this is a correct guess...

              //================================//
              // Trying the six allowable moves //
              //================================//

              // Pouring into contents_3...
              {
                     solve(configuration( // ...contents_7.
                           min(3, current.contents_3 + current.contents_7),
                           max(0, current.contents_7 - (3 - current.contents_3)),
                            current.contents_10));

                     solve(configuration( // ...contents_10.
                           min(3, current.contents_3 + current.contents_10),
                           current.contents_7,
                           max(0, current.contents_10 - (3 - current.contents_3))));
              }

              // Pouring into contents_7...
              {
                     solve(configuration( // ...contents_3.
                           max(0, current.contents_3 - (7 - current.contents_7)),
                           min(7, current.contents_7 + current.contents_3),
                           current.contents_10));

                     solve(configuration( // ...contents_10.
                           current.contents_3,
                           min(7, current.contents_7 + current.contents_10),
                           max(0, current.contents_10 - (7 - current.contents_7))));
              }

              // Pouring into contents_10...
              {
                     solve(configuration( // ...contents_3.
                           max(0, current.contents_3 - (10 - current.contents_10)),
                           current.contents_7,
                           min(10, current.contents_10 + current.contents_3)));

                     solve(configuration( // ...contents_7.
                           current.contents_3,
                           max(0, current.contents_7 - (10 - current.contents_10)),
                           min(10, current.contents_10 + current.contents_7)));
              }

              solution.pop_back(); // Backtrack. Either this was a good guess and the solution was already output, or it was bad and thus useless. In both cases, remove it from our vector.
       }
}

int main() {
       solve(configuration(0, 0, 10));
       fout.close();
       return 0;
}

فى البداية اقرا المقال ده Recursion -- Part 1

احنا فى المرة الى فاتت اتعرضنا لموضوع الrecursionوعرفنا مع بعض يعنى ايه recursionوبنستخدمه امتى وليه وخدنا مثال عليه اللى هو الfactorial وعرفنا ان احنا ممكن نعمل البرنامج ب recursionوممكن من غيرها برضه.


وعرفنا برضه الrecursion بتتكون من ايه.
النهاردة ان شاء الله هنكمل مع بعض وناخد مثال تانى كدة نفهمه مع بعض ونشرحه بالتفصيل
قبل مانبدا لازم بس نكون عارفين شويه تعريفات كدة مهمه يعنى بخصوص موضوع ال recursion.
نبدا باول تعريــــــــــــــــــــــــــــــــــــــــــف......

  • Direct Recursion:

A function is directly recursive if it contains an explicit call to itself.

يعنى ايه الكلام ده ؟ الى نفهمه من التعريف ده ان ال recursive funcion بتاعتنا نقدر نسميها direct لو هيا بتنده نفسها جوا الfuntion طيب تعالو ناخد مثال نفهم بيه احسن عندنا مثلا ال مثال ده ...

int foo(int x)

{

if (x <= 0)

return x;

else

return foo(x - 1);

}


هنا لاقينا ان الfunction بتاعتنا ندهت نفسها وهى جوه نفسها


تانى تعريف عندنا

  • Indirect Recursion:

A function foo is indirectly recursive if it contains a call to another function which ultimately calls foo.

نقدر نقول على ال function الى عندنا انها Indirect Recursion لو لاقينا فى الfunction بتاعتنا جواها فى calling ل function تانية

int foo(int x)

{

if (x <= 0)

return x;

return bar(x);

}

int bar(int y)

{

return foo(y - 1);

}

هنا زى ماحنا شايفين فى ال 2 functions دول ان فى كل واحده جواها بتنادى على فانكشن تانيه
فى كمان تعريفين كدة انهردة نعرف بس اساميهم لكن مش هانخوض فيهم هانشرحهم ان شاء الله المرة الجايه وهما

  • Tail Recursion
  • Linear and Tree Recursion


طب تعالوا بقى ناخد مثال النهاردة ونفهمو مع بعض

المثال بتاع النهاردة حاجة اسمها Fibonacci seriesدى عبارة عن sequence بس ليها rule عشان نقدر نكونها يعنى مش اى ارقام وخلاص

عندنا فى ال sequence دى اول 10 ارقام منها هما دووول ومن خلالها نقدر نستنج القانون العام بتاعها

ال 10 ارقام اهم

0 1 1 2 3 5 8 13 21 34

الى انا ملونهم دول اول رقمين يعنى

fib[0] & fib[1]

ودول معروفين اساسا يعنى مش هانجيبهم لكن احنا هانبدا نجيب القانون عشان نجيب اللى بعد ال 1



طب اسيبكم كدة شويه تفكرو فى انكم تجيبو القانون من غير ماتبصو تحت لو مش عرفتوه بعد فترة ممكن تنزلة تبصوا عليه تحت انا كتبه يلا take your time
..........................


........................


......................


...................


...............


.........


.....


..


.

خلاص الوقت خلص نيجى بقى نفهم مع بعض

عندنا اول رقمين معروفين اللى هما 0 1 نيجى نركز شويه معلش استحملونى فى اللى بعد ال 1 نلاقيه 1 وبعد ال 1 نلاقيه 2 معنى كدة ايه....؟


خلاص انا هاقول مانا ادتكم فرصه فوق

معناها ان كل رقم = مجموع الرقمين الى قبليه يعنى عندنا اول رقمين 0 1 مجموعهم = 1

عندنا 1 1 مجموعهم 2 وهكذا .........اما نشوف مين الى فكر صح ؟؟ وجاب القانون من

غير مايبص تحت


طيب معنى كلامك ده ان ال

FIB -->Fibonacci

Fib(0) = 0

Fib(1) = 1

Fib(2) = Fib(1) + Fib(0) = 1

Fib(3) = Fib(2) + Fib(1) = 2

Fib(4) = Fib(3) + Fib(2) = 3

Fib(5) = Fib(4) + Fib(3) = 5

Fib(6) = Fib(5) + Fib(4) = 8



General rule:

Fib(n) = Fib(n-1) + Fib(n-2) for all n >=2


حاجة مهمة بقى
ممكن كل واحد قبل مايشوف الكود بتاع ال function دى يحاول يعمله هو بايده لو هو فهم المرة اللى فاتت واللى معرفش مش مشكله خالص يبص على الكود

int fib(int n) // n >= 0

{

if (n == 0)

return 0;

if (n == 1)

return 1;

return fib(n - 1) + fib(n - 2);

}

وده reference على النت

Definitions

السنه الى فاتت واحنا فى سنه اولى كان فى مجموعه من ال sessions دكتور عمر عثمان كان بيديهلنا بهدف تطوير مستوانا وتدريبنا اكتر ...... فى session منهم كان فى كلمه دكتور عمر كان عامل جنبها emotion لسه فاكره لحد دلوقتى وهى كلمه Recursion ...... ماخضناش اوى فيها السنه الى فاتت .... و السنادى الحمد لله توسعنا فيها شويه لما خدنا ال trees فى ال Data Structure شفنا اد ايه ال Recursion مهمه فى انك بدل ماتكتب كود مكون من N linesممكن من خلال ال Recursion تكتب كود صغير جدا


فانا ان شاء الله هاتكلم عن الموضوع دا بس عشان هو طويل جدا فقررت انه يتجزء على كذا مرة .



يعنى ايه Recursion ؟؟؟؟؟!!!!
كلمة Recursion بالعربى معناها معاودة
وفى البرمجة Recursion عبارة عن تكرارfunction عن طريق ان ال function دى بتنادى نفسها اكتر من مرة لحد ماحاجه معينه تتحقق فتوقفها

بتقول بتنادى نفسها اكتر من مرة !!!!! طب امتى هاتقف ؟؟


ال Recursion هيا function زى اىfunction بتيجى عند وقت معين وتخلص وتنتهى .


طب ليه بنستخدم ال Recursion؟؟؟؟
ليها فوائد كتير جدا هنبقى نتطرقلها فى مقالات تانية ... لكن الحاجة اللى ممكن نلمسها دلوقتى تقليل عدد اسطر الكود

طب سؤال!!!!!هل الكود الى بنعمله بال Recursion ماينفعش اعمله من غير Recursion ?? لا طبعا ينفع احنا اتفقنا ان ال Recursion عبارة عن function زيها زى اى function يعنى نقدر نعمل الكود بال Recursion ومن غيرها برضه ينفع


اى recursive function بتتكون من جزئين مهمين اوى

اول جزء حاجة اسمها anchor case او termination condition طبعا مفهومه من اسمها ان ده الحاله الى هتخلى ال function بتاعتنا تخلص وتنتهى يعنى عند condition معين ال function بتعمل terminate و الconditon ده احنا الى بنحطه على اساس الكود بيعمل ايه.


تانى جزء فى ال recursive functin حاجة اسمها inductive case
ودى معنااها بكل البساطه انها هى الجزء الى بيحصل قبل مالفانكشن بتاعتنا توصل للanchor case .


طب ماتيجو ناخد مثال نوضح بيه الكلام الى عمالين نقوله ده

عندنا مثال مشهور جدا وهو حساب number factorial اللى بيرمزله بالعلامة "!"

للى مايعرفش ال factorial ...
ال factorial لرقم يعنى عدد الطرق اللى نقدر نرتب بيها عدد من الاشياء"Objects" فى خط مستقيم "line" ... وطبعا مفيش عدد سالب من ال "Objects" يعنى مينفعش اقول عندى -5 حاجات مثلا .... وبالتالى factorial رقم سالب غير معرف "undefined" مش 1

طب نيجى للحالات الخاصة :
factorial ال 1 بيساوى 1 ... لأن احنا عندنا طريقة واحدة نرتب بيها حاجة واحدة
factorial ال 0 بيساوى 1 برضه .... لان احنا لو معندناش حاجة علشان نرتبها .... يبقى احنا مقدمناش غير طريقة واحدة للترتيب وهى اننا منعملش حاجة :D


وممكن نحسب ال factorial بحاصل ضرب كل الارقام من 1 لحد الرقم نفسه يعنى مثلا لو عايزين نجيب مضروب 5 يبقى يساوى
5*4*3*2*1


يعنى نلاحظ من كدة ايه ؟؟؟؟؟نلاحظ ان مضروب الرقم لو كان مثلا n-----> n*(n-1)*(n-2)*(n-3).......*3*2*1
طب خلينا نتفق ان

factorial n <= 1 is 1

طيب دلوقتى احنا قلنا ان مضروب الرقم هيساوى حاصل ضرب الارقام من 1 لغايه الرقم نفسه يعنى اذا...


1! = 1

2! = 1*2 = 2

3! = 1*2*3=6

4! = 1*2*3*4=24

5! = 1*2*3*4*5=120

..............................................and so on


طب دلوقتى عايزين نركز شويه عشان انتو الى هاتكتبو القانون دلوقتى مش انا
دلوقتى احنا قلنا ان
5! = 5*4*3*2*1
طب لو ركزنا شويه كدة وجينا بعد ال 5 لاقينا 4*3*2*1 طب دا معناها ايه ؟؟؟؟؟
مش دى معناها 4! ؟؟؟؟؟ اه صح دى معناها 4! طب معلش هارخم شويه واقولكم ركزو اكترلو جينا بعد ال 4 هانلاقى ايه ؟؟؟
هانلاقى ان 3*2*1 دى سهله بقى انتو عرفتوها دى مضروب3 وهكذا طب نفهم من كدةا يه؟؟؟؟؟ نفهم ان مضروب الرقم = الرقم نفسه مضروب فى مضروب الى قبليه يعنى 5!=5*4! وهكذا .....

طب نيجى بقى نكتب القاعدة بتاعتنا عشان نرتب افكارنا..........

fact(N)=1 IF (N=1)

Fact(N)=N*Fact(N-1)=N*(N-1)*Fact(n-2).....*3*2*1 IF (N>1)

طب دلوقتى لما جينا وقلنا لو ال الرقم يساوى 1 يبقى ال factorial = 1

لكن لما لاقينا ان الرقم الى عندنا اكبر من 1 مش هانعرف ال function هترجع ايه

هى هترجع حاصل ضرب الرقم فى مضروب الى قبليه طب هاييجى الرقم الجديد هايساوى الرقم (القديم - 1) زى ماتفقنا طب لو الرقم الجديد بتاعنا اكبر من 1 هايرجع برضه الرقم مضروب فى factorialالى قبليه وهكذا لغايه ميلاقى ان الرقم اصبح 1 وبعدين يرجع حاصل ضربهم كلهم

قى المرة الجاية ان شاء الله هانعرف ازاى الحكايه دى بتم يعنى التخزين بتاع القيم وحاصل الضرب بيتخزن فين وكدة ........ لكن الى عايزين نعرفه دلوقتى ..... فوق فى السطرين الى فاتو انا قلت حاجة مهمة اول مايلاقى الرقم بقى 1 البرنامج بيخرج

دى معناها ايه ؟؟؟؟

دى الحاجة الى كنا اتكلمنا عليها فى الاول الى اسمها anchor case طيب فاضل حاجة تانيه الى انت بتقول عليها بتتكون منها ال recursion قصدكم ال inductive Or base case


طب كويس مركزين اهو طيب احنا اتفقنا ان inductive Or base caseهيا ايه ؟؟؟

الجزء الى بيحصل قبل مالفانكشن بتاعتنا توصل للanchor case طيب ايه الى بيحصل قبل مالفانكشن بتاعتنا تخلص ؟؟؟

صح هو الجزء بتاع ان احنا نحسب

Fact(N)=N*Fact(N-1)=N*(N-1)*Fact(n-2).....*3*2*1 IF (N>1)

طيب يبقى كدة احنا عرفنا يعنى ايه recursionوايه مكوناتها وفهمناهم خدنا مثال عليها نيجى بقى للcode

دا كود بيعمل فنكشن بتحسب factorial بدون recursion

int Factorial(int Number)

{

int Sum = 1;

for (int i = Number ; i > 1 ; i --)

Sum *= i;

return Sum;

}

وده كود بال recursion

int Factorial(int Number)

{

if(Number <= 1) // anchor case

return 1;

else

return ( Number * Factorial (Number - 1); // inductive OR recursive case

}

احنا ممكن لحد دلوقتى منحسش بقيمة ال Recursion وقد ايه هى نعمة من عند ربنا لاننا لسه فى البداية ولسه مش حسينا فوائدها

ملحوظة : هناااا نسخة PDF للمقال