-3

I Took the time to translate it properly
Four people are in front of you: a male magician, a female magician, a wizard
and a witch. Each person has a bag of one or more coins.
The coins are made of bronze, copper, brass or tin.
Which bag contains the fewest coins? Given that:

1 - There are no two bags of identical content.
2 – In a bag, there cannot be two of the same coins.
3 - A bag can contain either one, two or four coins.
4 - The sorcerer and the male magician each have a coin that
none of the other three have.
5 - All bags without a brass coin contain a bronze coin.
6 - All bags without tin coins do not contain a bronze coin either.
Create a Prolog program using depth-first search to find a solution
to this problem
I don't know where to go from here

coin(bronze).
coin(copper).
coin(brass).
coin(tin).
% 5: All bags without a brass coin contain a bronze coin.
hint_1(B) :- \+ \+ ( memberchk(brass, B) ; memberchk(bronze, B) ).

% 6: All bags without tin coins do not contain a bronze coin either.
hint_2(B) :- \+ ( \+ memberchk(tin, B), memberchk(bronze, B) ).

unique_coin(Us, Bags) :-
        member(U, Us),
        \+ (member(Bag, Bags), memberchk(U, Bag)).
bag(Cs) :-
        % 3: A bag can contain either one, two or four coins
        member(L, [1,2,4]), length(Cs, L),
     % 2: In a bag, there cannot be two of the same coins.
        foldl(ascending_coin, Cs, _, _).

ascending_coin(C, Prev, C) :-
        coin(C),
        Prev @< C..

 %All bags are different
all_dif([]).
all_dif([L|Ls]) :-
        maplist(dif(L), Ls),
        all_dif(Ls).

bags(Bs) :-
        Bs = [MM,FM,Wizard,Witch],
        maplist(bag, Bs),
        % 1: There are no two bags of identical content
        all_dif(Bs),
        % 4: The Wizard and the male magician each have a unique coin.
             unique_coin(Wizard, [MM,FM,Witch]),
        unique_coin(MM, [FM,Wizard,Witch]),
        maplist(hint_1, Bs),
        maplist(hint_2, Bs).
