summaryrefslogtreecommitdiff
path: root/prolog/problems/family_relations/descendant_2/common.py
blob: b2375a79f4f7d91f1f3b67efcdbf6042e9fd7e36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from operator import itemgetter
import socket
import prolog.engine
import prolog.util
from server.hints import Hint, HintPopup
import server.problems

id = 101
number = 9
visible = True
facts = 'family_relations'

solution = '''\
descendant(X, Y) :-
  parent(Y, X).
descendant(X, Y) :-
  parent(Y, Z),
  descendant(X, Z).
'''

hint_type = {
    'gender_is_irrelevant_markup': HintPopup('gender_is_irrelevant_markup'),
    'gender_is_irrelevant': Hint('gender_is_irrelevant'),
    'grandparent_used': Hint('grandparent_used'),
    'grandparent_used_markup': HintPopup('grandparent_used_markup'),
    'base_case': Hint('base_case'),
    'descendant_of_oneself': Hint('ancestor_to_oneself'),
    'descendant_of_oneself_with_or': Hint('descendant_of_oneself_with_or'),
    'descendant_need_not_be_parent': Hint('descendant_need_not_be_parent'),
    'wrong_direction': Hint('wrong_direction'),
    'predicate_always_false': Hint('predicate_always_false'),
    'timeout': Hint('timeout'),
    'final_hint': Hint('final_hint'),
}

test_cases = [
    ('descendant(anna, X)',
        [{'X': 'jerry'}, {'X': 'elaine'}, {'X': 'morty'}, {'X': 'helen'}]),
    ('descendant(daniela, X)',
        [{'X': 'nevia'}, {'X': 'aleksander'}, {'X': 'margaret'}, {'X': 'ana'}, {'X': 'aleksandr'}]),
    ('descendant(X, william)',
        [{'X': 'vanessa'}, {'X': 'patricia'}, {'X': 'susan'}, {'X': 'john'}, {'X': 'michael'}, {'X': 'michelle'}]),
]

def test(code, aux_code):
    n_correct = 0
    engine_id = None
    try:
        engine_id, output = prolog.engine.create(code=code+aux_code, timeout=1.0)
        if engine_id is not None and 'error' not in map(itemgetter(0), output):
            # Engine successfully created, and no syntax error in program.
            for query, answers in test_cases:
                if prolog.engine.check_answers(engine_id, query=query, answers=answers, timeout=1.0):
                    n_correct += 1
    except socket.timeout:
        pass
    finally:
        if engine_id:
            prolog.engine.destroy(engine_id)

    hints = [{'id': 'test_results', 'args': {'passed': n_correct, 'total': len(test_cases)}}]
    if n_correct == len(test_cases):
        tokens = prolog.util.tokenize(code)
        if prolog.util.Token('NAME', 'ancestor') not in tokens:
            hints += [{'id': 'final_hint'}]
    return n_correct, len(test_cases), hints

def hint(code, aux_code):
    tokens = prolog.util.tokenize(code)

    try:
        engine_id, output = prolog.engine.create(code=code+aux_code, timeout=1.0)

        # gender testing is redundant
        # this is not necessarily wrong, but worth mentioning anyway
        targets = [prolog.util.Token('NAME', 'male'), prolog.util.Token('NAME', 'female')]
        marks = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets]
        if marks:
            return [{'id': 'gender_is_irrelevant_markup', 'start': m[0], 'end': m[1]} for m in marks] + \
                   [{'id': 'gender_is_irrelevant'}]

        # grandparent is a sign of complications
        # it's likely used instead of recursion
        targets = [prolog.util.Token('NAME', 'grandparent')]
        marks = [(t.pos, t.pos + len(t.val)) for t in tokens if t in targets]
        if marks:
            return [{'id': 'grandparent_used_markup', 'start': m[0], 'end': m[1]} for m in marks] + \
                   [{'id': 'grandparent_used'}]

        # target predicate seems to always be false
        if not prolog.engine.ask_truthTO(engine_id, 'descendant(_, _)'):
            return [{'id': 'predicate_always_false'}]

        # ancestor instead of descendant (wrong direction)
        # warning: knowledge base dependent
        if prolog.engine.ask_truthTO(engine_id,
            'findall(_, descendant(william, X), L1), length(L1, 6), \
             findall(_, descendant(X, william), L2), length(L2, 2)'):
            return [{'id': 'wrong_direction'}]

        # missing/failed base case
        if prolog.engine.ask_truthTO(engine_id,
            'parent(Y, X), \+ descendant(X, Y)'):
            return [{'id': 'base_case'}]

        # descendant of oneself
        if prolog.engine.ask_truthTO(engine_id, 'descendant(X, X)'):
            if prolog.util.Token('SEMI', ';') in tokens:
                return [{'id': 'descendant_of_oneself_with_or'}]
            else:
                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'}]

    except socket.timeout as ex:
        return [{'id': 'timeout'}]

    finally:
        if engine_id:
            prolog.engine.destroy(engine_id)

    return None