def member(E: Term, A_List: Union[SuperSequence, Var]): """ Is E in A_List? """ # If A_List is empty, it can't have a member. So fail. if A_List.is_empty(): return # The following is an implicit 'or'. Either unify E with A_List.head() or call member(E, A_List.tail()). # The first case is easy. # for _ in unify(E, A_List.head( )): # yield yield from unify(E, A_List.head()) # The second case--member(E, A_List.tail())--is trickier. # Since A_List may be an open-ended LinkedList, A_List.tail() may be a Var. # In that case, we must first instantiate A_List.tail() to LinkedList( (Var, Var) ). A_List_Tail = A_List.tail() # Create A_List_New_Tail to be unified with A_List_Tail. # A_List_New_Tail will be A_List_Tail in most cases. # But if isinstance(A_List_Tail, Var), make A_List_New_Tail = LinkedList( (Var( ), Var( )) ). # In either case, unify A_List_Tail with A_List_New_Tail and call member(E, A_List_New_Tail). # An issue is that we can't import LinkedList since that would create an import cycle. # Instead use type(A_List), which will be LinkedList if A_List_Tail is a Var. A_List_New_Tail = type(A_List)( (Var(), Var())) if isinstance(A_List_Tail, Var) else A_List_Tail # If A_List_New_Tail is A_List_Tail, this unify does nothing. for _ in unify(A_List_New_Tail, A_List_Tail): yield from member(E, A_List_New_Tail)
def has_station(L: PyValue, S: PyValue): # print(f'-> has_station({L}, {S})?') for line in lines: # If L is instantiated, find out whether it is line before looking at all the stations. for _ in unify(L, line): for station in lines[line]: for _ in unify(S, station): # print(f'<- has_station({L}, {S})') yield
def complete_column(carry_out: int, Carry_Out_Dig: PyValue, sum_dig: int, Sum_Dig: PyValue, digits_in: List[int], Leading_Digits): """ If Sum_Dig (the variable representing the digit in the sum for this column) is not yet instantiated, instantiate it to sum_dig (if that digit is available). If Sum_Dig is already instantiated, ensure it is consistent with the sum_dig. Instantiate Carry_Out_Dig to carry_out. """ # Is Sum_Dig uninstantiated? If so, instantiate it to sum_digit if possible. # Then instantiate Carry_Out_Dig, and return (yield) digits_in with sum_digit removed. if not Sum_Dig.is_instantiated(): if sum_dig not in digits_in: # sum_dig is not available in digits_in. Fail, i.e., return instead of yield. return # sum_dig is available in digits_in. Give it to Sum_Dig as long as this does not give # 0 to one of the leading digits. if sum_dig != 0 or all(Sum_Dig is not LD for LD in Leading_Digits): for _ in unify_pairs([(Carry_Out_Dig, carry_out), (Sum_Dig, sum_dig)]): # Remove sum_digit from digits_in i = digits_in.index(sum_dig) yield digits_in[:i] + digits_in[i + 1:] # If Sum_Dig is instantiated, is it equal to sum_digit? # If so, instantiate Carry_Out_Dig and return the current digits_in. elif sum_dig == Sum_Dig.get_py_value(): for _ in unify(Carry_Out_Dig, carry_out): yield digits_in
def fill_column(PVs: List[PyValue], index: int, digits_in: List[int]): """ PVs are the digits in the current column to be added together, one from each term. digits-in are the digits that have not yet been assigned to a Var. Find digits in digits_in that make the column add up properly. Return (through yield) the digits that are not yet used after the new assignments. We do this recursively on PVs--even though we are currently assuming only two terms. """ if not PVs: # We have instantiated the digits to be added. # Instantiate Sum_Dig (if possible) and Carries[index - 1] to the total. # Completing the column is a bit more work than it might seem. (carry_in, digit_1, digit_2) = (D.get_py_value() for D in [Carries[index], Term1[index], Term2[index]]) total = sum([carry_in, digit_1, digit_2]) (carry_out, sum_dig) = divmod(total, 10) yield from complete_column(carry_out, Carries[index - 1], sum_dig, Sum[index], digits_in, Leading_Digits) else: # Get head and tail of PVs. [PV, *PVs] = PVs # If PV already has a value, nothing to do. Go on to the remaining PVs. if PV.is_instantiated(): yield from fill_column(PVs, index, digits_in) else: # Give PV one of the available digits. Through "backup" all digits will be tried. for i in range(len(digits_in)): if digits_in[i] != 0 or all(PV is not LV for LV in Leading_Digits): for _ in unify(PV, digits_in[i]): yield from fill_column( PVs, index, digits_in[:i] + digits_in[i + 1:])
def transversal_yield_lv(sets: List[PyList], so_far: PyList, Answer: Var): print(f'sets/[{", ".join([str(S) for S in sets])}]; so_far_reversed/{reversed(so_far)}') if not sets: yield from unify(reversed(so_far), Answer) else: [S, *Ss] = sets X = Var( ) for _ in member(X, S): for _ in fails(member)(X, so_far): yield from transversal_yield_lv(Ss, PyList([X]) + so_far, Answer)
def transversal_yield_lv(Sets: List[PyList], Partial_Transversal: PyList, Complete_Transversal: Var): print( f'Sets/[{", ".join([str(S) for S in Sets])}]; Partial_Transversal/{Partial_Transversal}' ) if not Sets: yield from unify(Partial_Transversal, Complete_Transversal) else: (S, Ss) = (Sets[0], Sets[1:]) Element = Var() for _ in member(Element, S): for _ in fails(member)(Element, Partial_Transversal): yield from transversal_yield_lv( Ss, Partial_Transversal + PyList([Element]), Complete_Transversal)
def __getitem__(self, key: Union[int, slice]): (prefix, tail) = self.prefix_and_tail() stop = key if isinstance(key, int) else \ key.stop if isinstance(key.stop, int) else \ len(self) if key.stop is None else \ None if len(prefix) >= stop: slice_elements = prefix[key] if isinstance(key, int): return slice_elements else: return LinkedList(slice_elements) if not isinstance(tail, Var): return None template = LinkedList(n_Vars(stop)) for _ in unify(self, template): (prefix, _) = template.prefix_and_tail() return LinkedList(prefix[key])
def append(Xs: Union[PySequence, Var], Ys: Union[PySequence, Var], Zs: Union[PySequence, Var]): """ append([], Ys, Zs). append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs). See discussion in linked_list version. This version assumes we are working with Python lists or tuples, i.e., no uninstantiated tails. """ if isinstance(Zs, Var) and (isinstance(Xs, Var) or isinstance(Ys, Var)): # Can't have Xs or Ys Var if Zs is Var. return if isinstance(Zs, Var): ListType = type(Xs) # Make Zs a list of Var's of length len(Xs) + len(Ys) # After this unification, Zs will still be a Var, but # by the time we reach this point again, Zs will refer # to its trail end, which is not a Var. for _ in unify(Zs, ListType(n_Vars(len(Xs) + len(Ys)))): yield from append(Xs, Ys, Zs) return # We now know that: Zs is not a Var -- although it may be a sequence of Vars. # Divide up its length among Xs and Ys ListType = type(Zs) len_Zs = len(Zs) for i in range(len_Zs + 1): # If Xs or Ys are already instantiated to some fixed length Sequence, unify will fail when given the wrong length. for _ in unify_pairs([(Xs, ListType(n_Vars(i))), (Ys, ListType(n_Vars(len_Zs - i)))]): # Although the lengths of Xs and Ys vary with i, # Xs, Ys, and Zs are all of fixed lengths in which len(Xs) + len(Ys) = len(Zs). # Concatenate Xs and Ys and then unify the concatenation with Zs. XYs = [ *Xs.unification_chain_end().args, *Ys.unification_chain_end().args ] yield from unify_sequences(XYs, Zs.args)
def place_remaining_queens(placement: List[PyValue]): """ Find a safe spot for the next queen and eithe quit if it's the last position or call this recursively. """ # next_col is the next column to be instantiated next_col = len([c for c in placement if c.is_instantiated()]) for d in range(len(placement)): for _ in unify(placement[next_col], d): # Note that there is no 'else' for if is_safe(placement). Whether or not d is a safe position, # we go on to the next value after processing it. if is_safe(placement): # Have we filled the board? If so, next_col will be, e.g., 7, and len(placement) will be 8. last_col = len(placement) - 1 if next_col == last_col: # Found a solution; yield. yield # More queens to place. else: # Find columns for the remaining queens. yield from place_remaining_queens(placement)
def is_a_subsequence_of(As: List, Zs: SuperSequence): """ As may be spread out in Zs but must be in the same order as in Zs. """ if not As: # If no more As to match, we're done. Succeed. yield elif not Zs: # If no more Zs to match the remaining As, fail. return else: for _ in forany([ # Match As[0] and Zs[0]; go on to is_a_subsequence_of(As[1:], Zs[1:]) lambda: forall([lambda: unify(As[0], Zs[0]), lambda: is_a_subsequence_of(As[1:], Zs[1:])]), # Whether or not we matched As[0] and Zs[0] above, try is_a_subsequence_of(As, Zs[1:]) lambda: is_a_subsequence_of(As, Zs[1:]) ]): yield
def connected(S1: PyValue, Line_Dist: PyValue, S2: PyValue): # S1: Union[PyValue, Var], Line_Dist: Var, S2: Union[PyValue, Var """ Are stations S1 and S2 connected on the same train line? If so, which line is it, and how many stations are between them? Line_Dist will be unified with (line, count_of_stations) """ # print(f'-> connected({S1}, {Line_Dist}, {S2})?') Line = PyValue() # Can use either forall or nested for _ in has_station's # for _ in forall([lambda: has_station(Line, S1), # lambda: has_station(Line, S2)]): for _ in has_station(Line, S1): for _ in has_station(Line, S2): # Ensure that S1 != S2 if S1 != S2: line = Line.get_py_value() stations = lines[line] pos1 = stations.index(S1.get_py_value()) pos2 = stations.index(S2.get_py_value()) yield from unify(Line_Dist, (line, abs(pos1 - pos2)))
def zebra_problem(Houses): for _ in forall([ # 1. The English live in the red house. lambda: member(House(nationality='English', color='red'), Houses), # lambda: print_SF(f'After 1: {Houses}', 'Succeed'), # 2. The Spanish have a dog. lambda: member(House(nationality='Spanish', pet='dog'), Houses), # lambda: print_SF(f'After 2: {Houses}', 'Succeed'), # 3. They drink coffee in the green house. lambda: member(House(drink='coffee', color='green'), Houses), # lambda: print_SF(f'After 3: {Houses}', 'Succeed'), # 4. The Ukrainians drink tea. lambda: member(House(nationality='Ukrainians', drink='tea'), Houses ), # lambda: print_SF(f'After 4: {Houses}', 'Succeed'), # 5. The green house is immediately to the right of the white house. lambda: is_contiguous_in( [House(color='white'), House(color='green')], Houses), # lambda: print_SF(f'After 5: {Houses}', 'Succeed'), # 6. The Old Gold smokers have snails. lambda: member(House(smoke='Old Gold', pet='snails'), Houses), # lambda: print_SF(f'After 6: {Houses}', 'Succeed'), # 7. They smoke Kool in the yellow house. lambda: member(House(smoke='Kool', color='yellow'), Houses), # lambda: print_SF(f'After 7: {Houses}', 'Succeed'), # 8. They drink milk in the middle house. # Note the use of a slice. Houses[2] picks the middle house. lambda: unify(House(drink='milk'), Houses[2]), # lambda: print_SF(f'After 8: {Houses}', 'Succeed'), # 9. The Norwegians live in the first house on the left. lambda: unify(House(nationality='Norwegians'), Houses.head()), # lambda: print_SF(f'After 9: {Houses}', 'Succeed'), # 10. The Chesterfield smokers live next to the fox. lambda: next_to(House(smoke='Chesterfield'), House(pet='fox'), Houses), # lambda: print_SF(f'After 10: {Houses}', 'Succeed'), # 11. They smoke Kool in the house next to the horse. lambda: next_to(House(smoke='Kool'), House(pet='horse'), Houses), # lambda: print_SF(f'After 11: {Houses}', 'Succeed'), # 12. The Lucky smokers drink juice. lambda: member(House(drink='juice', smoke='Lucky'), Houses), # lambda: print_SF(f'After 12: {Houses}', 'Succeed'), # 13. The Japanese smoke Parliament. lambda: member(House(nationality='Japanese', smoke='Parliament'), Houses), # lambda: print_SF(f'After 13: {Houses}', 'Succeed'), # 14. The Norwegians live next to the blue house. lambda: next_to(House(nationality='Norwegians'), House(color='blue' ), Houses), # lambda: print_SF(f'After 14: {Houses}', 'Succeed'), # Fill in unmentioned properties. lambda: members([House(pet='zebra'), House(drink='water')], Houses), ]): yield
def is_even_1(i: int) -> Generator[None, None, None]: for _ in unify(PyValue(True), PyValue(i % 2 == 0)): yield
def clue_8(self, Houses: SuperSequence): """ 8. They drink milk in the middle house. Note the use of a slice. Houses[2] picks the middle house. """ yield from unify(House(drink='milk'), Houses[2])
]): yield if __name__ == '__main__': print(emptyLinkedList) E = PyValue(3) for _ in member(E, emptyLinkedList): print(f'Error: should not get here.') A = LinkedList((Var(), Var())) A112 = A[4:11:2] A37 = A[3:7] print(f'\nA: {A}\nA[3:7]: {A37}\nA[4:11:2]: {A112}') for _ in unify(A37, LinkedList('ABCD')): print(f'\nA: {A}\nA[3:7]: {A37}\nA[4:11:2]: {A112}') print() print(f'A[:4]: {A[:4]}') A_tail = A.tail() print(f'A.tail()[:3]: {A_tail[:3]}') print(f'A.tail().tail()[:2]: {A.tail().tail()[:2]}') print(f'\nemptyLinkedList: {emptyLinkedList}') Xs = LinkedList([*range(10)]) print(f'\nSlices: Xs: {Xs}') print(f'\tXs[2:-1:2]: {Xs[2:-1:2]}') print(f'\tXs[-1:2:-2]: {Xs[-1:2:-2]}') print(f'\tXs[5]: {Xs[5]}')
def clue_9(self, Houses: SuperSequence): """ 9. The Norwegians live in the first house on the left. Instead of Houses.head(), could have written Houses[0]. """ yield from unify(House(nationality='Norwegians'), Houses.head())
Xs = Var() Ys = Var() Zs = PyList(list(range(5))) print(f'\nappend({Xs}, {Ys}, {Zs})') for _ in append(Xs, Ys, Zs): print(f'\tXs: {Xs}, Ys: {Ys}, Zs: {Zs}') Xs = Var() Ys = Var() Zs = PyTuple(tuple(range(5))) print(f'\nappend({Xs}, {Ys}, {Zs})') for _ in append(Xs, Ys, Zs): print(f'\tXs: {Xs}, Ys: {Ys}, Zs: {Zs}') X = tuple(n_Vars(15)) Y = X[4:8] print(f'\nX: {PyTuple(X)}, Y: {PyTuple(Y)}') for _ in unify_sequences(Y, tuple(map(PyValue, ['A', 'B', 'C', 'D']))): print( f"unify_sequences(Y, tuple(map(PyValue, ['A', 'B', 'C', 'D']))) => X: {PyTuple(X)}, Y: {PyTuple(Y)}" ) B = tuple(n_Vars(8)) print(f'\nB: {PyTuple(B)}') for _ in unify(B[3], PyValue('XYZ')): print(f"unify(B[3], PyValue('XYZ')) => B: {PyTuple(B)}")