From c50452d81b08d54b734f1da73b736bfbc2997215 Mon Sep 17 00:00:00 2001 From: Aleksander Sadikov Date: Tue, 22 Sep 2015 17:57:04 +0200 Subject: New problems with hints for famrel section added. This practically completes famrel section without some details. --- .../problems/family_relations/ancestor_2/common.py | 101 ++++++++++++++++++ prolog/problems/family_relations/ancestor_2/sl.py | 62 +++++++++++ prolog/problems/family_relations/aunt_2/common.py | 100 +++++++++++++++++ prolog/problems/family_relations/aunt_2/sl.py | 60 +++++++++++ .../problems/family_relations/brother_2/common.py | 12 +-- prolog/problems/family_relations/brother_2/sl.py | 2 +- .../problems/family_relations/cousin_2/common.py | 95 +++++++++++++++++ prolog/problems/family_relations/cousin_2/sl.py | 63 +++++++++++ .../family_relations/descendant_2/common.py | 101 ++++++++++++++++++ .../problems/family_relations/descendant_2/sl.py | 63 +++++++++++ .../problems/family_relations/father_2/common.py | 5 +- .../family_relations/grandparent_2/common.py | 118 ++++++++++++++++++++- .../problems/family_relations/grandparent_2/sl.py | 55 ++++++++++ .../problems/family_relations/mother_2/common.py | 5 +- .../problems/family_relations/sister_2/common.py | 12 +-- prolog/problems/family_relations/sister_2/sl.py | 2 +- 16 files changed, 835 insertions(+), 21 deletions(-) create mode 100644 prolog/problems/family_relations/ancestor_2/sl.py create mode 100644 prolog/problems/family_relations/aunt_2/sl.py create mode 100644 prolog/problems/family_relations/cousin_2/sl.py create mode 100644 prolog/problems/family_relations/descendant_2/sl.py create mode 100644 prolog/problems/family_relations/grandparent_2/sl.py 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 = '''\ +

ancestor(X, Y): X je prednik (oče, mama, dedek, ...) od Y.

+
+  ?- ancestor(patricia, X).
+    X = john ;
+    X = michael ;
+    X = michelle.
+
''' + +plan = ['''\ +

Sedaj pa bo potrebna rekurzija... kako lahko problem prevedem na (en korak) manjši problem?

+

+

Loni naj zbriše imena relacij (parent, ancestor) s te slike!

+''', '''\ +

+''', '''\ +

Če je X starš od nekega Z in je +ta Z prednik od Y, +potem je X tudi prednik od Y.

+'''] + +hint = { + 'gender_is_irrelevant': '''\ +

Je spol res pomemben?

+''', + + 'grandparent_used': '''\ +

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)...

+''', + + 'base_case': '''\ +

Si pomislil na robni pogoj? Kaj je najbolj enostaven par (prednik, potomec)?

+

+''', + + 'ancestor_to_oneself': '''\ +

Kako je lahko nekdo prednik samemu sebi?

+''', + + 'descendant_need_not_be_parent': '''\ +

Potomec Y pravzaprav ne rabi imeti otrok...

+''', + + 'wrong_direction': '''\ +

Si morda sprogramiral ravno obratno in zamenjal prednika s potomcem? +X naj bo prednik od Y in ne obratno!

+''', + +# 'interesting_tidbit': '''\ +#

Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "sister". +# Teta je namreč sestra od starša od Y.

+# ''', +} + 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 = '''\ +

aunt(X, Y): X je teta od Y.

+
+  ?- aunt(sally, X).
+    X = vanessa ;
+    X = patricia.
+
''' + +plan = ['''\ +

Morda lahko uporabiš rešitev kakšne prejšnje naloge?

+

+

Loni naj zbriše imena relacij (parent, sister) s te slike!

+''', '''\ +

+''', '''\ +

Če je X sestra od starša od Y, +potem je X teta od Y.

+'''] + +hint = { + 'x_and_y_mixed_up': '''\ +

X mora biti teta od Y, ne obratno.

+''', + + 'precedence_fail': '''\ +

Si morda narobe upošteval prioriteto operatorjev IN ter ALI?

+

Operator IN veže močneje od ALI, če želiš spremeniti prioriteto, +lahko uporabiš oklepaje.

+''', + + 'x_must_be_female': '''\ +

Teta je navadno ženskega spola.

+''', # TODO: Loni should mark female gender on some aunt hints! + + 'aunt_vs_mother': '''\ +

Kako je teta lahko hkrati še mama od Y? +Si morda pozabil, da teta in starš od Y ne smeta biti ista oseba?

+

+''', + + 'x_need_not_be_parent': '''\ +

X pravzaprav ne rabi imeti otrok, da je lahko teta od nekoga...

+''', + + 'y_need_not_be_parent': '''\ +

Nečak ali nečakinja Y pravzaprav ne rabi imeti otrok, da ima teto...

+''', + + 'interesting_tidbit': '''\ +

Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "sister". +Teta je namreč sestra od starša od Y.

+''', # 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 = {

Brat je navadno moškega spola.

''', - 'Y_can_be_of_any_gender': '''\ + 'y_can_be_of_any_gender': '''\

Y je pravzaprav lahko poljubnega spola.

''', 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 = '''\ +

cousin(X, Y): X je bratranec ali sestrična od Y.

+
+  ?- cousin(andrew, X).
+    X = vanessa ;
+    X = patricia.
+
''' + +plan = ['''\ +

Verjetno bi se sedaj splačalo uporabiti rešitev kakšne prejšnje naloge?

+

+

Loni naj zbriše imena relacij (parent, sister) s te slike!

+

Seveda se da rešiti tudi brez prejšnjih rešitev... MORE

+

+

Loni naj zbriše imena relacij (parent, sister) s te slike!

+''', '''\ +

+''', '''\ +

Če je PX starš od X in je +PY starš od Y ter sta +PX in PY brat ali sestra, +potem je X bratranec/sestrična od Y.

+'''] + +hint = { + 'gender_is_irrelevant': '''\ +

Je spol res pomemben?

+''', + + 'precedence_fail': '''\ +

Si morda narobe upošteval prioriteto operatorjev IN ter ALI?

+

Operator IN veže močneje od ALI, če želiš spremeniti prioriteto, +lahko uporabiš oklepaje.

+''', + + 'cousin_vs_sibling': '''\ +

Kako je lahko bratranec/sestrična hkrati tudi brat/sestra od Y? +Si morda pozabil, da starša od X in Y ne smeta biti ista oseba?

+

+''', + + 'cousin_to_oneself': '''\ +

Kako je lahko nekdo bratranec/sestrična samemu sebi? +Imata morda X in Y istega starša?

+

+''', + + 'cousin_need_not_be_parent': '''\ +

Bratranec/sestrična pravzaprav ne rabi imeti otrok...

+''', + +# 'interesting_tidbit': '''\ +#

Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "sister". +# Teta je namreč sestra od starša od Y.

+# ''', +} + 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 = '''\ +

descendant(X, Y): X is a descendant (child, grandchild,...) of Y.

+
+  ?- descendant(patricia, X).
+    X = william ;
+    X = tina ;
+    X = thomas.
+
''' + +plan = ['''\ +

Brez rekurzije ne bo šlo... kako lahko problem prevedem na (en korak) manjši problem?

+

+

Loni naj zbriše imena relacij (parent, ancestor) s te slike!

+''', '''\ +

+''', '''\ +

Če je nek Z starš od X in je +ta Z hkrati potomec od Y, +potem je tudi X potomec od Y.

+'''] + +hint = { + 'gender_is_irrelevant': '''\ +

Je spol res pomemben?

+''', + + 'grandparent_used': '''\ +

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)...