John
  • 5
  • 3
  • I'm thinking of something like defining a 4 arguments predicate and initializing them with a list each? :( – John Feb 21 '15 at 03:36
  • 1
    I didn't downvote, but if you read the online stackoverflow.com help about how to ask a good question, you might find out why you have downvotes. If you don't know where to start with this problem, then you've chosen one that's too complex for what you know. Why not start with something simpler? Have you been through a [Prolog tutorial](http://www.learnprolognow.org)? Or check out [99 Prolog problems](http://www.ic.unicamp.br/~meidanis/courses/mc336/2009s2/prolog/problemas/) which also shows solutions. – lurker Feb 21 '15 at 12:17
  • 1
    Well i went through a series of problem on learn prolog now i stopped when i encountered these types. i just need a way to start, as i said i see this question as 4 people with a list each (and 7 restrictions) i guess i should just return the list with the least element? please help!! – John Feb 21 '15 at 15:48
  • 1
    What website did you find this problem at? Before you think about "how to return the list", you need to think about how you would represent your facts and rules in Prolog. Step through the information you're given and decide what's a fact and what's a rule. Try Googling "prolog logic problem" to get some ideas on how to approach such a problem. – lurker Feb 21 '15 at 15:52
  • i took this from a midterm in a Spain university (my hometown, and first language), i just translated this! – John Feb 21 '15 at 15:59
  • i also tried an Einstein's riddle it seemed simpler, when i saw the solution – John Feb 21 '15 at 16:02
  • 1
    @John If you will show us a bit of code indicating how you're thinking about the problem, it will be much easier for us to see what sort of understanding you have and we will be better able to provide advice. In general, the place to start is by translating the facts and rules expressed in natural language into Prolog facts and rules. – Shon Feb 21 '15 at 16:14
  • 1
    Ok, i'll go on campus...yet again :( work on it and provide you some code, i had something but my frustration made me delete it lol! – John Feb 21 '15 at 16:18
  • @aBathologist, once my code is ready, should i just update my original post? or put it in comment (even though it will be kinda long) – John Feb 21 '15 at 18:50
  • @John. fwiw, you don't even need to enter a runnable program here or anything. That means you don't have to go to campus, if it's just to get access to a machine with Prolog installed (but you maybe should install it on your personal computer if you have one? It is very easy to install a Prolog implementation). You can simply give your best effort at translating the natural language rules to Prolog (or however you think to approach it). That way we can see how your thinking about the problem, what you already understand, etc. – Shon Feb 21 '15 at 18:50
  • @John Best to update this question. View your question as a draft of a work in a progress. A good StackOverflow Question-Answer pair is often revised many times, both on the question and on the answers :) – Shon Feb 21 '15 at 18:51
  • For the campus, it's just because i can't focus at home, to much distraction. :( – John Feb 21 '15 at 18:53
  • 1
    I updated my post, i'm i on the right track? :) – John Feb 21 '15 at 19:30
  • @John I'm not familiar with `+member/2`, nor did I find info on it after a quick search. Could you explain? – Shon Feb 21 '15 at 19:35
  • \+member /2 (the back slash did not appeared for some reason. it checks whether the first argument is in the second. in my case member (bronze,A), checks if the bronze is in the list A... from what i understand – John Feb 21 '15 at 19:39
  • @John oh yes, I see! If you edit the code to read as a code block (option available in the tool bar of the edit menu, looks like `{ }`, then the code will be represented correctly. – Shon Feb 21 '15 at 20:03
  • Done i tried to do it before but i didn't know that i had to select first :) – John Feb 21 '15 at 20:09
  • I will be working on trees now, spend to much time on this (Friday night and today), if someone find the solution for this or give some some hits that will help me that will be great! – John Feb 21 '15 at 20:43
  • @lurker did find anything? – John Feb 21 '15 at 20:56
  • 1
    Hey guys i'm back, i have done some good progress and i'm updating my answer! – John Feb 26 '15 at 21:05

2 Answers2

1

Here is my attempt at a solution. It seems to work, but I'm not very well practiced with these kinds of puzzles. Comments with #n indicate that the code on that line is meant to address rule n from your puzzle.

Edit: I've updated my code to incorporate two better ideas form yours:

  1. I defined not_member/2, so I can maplist the check for unique items over the other lists. This is a compromise between generality and your unique_coin/2. Although your unique_coin/2 might just be a better option.
  2. Following you, I translated my conditionals (enforcing the "if a list doesn't have a brass coin..." rules) into disjunctions.

bag_with_fewest_coins(Answers) :-
    Bags = [A, B, C, D],                                 % there are four bags...
    maplist(coin_bag, Bags),                             % ... of coins
    is_set(Bags),                                        % #1
    member(MemA, A), maplist(not_member(MemA), [B,C,D]), % #4
    member(MemB, B), maplist(not_member(MemB), [A,C,D]), % #4
    predsort(compare_lengths, Bags, [Answer|_]). % Answer is shortest list.

not_member(X, Ys) :-
    \+ member(X, Ys).

%% To be used with predsort/3:
compare_lengths(Comp, A, B) :-
    length(A, LenA), length(B, LenB),
    compare(Comp, LenA, LenB).         % Per @mat's suggestion in comments.

coin_bag(Bag) :-
    Coins = [bronze, copper, brass, tin],          % types of coins
    member(Len, [1,2,4]), length(Bag, Len),        % #3
    set_subset(Coins, Bag),                        % #2
    (member(brass, Bag) ; member(bronze, Bag)),    % #5
    (member(tin, Bag)   ; \+ member(bronze, Bag)). % #6

% Taken form gusbro's SO answer: http://stackoverflow.com/a/4917016/1187277
% For the purposes of this predicate, each element of Set is thought of as
% distinct, qua differentiable entity, regardless of whether it would unify,
% equate with, or otherwise match another element in Set.
set_subset([], []).
set_subset([X|Set], [X|Subset]) :-
    set_subset(Set, Subset).
set_subset([_|Set], Subset) :-
    set_subset(Set, Subset).

Then, as per @mat's advice in the comments, we can use set_of/3 to be sure that we collect all distinct answers:

?- setof(B, bag_with_fewest_coins(B), Answers).
Answers = [[brass]].

Which shows that there is only one unique answer.


These tips pertained to a previous version of the question. I left them in case they are helpful to someone in the future:

It is helpful to use very descriptive names for rules and facts. rule is not helpful in that it tells us nothing about what kind of rule we're describing. In Prolog, anything of the form <head> :- <body>. is a rule, so we know something is a rule just based on its syntax. That frees us up to name our rules something descriptive, e.g., bag_contents.

Some of your rules don't make much sense to me, and I think this is because you're slightly misunderstanding what the expressions are in Prolog. So here are some pointers on your rules:

I expect you mean this rule to say "M2 is the first element of a list and it is a either a list with length 2, or a list with length 4":

rule(M2,[M2|_]):- length(M2,X), X=2; X = 4.

But it actually says "M2 has length X and X = 2, or X = 4", and this is because the precedence of th ;/2 operator is greater than that of the ,/2 operator. Thus, you need to use parenthesis to group the expressions in the right way—like so: length(M2,X), (X=2; X = 4).

The second disjunct of this rule will never succeed:

rule([A|B]):- length(A,Y), Y= 1; Y = 2, Y=4, rule(B).

Because the second part (after the ;) reads "Y = 2 and Y = 4 and rule(B)", but if Y = 2 then \+ Y = 4, so it will always fail.

So, my recommendation for further progress is to give your rules descriptive, distinct names, correct the flawed logic in some clauses, and try setting up cases where you can test rules in the top level as you go.

Shon
  • 3,989
  • 1
  • 22
  • 35
  • Thanks for your time! prolog isn't just for me :(, for the 2nd disjunct it was a typo, i meant Y=2 followed with a semi colon – John Feb 21 '15 at 21:44
  • @John Well, it just takes time! There's a lot to learn. You may be trying to tackle problems that are beyond you're knowledge-level at the moment. Are you working through a book or course or something? – Shon Feb 21 '15 at 21:46
  • i'm taking a prolog course but since i'm not quite comfortable with it, i decided to do random problem online from what we learned i should be able to solve this with the time i spent on it – John Feb 21 '15 at 21:49
  • 1
    PS: thanks for the 99 problems, doing them and gosh i learn so much – John Feb 22 '15 at 00:35
  • @John that tip was from lurker. But the 99 problems are very good training, and I'm glad you are trying them. There a lot of little details to learn when learning a totally new paradigm, and the 99 problems helps you discover them gradually. Remember not to be too impatient with yourself :) – Shon Feb 22 '15 at 00:42
  • @John On a cursory glance, It looks like you made tons of progress on your understanding of Prolog idioms, predicates, and analysis of problems for the sake of a declarative programming solutions. Good work and congratulations on that :) I'll see if I can make any progress on this problem myself and get back to you if I do. – Shon Feb 27 '15 at 02:59
  • @John My pleasure. At this point it's just recreational, collaborative puzzle solving :) Once I figure out a solution, I'll update my answer with it and we can compare my solution to what you've got so far. Maybe a more knowledgeable Prologger will even come by and show us both how it's done ;) – Shon Feb 27 '15 at 03:33
  • Maybe the puzzle is a bit too challenging for the prologger too lol!! – John Feb 27 '15 at 05:04
  • @John That's certainly not the case :) I added my first go at a solution. Let me know if you have any questions. – Shon Feb 27 '15 at 05:36
  • 1
    Yah, it was a joke :) ! Thanks for your answer very clean code! but since i need to know who has the smallest bag, and the query coin_bag(Ans) only return true, how do i fix this? – John Feb 27 '15 at 06:04
  • 1
    I will try to figure out in the meantime :), in other word, is it the Magician Witch or Wizard who has it :) – John Feb 27 '15 at 06:05
  • @John Sorry! That was a typo on my part. I have corrected it. `coin_bag(X)` should unify `X` with a list of coins, satisfying the specifications. Use `bag_with_fewest_coins/1` to get the smallest bag. – Shon Feb 27 '15 at 06:08
  • 1
    THANKS a lot for solving this! – John Feb 27 '15 at 06:09
  • @John I hope looking at my solution helps. But you obviously made a lot of progress in trying to work through the problem yourself. Even if you didn't figure out everything exactly right, you seem to be grasping the essential strategy of breaking these problems down into declarative specifications of the facts and constraints that describe the problem space. That's great :) – Shon Feb 27 '15 at 06:14
  • @John In fact, I think there are several ways in which your code exhibits a more cleanly LP approach then mine. If I get time and energy in the following few days maybe I'll borrow some strategies from your work... – Shon Feb 27 '15 at 06:16
  • Oh yeah, your solution definitely helped, can't thank enough. And i learned a lot since last time, thank to your hits+ answers and the 99 problems from @lurker – John Feb 27 '15 at 06:25
  • Sorry to come back but i was going through deeply and i found that if i query coin_bag(Ans) i will get an answer. But this particular function never calls `bag_with_fewest_coins(Answer)` nor compare_length (i tested by deleting them). On the other hand if i query `bag_with_fewest_coins(Answer)` i get false.. – John Feb 27 '15 at 06:46
  • i'll do some research about the predsort/3 predicate – John Feb 27 '15 at 06:52
  • @John The organization of my code might have been confusing. The predicates are now ordered from more general to specific. As you observed, `coin_bag/1` does not call `bag_with_fewest_coins/1` or `compare_length/3`. But `bag_with_fewest_coins/1` does call both of those: `coin_bag/1` is mapped over the list `Bags` and `compare_lengths/3` is used as the comparison predicate for `predsort/3`. I'm not sure why you're getting `false` when querying `bag_with_fewest_coins/1` though... it's working for me... which Prolog implementation are you using? – Shon Feb 27 '15 at 11:00
  • @John I added some ideas from your code into mine. Also, a pedantic point that is really helpful in "thinking in Prolog": we call the procedures "predicates" not "functions". It helps to refer to them correctly, because it remind us that predicates don't work like functions—the differences are crucial. Re: [`predsort/3`](http://www.swi-prolog.org/pldoc/doc_for?object=predsort/3). – Shon Feb 27 '15 at 11:23
  • 1
    I'm sorry it was my fault, i had other predicate of the same names! created a new file and it's working like a charm :) thank you – John Feb 27 '15 at 13:24
  • 1
    It is better to use `setof/3` around the main predicate to collect all solutions instead of arbitrarily committing to the first one here. After removing the `!/0`, when the query `setof(B, bag_with_fewest_coins(B), [Bag]).` succeeds, you know that the solution is indeed unique. An alternative is to obtain all solutions and check this manually as demonstrated [here](http://www.reddit.com/r/learnprogramming/comments/2wwbco/prolog_puzzle/). Especially for larger problems, the `setof/3` version seems much preferable. – mat Feb 27 '15 at 14:16
  • Thanks for the tip, @mat! I made the change as you suggested. I did feel a bit dirty when I made that cut. – Shon Feb 28 '15 at 00:09
  • 1
    Good, but that's not yet what I meant: the predicate is currently still not deterministic. The scope of `setof/3` must be extended to catch all matching bags in one swoop. If you do it inside `bag_with_fewest_coins/1`, then you will have to pull more goals into the call, and use existential quantification for some variables. Alternatively, wrap the whole invocation, as I have shown above. – mat Feb 28 '15 at 18:17
  • 1
    Also check out `compare/3` for the three-way comparison: `compare(Comp, LenA, LenB)`. – mat Mar 01 '15 at 09:48
  • @mat Thanks! I don't know what I was thinking when I used `setof/3` on `predsort/3`! In fact, I must not have been thinking at all. But thanks for the correction and the suggestion to use `compare/3`! Much better :) – Shon Mar 01 '15 at 12:20
-1

Try http://swish.swi-prolog.org/ this will work.. there maybe some problem SWI-Prolog desktop edition. The desktop veriosn can't check build in memberchek and foldl function.. hope this helps

  • I didn't down-vote you. But, fyi, it sounds like you meant to link to some runnable code on swish, yet your link just goes to swish itself, to a blank start-up state. – Shon Mar 01 '15 at 12:08
  • I simply meant so suggest to use swish as an editor to the 1st code.. SWI-Prolog for desktop doesn't support some of the function.. – user3455379 Mar 02 '15 at 07:22
  • Then I'm afraid I don't understand, because SWI Prolog definitely has [`memberchk/2`](http://www.swi-prolog.org/pldoc/doc_for?object=memberchk/2) and [`foldl/4`(http://www.swi-prolog.org/pldoc/doc_for?object=foldl/4)]. It is my understanding that, in contrast, swish runs under a severely limited instruction set. – Shon Mar 02 '15 at 07:56