diff options
16 files changed, 835 insertions, 21 deletions
diff --git a/prolog/problems/family_relations/ancestor_2/common.py b/prolog/problems/family_relations/ancestor_2/common.py index 473fa27..873b87b 100644 --- a/prolog/problems/family_relations/ancestor_2/common.py +++ b/prolog/problems/family_relations/ancestor_2/common.py @@ -13,3 +13,104 @@ ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y). ''' + +hint_type = { + 'gender_is_irrelevant': Hint('gender_is_irrelevant'), + 'grandparent_used': Hint('grandparent_used'), + 'base_case': Hint('base_case'), + 'ancestor_to_oneself': Hint('ancestor_to_oneself'), + 'descendant_need_not_be_parent': Hint('descendant_need_not_be_parent'), + 'wrong_direction': Hint('wrong_direction'), +} + +test_cases = [ + # TODO +] + +def test(program, solved_problems): + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id or 'error' in map(itemgetter(0), output): + # Engine creation failed, or syntax error in code. + return 0, len(test_cases) + + n_correct = 0 + for query, answers in test_cases: + # Limit inferences for each solution to curb unbounded recursion. + limited = 'call_with_inference_limit(({}), 100000, _)'.format(query) + if prolog.engine.check_answers(engine_id, query=limited, answers=answers, timeout=1.0): + n_correct += 1 + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + passed = n_correct == len(test_cases) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}] + return passed, hints + +def hint(program, solved_problems): + tokens = prolog.util.tokenize(program) + + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id: + raise Exception('Prolog engine failed to create.') + + # gender testing is redundant + # this is not necessarily wrong, but worth mentioning anyway + targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female')] + pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets] + if pos: + # TODO: resolve how highlighting info is communicated + return [{'id': 'gender_is_irrelevant', 'highlight': pos}] + + # grandparent is a sign of complications + # it's likely used instead of recursion + targets = [prolog.util.Token('NAME', 'grandparent')] + pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets] + if pos: + # TODO: resolve how highlighting info is communicated + return [{'id': 'grandparent_used', 'highlight': pos}] + + # missing/failed base case + # TODO: how will ask_truth handle/return timeouts... + # TODO: timeout is the same as fail in this particular case + if prolog.engine.ask_truth(engine_id, + 'parent(X, Y), \+ ancestor(X, Y)'): + return [{'id', 'base_case'}] + + # ancestor to oneself + if prolog.engine.ask_truth(engine_id, 'ancestor(X, X)'): + return [{'id', 'ancestor_to_oneself'}] + + # Y does not necessarily need to be a parent + if prolog.engine.ask_one(engine_id, + 'ancestor(_, Y), \+ parent(Y, _)') == 'false': + return [{'id': 'descendant_need_not_be_parent'}] + + # descendant instead of ancestor (wrong direction) + # warning: knowledge base dependent + if prolog.engine.ask_truth(engine_id, + 'findall(_, ancestor(william, X), L1), length(L1, 2), \ + findall(_, ancestor(X, william), L2), length(L2, 6)'): + return [{'id', 'wrong_direction'}] + + except socket.timeout as ex: + pass + + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + return None + diff --git a/prolog/problems/family_relations/ancestor_2/sl.py b/prolog/problems/family_relations/ancestor_2/sl.py new file mode 100644 index 0000000..76c2bea --- /dev/null +++ b/prolog/problems/family_relations/ancestor_2/sl.py @@ -0,0 +1,62 @@ +# coding=utf-8 + +id = 100 +name = 'ancestor/2' +slug = 'X je prednik od Y' + +description = '''\ +<p><code>ancestor(X, Y)</code>: <code>X</code> je prednik (oče, mama, dedek, ...) od <code>Y</code>.</p> +<pre> + ?- ancestor(patricia, X). + X = john ; + X = michael ; + X = michelle. +</pre>''' + +plan = ['''\ +<p>Sedaj pa bo potrebna rekurzija... kako lahko problem prevedem na (en korak) manjši problem?</p> +<p><img src="Ancestor-3.png" /></p> +<p>Loni naj zbriše imena relacij (parent, ancestor) s te slike!</p> +''', '''\ +<p><img src="Ancestor-3.png" /></p> +''', '''\ +<p>Če je <code>X</code> starš od nekega <code>Z</code> in je +ta <code>Z</code> prednik od <code>Y</code>, +potem je <code>X</code> tudi prednik od <code>Y</code>.</p> +'''] + +hint = { + 'gender_is_irrelevant': '''\ +<p>Je spol res pomemben?</p> +''', + + 'grandparent_used': '''\ +<p>Rešitev z "grandparent" bo premalo splošna, poskusi nadomestiti to z rekurzijo. +Skratka, poskusi prevesti na "manjši" problem, npr. prednik v enem koraku manj +(en korak bližji prednik)...</p> +''', + + 'base_case': '''\ +<p>Si pomislil na robni pogoj? Kaj je najbolj enostaven par (prednik, potomec)?</p> +<p><img src="Ancestor-1.png" /></p> +''', + + 'ancestor_to_oneself': '''\ +<p>Kako je lahko nekdo prednik samemu sebi?</p> +''', + + 'descendant_need_not_be_parent': '''\ +<p>Potomec <code>Y</code> pravzaprav ne rabi imeti otrok...</p> +''', + + 'wrong_direction': '''\ +<p>Si morda sprogramiral ravno obratno in zamenjal prednika s potomcem? +<code>X</code> naj bo prednik od <code>Y</code> in ne obratno!</p> +''', + +# 'interesting_tidbit': '''\ +# <p>Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "sister". +# Teta je namreč sestra od starša od <code>Y</code>.</p> +# ''', +} + diff --git a/prolog/problems/family_relations/aunt_2/common.py b/prolog/problems/family_relations/aunt_2/common.py index f0af2dc..ed291fc 100644 --- a/prolog/problems/family_relations/aunt_2/common.py +++ b/prolog/problems/family_relations/aunt_2/common.py @@ -17,3 +17,103 @@ aunt(X, Y) :- sister98(X, Z), parent(Z, Y). ''' + +hint_type = { + 'x_and_y_mixed_up': Hint('x_and_y_mixed_up'), + 'precedence_fail': Hint('precedence_fail'), + 'x_must_be_female': Hint('x_must_be_female'), + 'aunt_vs_mother': Hint('aunt_vs_mother'), + 'x_need_not_be_parent': Hint('x_need_not_be_parent'), + 'y_need_not_be_parent': Hint('y_need_not_be_parent'), +} + +test_cases = [ + # TODO +] + +def test(program, solved_problems): + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id or 'error' in map(itemgetter(0), output): + # Engine creation failed, or syntax error in code. + return 0, len(test_cases) + + n_correct = 0 + for query, answers in test_cases: + # Limit inferences for each solution to curb unbounded recursion. + limited = 'call_with_inference_limit(({}), 100000, _)'.format(query) + if prolog.engine.check_answers(engine_id, query=limited, answers=answers, timeout=1.0): + n_correct += 1 + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + passed = n_correct == len(test_cases) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}] + return passed, hints + +def hint(program, solved_problems): + tokens = prolog.util.tokenize(program) + + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id: + raise Exception('Prolog engine failed to create.') + + # X and Y mixed up + # warning: knowledge base dependent + # independent: match on findall(X/Y, (sister(Y, P), parent(P, X)), L) + if prolog.engine.ask_truth(engine_id, + 'findall(X/Y, aunt(X, Y), \ + [vanessa/sally, patricia/sally, joanne/melanie, john/vanessa, susan/patricia])'): + return [{'id', 'x_and_y_mixed_up'}] + + # precedence fail (AND block vs OR block) + # case in point: female(X), parent(P, Y), brother(P, X) ; sister(P, X) + # warning: knowledge base dependent + if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id, + 'findall(_, aunt(X, Y), L), length(L, 15)'): + return [{'id', 'precedence_fail'}] + + # X must be female + if prolog.engine.ask_truth(engine_id, + 'aunt(X, _), male(X)'): + return [{'id', 'x_must_be_female'}] + + # X and P can be the same person + # this can occur if the problem is not solved using sister/2 + if prolog.engine.ask_truth(engine_id, + 'aunt(X, Y), mother(X, Y)'): + return [{'id', 'aunt_vs_mother'}] + + # X does not necessarily need to be a parent + # perhaps risky as only one aunt that is not a parent exists in DB (melanie) + if prolog.engine.ask_one(engine_id, + 'aunt(X, _), \+ parent(X, _)') == 'false': + return [{'id': 'x_need_not_be_parent'}] + + # Y does not necessarily need to be a parent + # perhaps risky as only one such nephew exists in DB (susan) + if prolog.engine.ask_one(engine_id, + 'aunt(_, Y), \+ parent(Y, _)') == 'false': + return [{'id': 'y_need_not_be_parent'}] + + except socket.timeout as ex: + pass + + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + return None + diff --git a/prolog/problems/family_relations/aunt_2/sl.py b/prolog/problems/family_relations/aunt_2/sl.py new file mode 100644 index 0000000..95fc6b2 --- /dev/null +++ b/prolog/problems/family_relations/aunt_2/sl.py @@ -0,0 +1,60 @@ +# coding=utf-8 + +id = 98 +name = 'aunt/2' +slug = 'X je teta od Y' + +description = '''\ +<p><code>aunt(X, Y)</code>: <code>X</code> je teta od <code>Y</code>.</p> +<pre> + ?- aunt(sally, X). + X = vanessa ; + X = patricia. +</pre>''' + +plan = ['''\ +<p>Morda lahko uporabiš rešitev kakšne prejšnje naloge?</p> +<p><img src="Aunt-5.png" /></p> +<p>Loni naj zbriše imena relacij (parent, sister) s te slike!</p> +''', '''\ +<p><img src="Aunt-5.png" /></p> +''', '''\ +<p>Če je <code>X</code> sestra od starša od <code>Y</code>, +potem je <code>X</code> teta od <code>Y</code>.</p> +'''] + +hint = { + 'x_and_y_mixed_up': '''\ +<p><code>X</code> mora biti teta od <code>Y</code>, ne obratno.</p> +''', + + 'precedence_fail': '''\ +<p>Si morda narobe upošteval prioriteto operatorjev IN ter ALI?</p> +<p>Operator IN veže močneje od ALI, če želiš spremeniti prioriteto, +lahko uporabiš oklepaje.</p> +''', + + 'x_must_be_female': '''\ +<p>Teta je navadno ženskega spola.</p> +''', # TODO: Loni should mark female gender on some aunt hints! + + 'aunt_vs_mother': '''\ +<p>Kako je teta lahko hkrati še mama od <code>Y</code>? +Si morda pozabil, da teta in starš od <code>Y</code> ne smeta biti ista oseba?</p> +<p><img src="Aunt-4.png" /></p> +''', + + 'x_need_not_be_parent': '''\ +<p><code>X</code> pravzaprav ne rabi imeti otrok, da je lahko teta od nekoga...</p> +''', + + 'y_need_not_be_parent': '''\ +<p>Nečak ali nečakinja <code>Y</code> pravzaprav ne rabi imeti otrok, da ima teto...</p> +''', + + 'interesting_tidbit': '''\ +<p>Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "sister". +Teta je namreč sestra od starša od <code>Y</code>.</p> +''', # TODO: only trigger if sister is not part of correct solution +} + diff --git a/prolog/problems/family_relations/brother_2/common.py b/prolog/problems/family_relations/brother_2/common.py index 0df2d73..327e746 100644 --- a/prolog/problems/family_relations/brother_2/common.py +++ b/prolog/problems/family_relations/brother_2/common.py @@ -24,7 +24,7 @@ brother(X, Y) :- hint_type = { 'x_must_be_male': Hint('x_must_be_male'), 'common_parent_needed': Hint('common_parent_needed'), - 'Y_can_be_of_any_gender': Hint('Y_can_be_of_any_gender'), + 'y_can_be_of_any_gender': Hint('Y_can_be_of_any_gender'), 'x_y_must_be_different': Hint('x_y_must_be_different'), } @@ -87,14 +87,12 @@ def hint(program, solved_problems): # Y can be of any gender, incl. female if prolog.engine.ask_one(engine_id, 'brother(_, Y), female(Y)') == 'false': - return [{'id': 'Y_can_be_of_any_gender'}] + return [{'id': 'y_can_be_of_any_gender'}] # X and Y must be different - ans = prolog.engine.ask_one(engine_id, - 'setof(X, (member(X, [william, alessandro, andrew]), brother(X, X)), L), length(L, N).') - # TODO this barfs if ans is 'false' - #if ans.get('N') == '3': - # return [{'id': 'x_y_must_be_different'}] + if prolog.engine.ask_truth(engine_id, + 'setof(X, (member(X, [william, alessandro, andrew]), brother(X, X)), L), length(L, 3)'): + return [{'id': 'x_y_must_be_different'}] except socket.timeout as ex: pass diff --git a/prolog/problems/family_relations/brother_2/sl.py b/prolog/problems/family_relations/brother_2/sl.py index 116a968..2176bd8 100644 --- a/prolog/problems/family_relations/brother_2/sl.py +++ b/prolog/problems/family_relations/brother_2/sl.py @@ -38,7 +38,7 @@ hint = { <p>Brat je navadno moškega spola.</p> ''', - 'Y_can_be_of_any_gender': '''\ + 'y_can_be_of_any_gender': '''\ <p><code>Y</code> je pravzaprav lahko poljubnega spola.</p> ''', diff --git a/prolog/problems/family_relations/cousin_2/common.py b/prolog/problems/family_relations/cousin_2/common.py index 7cd6392..d95babd 100644 --- a/prolog/problems/family_relations/cousin_2/common.py +++ b/prolog/problems/family_relations/cousin_2/common.py @@ -26,3 +26,98 @@ cousin(X, Y) :- ; sister99(PX, PY) ). ''' + +hint_type = { + 'gender_is_irrelevant': Hint('gender_is_irrelevant'), + 'precedence_fail': Hint('precedence_fail'), + 'cousin_vs_sibling': Hint('cousin_vs_sibling'), + 'cousin_to_oneself': Hint('cousin_to_oneself'), + 'cousin_need_not_be_parent': Hint('cousin_need_not_be_parent'), +} + +test_cases = [ + # TODO +] + +def test(program, solved_problems): + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id or 'error' in map(itemgetter(0), output): + # Engine creation failed, or syntax error in code. + return 0, len(test_cases) + + n_correct = 0 + for query, answers in test_cases: + # Limit inferences for each solution to curb unbounded recursion. + limited = 'call_with_inference_limit(({}), 100000, _)'.format(query) + if prolog.engine.check_answers(engine_id, query=limited, answers=answers, timeout=1.0): + n_correct += 1 + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + passed = n_correct == len(test_cases) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}] + return passed, hints + +def hint(program, solved_problems): + tokens = prolog.util.tokenize(program) + + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id: + raise Exception('Prolog engine failed to create.') + + # gender testing is redundant + # this is not necessarily wrong, but worth mentioning anyway + targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female')] + pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets] + if pos: + # TODO: resolve how highlighting info is communicated + return [{'id': 'gender_is_irrelevant', 'highlight': pos}] + + # precedence fail (AND block vs OR block) + # case in point: parent(PX, X), parent(PY, Y), brother(PX, PY) ; sister(PX, PY) + # or this case: parent(PX, X), parent(PY, Y), sister(PX, PY) ; brother(PX, PY) + # warning: knowledge base dependent + if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id, + 'findall(_, cousin(X, Y), L), (length(L, 14) ; length(L, 16))'): + return [{'id', 'precedence_fail'}] + + # cousin should not be brother or sister + # common parent, probably solved via grandparent + if prolog.engine.ask_truth(engine_id, + 'cousin(X, Y), (brother(X, Y) ; sister(X, Y))'): + return [{'id', 'cousin_vs_sibling'}] + + # cousin to him/herself + # just common grandparent is not enough + if prolog.engine.ask_truth(engine_id, 'cousin(X, X)'): + return [{'id', 'cousin_to_oneself'}] + + + # X (or Y) does not necessarily need to be a parent + # cousin is a symmetrical relation, so X and Y are covered by this + if prolog.engine.ask_one(engine_id, + 'cousin(X, _), \+ parent(X, _)') == 'false': + return [{'id': 'cousin_need_not_be_parent'}] + + except socket.timeout as ex: + pass + + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + return None + diff --git a/prolog/problems/family_relations/cousin_2/sl.py b/prolog/problems/family_relations/cousin_2/sl.py new file mode 100644 index 0000000..ebecf8d --- /dev/null +++ b/prolog/problems/family_relations/cousin_2/sl.py @@ -0,0 +1,63 @@ +# coding=utf-8 + +id = 99 +name = 'cousin/2' +slug = 'X je bratranec ali sestrična od Y' + +description = '''\ +<p><code>cousin(X, Y)</code>: <code>X</code> je bratranec ali sestrična od <code>Y</code>.</p> +<pre> + ?- cousin(andrew, X). + X = vanessa ; + X = patricia. +</pre>''' + +plan = ['''\ +<p>Verjetno bi se sedaj splačalo uporabiti rešitev kakšne prejšnje naloge?</p> +<p><img src="Cousin-1.png" /></p> +<p>Loni naj zbriše imena relacij (parent, sister) s te slike!</p> +<p>Seveda se da rešiti tudi brez prejšnjih rešitev... MORE</p> +<p><img src="Cousin-3.png" /></p> +<p>Loni naj zbriše imena relacij (parent, sister) s te slike!</p> +''', '''\ +<p><img src="Cousin-1.png" /></p> +''', '''\ +<p>Če je <code>PX</code> starš od <code>X</code> in je +<code>PY</code> starš od <code>Y</code> ter sta +<code>PX</code> in <code>PY</code> brat ali sestra, +potem je <code>X</code> bratranec/sestrična od <code>Y</code>.</p> +'''] + +hint = { + 'gender_is_irrelevant': '''\ +<p>Je spol res pomemben?</p> +''', + + 'precedence_fail': '''\ +<p>Si morda narobe upošteval prioriteto operatorjev IN ter ALI?</p> +<p>Operator IN veže močneje od ALI, če želiš spremeniti prioriteto, +lahko uporabiš oklepaje.</p> +''', + + 'cousin_vs_sibling': '''\ +<p>Kako je lahko bratranec/sestrična hkrati tudi brat/sestra od <code>Y</code>? +Si morda pozabil, da starša od <code>X</code> in <code>Y</code> ne smeta biti ista oseba?</p> +<p><img src="Cousin-4.png" /></p> +''', + + 'cousin_to_oneself': '''\ +<p>Kako je lahko nekdo bratranec/sestrična samemu sebi? +Imata morda <code>X</code> in <code>Y</code> istega starša?</p> +<p><img src="Cousin-2.png" /></p> +''', + + 'cousin_need_not_be_parent': '''\ +<p>Bratranec/sestrična pravzaprav ne rabi imeti otrok...</p> +''', + +# 'interesting_tidbit': '''\ +# <p>Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "sister". +# Teta je namreč sestra od starša od <code>Y</code>.</p> +# ''', +} + diff --git a/prolog/problems/family_relations/descendant_2/common.py b/prolog/problems/family_relations/descendant_2/common.py index 53320b3..9032612 100644 --- a/prolog/problems/family_relations/descendant_2/common.py +++ b/prolog/problems/family_relations/descendant_2/common.py @@ -13,3 +13,104 @@ descendant(X, Y) :- parent(Y, Z), descendant(X, Z). ''' + +hint_type = { + 'gender_is_irrelevant': Hint('gender_is_irrelevant'), + 'grandparent_used': Hint('grandparent_used'), + 'base_case': Hint('base_case'), + 'descendant_of_oneself': Hint('ancestor_to_oneself'), + 'descendant_need_not_be_parent': Hint('descendant_need_not_be_parent'), + 'wrong_direction': Hint('wrong_direction'), +} + +test_cases = [ + # TODO +] + +def test(program, solved_problems): + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id or 'error' in map(itemgetter(0), output): + # Engine creation failed, or syntax error in code. + return 0, len(test_cases) + + n_correct = 0 + for query, answers in test_cases: + # Limit inferences for each solution to curb unbounded recursion. + limited = 'call_with_inference_limit(({}), 100000, _)'.format(query) + if prolog.engine.check_answers(engine_id, query=limited, answers=answers, timeout=1.0): + n_correct += 1 + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + passed = n_correct == len(test_cases) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}] + return passed, hints + +def hint(program, solved_problems): + tokens = prolog.util.tokenize(program) + + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id: + raise Exception('Prolog engine failed to create.') + + # gender testing is redundant + # this is not necessarily wrong, but worth mentioning anyway + targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female')] + pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets] + if pos: + # TODO: resolve how highlighting info is communicated + return [{'id': 'gender_is_irrelevant', 'highlight': pos}] + + # grandparent is a sign of complications + # it's likely used instead of recursion + targets = [prolog.util.Token('NAME', 'grandparent')] + pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets] + if pos: + # TODO: resolve how highlighting info is communicated + return [{'id': 'grandparent_used', 'highlight': pos}] + + # missing/failed base case + # TODO: how will ask_truth handle/return timeouts... + # TODO: timeout is the same as fail in this particular case + if prolog.engine.ask_truth(engine_id, + 'parent(Y, X), \+ descendant(X, Y)'): + return [{'id', 'base_case'}] + + # descendant of oneself + if prolog.engine.ask_truth(engine_id, 'descendant(X, X)'): + return [{'id', 'descendant_of_oneself'}] + + # X does not necessarily need to be a parent + if prolog.engine.ask_one(engine_id, + 'descendant(X, _), \+ parent(X, _)') == 'false': + return [{'id': 'descendant_need_not_be_parent'}] + + # ancestor instead of descendant (wrong direction) + # warning: knowledge base dependent + if prolog.engine.ask_truth(engine_id, + 'findall(_, descendant(william, X), L1), length(L1, 6), \ + findall(_, descendant(X, william), L2), length(L2, 2)'): + return [{'id', 'wrong_direction'}] + + except socket.timeout as ex: + pass + + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + return None + diff --git a/prolog/problems/family_relations/descendant_2/sl.py b/prolog/problems/family_relations/descendant_2/sl.py new file mode 100644 index 0000000..fb60e76 --- /dev/null +++ b/prolog/problems/family_relations/descendant_2/sl.py @@ -0,0 +1,63 @@ +# coding=utf-8 + +id = 101 +name = 'descendant/2' +slug = 'the descendant relation' + +description = '''\ +<p><code>descendant(X, Y)</code>: <code>X</code> is a descendant (child, grandchild,...) of <code>Y</code>.</p> +<pre> + ?- descendant(patricia, X). + X = william ; + X = tina ; + X = thomas. +</pre>''' + +plan = ['''\ +<p>Brez rekurzije ne bo šlo... kako lahko problem prevedem na (en korak) manjši problem?</p> +<p><img src="Descendant-3.png" /></p> +<p>Loni naj zbriše imena relacij (parent, ancestor) s te slike!</p> +''', '''\ +<p><img src="Descendant-3.png" /></p> +''', '''\ +<p>Če je nek <code>Z</code> starš od <code>X</code> in je +ta <code>Z</code> hkrati potomec od <code>Y</code>, +potem je tudi <code>X</code> potomec od <code>Y</code>.</p> +'''] + +hint = { + 'gender_is_irrelevant': '''\ +<p>Je spol res pomemben?</p> +''', + + 'grandparent_used': '''\ +<p>Rešitev z "grandparent" bo premalo splošna, poskusi nadomestiti to z rekurzijo. +Skratka, poskusi prevesti na "manjši" problem, npr. potomec v enem koraku manj +(en korak bližji potomec)...</p> +''', + + 'base_case': '''\ +<p>Si pomislil na robni pogoj? Kaj je najbolj enostaven par (potomec, prednik)?</p> +<p><img src="Descendant-1.png" /></p> +''', + + 'descendant_of_oneself': '''\ +<p>Kako je lahko nekdo potomec samega sebe?</p> +''', + + 'descendant_need_not_be_parent': '''\ +<p>Potomec <code>X</code> pravzaprav ne rabi imeti otrok...</p> +''', + + 'wrong_direction': '''\ +<p>Si morda sprogramiral ravno obratno in zamenjal prednika s potomcem? +<code>X</code> naj bo potomec od <code>Y</code> in ne obratno!</p> +''', + + 'interesting_tidbit': '''\ +<p>Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "ancestor". +Samo obrni spremenljivki <code>X</code> in <code>Y</code>; +če je <code>X</code> potomec od <code>Y</code>, potem je <code>Y</code> prednik od <code>X</code>.</p> +''', +} + diff --git a/prolog/problems/family_relations/father_2/common.py b/prolog/problems/family_relations/father_2/common.py index 22c9b48..cbfbcab 100644 --- a/prolog/problems/family_relations/father_2/common.py +++ b/prolog/problems/family_relations/father_2/common.py @@ -74,9 +74,10 @@ def hint(program, solved_problems): # this hint has to be before the next two # as otherwise those two would always override it # and not convey the same (amount of) help/information + # warning: due to speed considerations this (48) is knowledge base dependent + # independent: findall(_, (parent(X, Y) ; male(X)), L2) if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id, - 'findall(_, father(X, Y), L1), length(L1, N), \ - findall(_, (parent(X, Y) ; male(X)), L2), length(L2, N)'): + 'findall(_, father(X, Y), L), length(L, 48)'): return [{'id': 'or_instead_of_and'}] # X must be male diff --git a/prolog/problems/family_relations/grandparent_2/common.py b/prolog/problems/family_relations/grandparent_2/common.py index 1091d52..98e40fc 100644 --- a/prolog/problems/family_relations/grandparent_2/common.py +++ b/prolog/problems/family_relations/grandparent_2/common.py @@ -1,8 +1,14 @@ # coding=utf-8 +import socket +import prolog.engine +import prolog.util +from server.hints import Hint, HintSequence +import server.problems + id = 95 group = 'family_relations' -number = 2 +number = 30 visible = True facts = 'family_relations' @@ -11,3 +17,113 @@ grandparent(X, Y) :- parent(X, Z), parent(Z, Y). ''' + +hint_type = { + 'no_common_z': Hint('no_common_z'), + 'gender_is_irrelevant': Hint('gender_is_irrelevant'), + 'or_instead_of_and': Hint('or_instead_of_and'), + 'x_must_be_parent': Hint('x_must_be_parent'), + 'x_need_not_have_parent': Hint('x_need_not_have_parent'), + 'y_must_have_parent': Hint('y_must_have_parent'), + 'y_need_not_be_parent': Hint('y_need_not_be_parent'), +} + +test_cases = [ + # TODO +] + +def test(program, solved_problems): + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id or 'error' in map(itemgetter(0), output): + # Engine creation failed, or syntax error in code. + return 0, len(test_cases) + + n_correct = 0 + for query, answers in test_cases: + # Limit inferences for each solution to curb unbounded recursion. + limited = 'call_with_inference_limit(({}), 100000, _)'.format(query) + if prolog.engine.check_answers(engine_id, query=limited, answers=answers, timeout=1.0): + n_correct += 1 + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + passed = n_correct == len(test_cases) + hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}] + return passed, hints + +def hint(program, solved_problems): + tokens = prolog.util.tokenize(program) + + code = (program + '\n' + + server.problems.solutions_for_problems('prolog', solved_problems) + '\n' + + server.problems.load_facts('prolog', facts).facts) + + engine_id = None + try: + engine_id, output = prolog.engine.create(code=code, timeout=1.0) + if not engine_id: + raise Exception('Prolog engine failed to create.') + + # TODO: generic hint for: + # TODO: parent(X, Z1), parent(Z2, Y), Z1 = Z2 + + # broken "link" mistake -- no common parent/child Z + # warning: due to speed considerations this (1024) is knowledge base dependent + # independent: findall(_, (parent(X, Z1), parent(Z2, Y)), L) + if prolog.engine.ask_truth(engine_id, + 'findall(_, grandparent(X, Y), L), length(L, 1024)'): + return [{'id': 'no_common_z'}] + + # gender testing is redundant + # this is not necessarily wrong, but worth mentioning anyway + targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female')] + pos = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets] + if pos: + # TODO: resolve how highlighting info is communicated + return [{'id': 'gender_is_irrelevant', 'highlight': pos}] + + # OR (;) instead of AND (,) + # this hint has to be before the next two + # as otherwise those two would always override it + # and not convey the same (amount of) help/information + # warning: due to speed considerations this (64) is knowledge base dependent + if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id, + 'findall(_, grandparent(X, Y), L), length(L, 64)'): + return [{'id': 'or_instead_of_and'}] + + # X must be a parent + if prolog.engine.ask_truth(engine_id, + 'grandparent(X, _), \+ parent(X, _)'): + return [{'id': 'x_must_be_parent'}] + + # X does not necessarily need to have a parent + if prolog.engine.ask_one(engine_id, + 'grandparent(X, _), \+ parent(_, X)') == 'false': + return [{'id': 'x_need_not_have_parent'}] + + # Y must have a parent + if prolog.engine.ask_truth(engine_id, + 'grandparent(_, Y), \+ parent(_, Y)'): + return [{'id': 'y_must_have_parent'}] + + # Y does not necessarily need to be a parent + if prolog.engine.ask_one(engine_id, + 'grandparent(_, Y), \+ parent(Y, _)') == 'false': + return [{'id': 'y_need_not_be_parent'}] + + except socket.timeout as ex: + pass + + finally: + if engine_id: + prolog.engine.destroy(engine_id) + + return None + diff --git a/prolog/problems/family_relations/grandparent_2/sl.py b/prolog/problems/family_relations/grandparent_2/sl.py new file mode 100644 index 0000000..a26d0b7 --- /dev/null +++ b/prolog/problems/family_relations/grandparent_2/sl.py @@ -0,0 +1,55 @@ +# coding=utf-8 + +id = 95 +name = 'grandparent/2' +slug = 'X je dedek ali babica od Y' + +description = '''\ +<p><code>grandparent(X, Y)</code>: <code>X</code> je dedek ali babica od <code>Y</code>.</p> +<pre> + ?- grandparent(tina, X). + X = vanessa ; + X = patricia. + ?- grandparent(tina, vanessa). + true. +</pre>''' + +plan = ['''\ +<p><img src="Grandparent-1.png" /></p> +<p>Loni naj zbriše imena relacij (parent, grandparent) s te slike!</p> +''', '''\ +<p><img src="Grandparent-1.png" /></p> +''', '''\ +<p>Če je <code>X</code> starš od starša od <code>Y</code>, +potem je <code>X</code> stari starš od <code>Y</code>.</p> +'''] + +hint = { + 'no_common_z': '''\ +<p>Si povezal <code>X</code> in <code>Y</code> preko neke skupne (iste!) osebe?</p> +''', # TODO: Tim, tale namig.. nisem prepričan, da mi je všeč... (kako je izražen) + + 'gender_is_irrelevant': '''\ +<p>Iščemo starega starša in vnuka, spol pri tem ni pomemben.</p> +''', + + 'or_instead_of_and': '''\ +<p>Si morda uporabil podpičje (ki pomeni ALI) namesto vejice (ki pomeni IN)?</p> +''', + + 'x_must_be_parent': '''\ +<p>Dedek ali babica ima gotovo kakšnega otroka, kajne? Torej je starš od nekoga...</p> +''', + + 'x_need_not_have_parent': '''\ +<p>Starš od <code>X</code> ni pomemben, ko iščemo vnuka...</p> +''', + + 'y_must_have_parent': '''\ +<p>Vnuk <code>Y</code> mora imeti starša, kajne? Ta starš pa je...</p> +''', + + 'y_need_not_be_parent': '''\ +<p>Vnuk <code>Y</code> pravzaprav ne rabi imeti otrok, da ima dedka ali babico...</p> +''', +} diff --git a/prolog/problems/family_relations/mother_2/common.py b/prolog/problems/family_relations/mother_2/common.py index be0d19b..d819ef5 100644 --- a/prolog/problems/family_relations/mother_2/common.py +++ b/prolog/problems/family_relations/mother_2/common.py @@ -85,9 +85,10 @@ def hint(program, solved_problems): # this hint has to be before the next two # as otherwise those two would always override it # and not convey the same (amount of) help/information + # warning: due to speed considerations this (50) is knowledge base dependent + # independent: findall(_, (parent(X, Y) ; female(X)), L2) if prolog.util.Token('SEMI', ';') in tokens and prolog.engine.ask_truth(engine_id, - 'findall(_, mother(X, Y), L1), length(L1, N), \ - findall(_, (parent(X, Y) ; female(X)), L2), length(L2, N)'): + 'findall(_, mother(X, Y), L), length(L, 50)'): return [{'id': 'or_instead_of_and'}] # X must be female diff --git a/prolog/problems/family_relations/sister_2/common.py b/prolog/problems/family_relations/sister_2/common.py index b3cbff0..6c48b00 100644 --- a/prolog/problems/family_relations/sister_2/common.py +++ b/prolog/problems/family_relations/sister_2/common.py @@ -24,7 +24,7 @@ sister(X, Y) :- hint_type = { 'x_must_be_female': Hint('x_must_be_female'), 'common_parent_needed': Hint('common_parent_needed'), - 'Y_can_be_of_any_gender': Hint('Y_can_be_of_any_gender'), + 'y_can_be_of_any_gender': Hint('Y_can_be_of_any_gender'), 'x_y_must_be_different': Hint('x_y_must_be_different'), } @@ -118,15 +118,13 @@ def hint(program, solved_problems): # this is an example of using the ordering of hints for easier trigger checks if prolog.engine.ask_one(engine_id, 'sister(_, Y), male(Y)') == 'false': - return [{'id': 'Y_can_be_of_any_gender'}] + return [{'id': 'y_can_be_of_any_gender'}] # X and Y must be different # this is the last code-specific hint (when everything else doesn't trigger anymore) - ans = prolog.engine.ask_one(engine_id, - 'setof(X, (member(X, [sally, nevia, vanessa]), sister(X, X)), L), length(L, N).') - # TODO this barfs if ans is 'false' - #if ans.get('N') == '3': - # return [{'id': 'x_y_must_be_different'}] + if prolog.engine.ask_truth(engine_id, + 'setof(X, (member(X, [sally, nevia, vanessa]), sister(X, X)), L), length(L, 3)'): + return [{'id': 'x_y_must_be_different'}] # last hints are connected with programming style # here, this means detecting such code: diff --git a/prolog/problems/family_relations/sister_2/sl.py b/prolog/problems/family_relations/sister_2/sl.py index aa66567..8c4c60f 100644 --- a/prolog/problems/family_relations/sister_2/sl.py +++ b/prolog/problems/family_relations/sister_2/sl.py @@ -37,7 +37,7 @@ hint = { <p>Sestra je navadno ženskega spola.</p> ''', - 'Y_can_be_of_any_gender': '''\ + 'y_can_be_of_any_gender': '''\ <p><code>Y</code> je pravzaprav lahko poljubnega spola.</p> ''', |