+''', + + 'base_case': '''\ +

Si pomislil na robni pogoj? Kaj je najbolj enostaven par (potomec, prednik)?

+

+''', + + 'descendant_of_oneself': '''\ +

Kako je lahko nekdo potomec samega sebe?

+''', + + 'descendant_need_not_be_parent': '''\ +

Potomec X pravzaprav ne rabi imeti otrok...

+''', + + 'wrong_direction': '''\ +

Si morda sprogramiral ravno obratno in zamenjal prednika s potomcem? +X naj bo potomec od Y in ne obratno!

+''', + + 'interesting_tidbit': '''\ +

Zanimivost: nalogo bi lahko rešil tudi z uporabo rešitve za relacijo "ancestor". +Samo obrni spremenljivki X in Y; +če je X potomec od Y, potem je Y prednik od X.

+''', +} + 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 = '''\ +

grandparent(X, Y): X je dedek ali babica od Y.

+
+  ?- grandparent(tina, X).
+    X = vanessa ;
+    X = patricia.
+  ?- grandparent(tina, vanessa).
+    true.
+
''' + +plan = ['''\ +

+

Loni naj zbriše imena relacij (parent, grandparent) s te slike!

+''', '''\ +

+''', '''\ +

Če je X starš od starša od Y, +potem je X stari starš od Y.

+'''] + +hint = { + 'no_common_z': '''\ +

Si povezal X in Y preko neke skupne (iste!) osebe?

+''', # TODO: Tim, tale namig.. nisem prepričan, da mi je všeč... (kako je izražen) + + 'gender_is_irrelevant': '''\ +

Iščemo starega starša in vnuka, spol pri tem ni pomemben.

+''', + + 'or_instead_of_and': '''\ +

Si morda uporabil podpičje (ki pomeni ALI) namesto vejice (ki pomeni IN)?

+''', + + 'x_must_be_parent': '''\ +

Dedek ali babica ima gotovo kakšnega otroka, kajne? Torej je starš od nekoga...

+''', + + 'x_need_not_have_parent': '''\ +

Starš od X ni pomemben, ko iščemo vnuka...

+''', + + 'y_must_have_parent': '''\ +

Vnuk Y mora imeti starša, kajne? Ta starš pa je...

+''', + + 'y_need_not_be_parent': '''\ +

Vnuk Y pravzaprav ne rabi imeti otrok, da ima dedka ali babico...

+''', +} 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 = {

Sestra je navadno ženskega spola.

''', - 'Y_can_be_of_any_gender': '''\ + 'y_can_be_of_any_gender': '''\

Y je pravzaprav lahko poljubnega spola.

''', -- cgit v1.2.1