The easiest solution to this problem is to set up four sets, one for each card, and then simply try every potential answer from 1 to 16. However, I'm going to use bit arithmetic to give you some practice. As I have mentioned in the past, you may represent a set of up to 32 numbers with an integer (or up to 64 numbers with an unsigned long long). A number i is in the set represented by integer s if the i-th bit of s is set. In binary arithmetic:
I'll end up with four cards, which I'll keep in a vector c. Now, what I want to do is take the intersection of four sets. If the answer to a question is 'Y', then I want the set in c. If the answer is no, then I want the set's complement. So, what I do first is go through the four answers to the questions, and if any answer is 'N', I'll set the relevant card to its binary negation using ˜. Then, I'll calculate the intersection of the four cards. The bit that is set in the result of the intersection is going to be the bit for the answer. Here's the code (NumberMagicEasy-NoPrint.cpp):
#include <string> #include <vector> #include <iostream> using namespace std; class NumberMagicEasy { public: int theNumber(string answer); }; int NumberMagicEasy::theNumber(string answer) { int i; int res; vector <int> c; c.push_back((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8)); c.push_back((1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 9) | (1 << 10) | (1 << 11) | (1 << 12)); c.push_back((1 << 1) | (1 << 2) | (1 << 5) | (1 << 6) | (1 << 9) | (1 << 10) | (1 << 13) | (1 << 14)); c.push_back((1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9) | (1 << 11) | (1 << 13) | (1 << 15)); for (i = 0; i < 4; i++) if (answer[i] == 'N') c[i] = ~c[i]; res = c[0] & c[1] & c[2] & c[3]; for (i = 1; i <= 16; i++) if (res & (1 << i)) return i; return -1; // This won't happen, but it shuts the compiler up and helps find bugs. } |
To test and run this, do the following in your own directory (besides having examples, if you give the program an argument of -1, you can enter the Y's and N's on standard input.)
UNIX> cp /home/plank/cs302/Notes/NumberMagicEasy/Main.cpp . UNIX> cp /home/plank/cs302/Notes/NumberMagicEasy/NumberMagicEasy-NoPrint.cpp NumberMagicEasy.cpp UNIX> g++ Main.cpp UNIX> a.out 0 5 UNIX> a.out 1 8 UNIX> a.out 2 16 UNIX> a.out 3 1 UNIX> echo NNNN | a.out -1 16 UNIX> echo YYNN | a.out -1 4 UNIX>If this is confusing to you, I suggest that you try the program below that prints the bits and the hex.
After doing the bitwise-and of the four sets, I calculated the return value by seeing if each element from 1 to 16 is in the final set.
Also, think about the following -- what if the input is 'NNNN'? The intersection is going to have a bunch of bits set that are not in positions 1 through 16. Why? Think about card c[0]. Originally it was:
When I negate it, it turns into
If I do that to all of the cards, then the final result will have all of those upper ones set, plus bit 0 set. However, since my code to find the final value doesn't test those bits, I don't need to worry about it. It's just something you should keep in the back of your mind.
UNIX> cp /home/plank/cs302/Notes/NumberMagicEasy-Print.cpp NumberMagicEasy.cpp UNIX> g++ Main.cpp UNIX> a.out 0 Here are the sets after being set up: c[0]: 0x000001fe 000000000000000 0000000011111111 0 c[1]: 0x00001e1e 000000000000000 0000111100001111 0 c[2]: 0x00006666 000000000000000 0011001100110011 0 c[3]: 0x0000aaaa 000000000000000 0101010101010101 0 Now let's print them after negating: (Y) c[0]: 0x000001fe 000000000000000 0000000011111111 0 (N) c[1]: 0xffffe1e1 111111111111111 1111000011110000 1 (Y) c[2]: 0x00006666 000000000000000 0011001100110011 0 (Y) c[3]: 0x0000aaaa 000000000000000 0101010101010101 0 Here's the final result: 0x00000020 000000000000000 0000000000010000 0 res has value 5 5 UNIX> a.out 1 Here are the sets after being set up: c[0]: 0x000001fe 000000000000000 0000000011111111 0 c[1]: 0x00001e1e 000000000000000 0000111100001111 0 c[2]: 0x00006666 000000000000000 0011001100110011 0 c[3]: 0x0000aaaa 000000000000000 0101010101010101 0 Now let's print them after negating: (Y) c[0]: 0x000001fe 000000000000000 0000000011111111 0 (N) c[1]: 0xffffe1e1 111111111111111 1111000011110000 1 (N) c[2]: 0xffff9999 111111111111111 1100110011001100 1 (N) c[3]: 0xffff5555 111111111111111 1010101010101010 1 Here's the final result: 0x00000100 000000000000000 0000000010000000 0 res has value 8 8 UNIX> a.out 2 Here are the sets after being set up: c[0]: 0x000001fe 000000000000000 0000000011111111 0 c[1]: 0x00001e1e 000000000000000 0000111100001111 0 c[2]: 0x00006666 000000000000000 0011001100110011 0 c[3]: 0x0000aaaa 000000000000000 0101010101010101 0 Now let's print them after negating: (N) c[0]: 0xfffffe01 111111111111111 1111111100000000 1 (N) c[1]: 0xffffe1e1 111111111111111 1111000011110000 1 (N) c[2]: 0xffff9999 111111111111111 1100110011001100 1 (N) c[3]: 0xffff5555 111111111111111 1010101010101010 1 Here's the final result: 0xffff0001 111111111111111 1000000000000000 1 res has value 16 16 UNIX>
Now, do you really think I typed in those push_back() statements? No, of course not. What I did was the following. In vi, I typed in the values that I wanted to be setting in the set:
1 2 3 4 5 6 7 8 |
Next, I put the cursor in the first line, and typed:
:,.+7s/\(.*\)/(1 << \1) | |
Before I typed RETURN, I copied that command into my clipboard. Then, I typed return:
(1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | |
Nice -- now, a bunch of capital J's gives me:
(1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | |
It's pretty easy to put that into the format that you want. Then, for the next card, simply type in the next 8 numbers, and paste the command from the clipboard, and do a bunch of capital J's. We love vi.