This forum is in permanent archive mode. Our new active community can be found here.

Logic Puzzle 2 wrap-up

edited November 2009 in Everything Else
The Rules:
  1. No visible posting of answers/hints/etc. for the first 24 hours after this is posted, i.e., whisper to self for later reveal.
  2. If you want bragging rights (since un-whispering will show up as an "edit"), copy paste the text of your whispered answer into a text file and post the md5 check sum.
  3. This puzzle has two solutions. After 24 hours I'll check to see if anyone has figured out the hard one. If yes, I'll stand in awe of the forum member in question (at least, in awe of his/her Googling skills). If no, I'll start dropping some hints.
  4. "Punctuation Fucking" (trans: Fin. "nit picking") is not the aim here. Nor is "thinking outside the box" (as commendable as that may be). If the puzzle is unclear whisper me, if you have "funny" solutions, wait 24 hours ;-).
A hypothetical computer program:
The main routine M has a 128 bit key A.
In an infinite loop:
  • The main routine will call at random a subroutine S_###, with ### a number between 000 and 127.
  • The subroutine will be handed two arguments:
    • the value of the bit in A, corresponding to the subroutines number, e.g. S_003 will get the value of the third bit in A.
    • the loop counter.
  • The subroutines can get() and set() the value of one global (all subroutines can see it) parameter P_g, with the length of 1 bit (0 or 1)
  • The subroutines can get() and set() the value of one local (every subroutine has it's own) parameter P_l###, with infinite length
  • The subroutines must exit with a 0 or 1.
  • If the subroutine exist with a 0, the main routine continues the loop
If at any point a subroutine exits with a 1, M will exit the loop and concatenate the first bits on all the local variables P_l###, in order, into a 128 bit key B.

Question:
How do you program the subroutines in such a way that the program will eventually stop with A==B? Effectively, how do you program the subroutines to "know" when they all have all been called by the main routine at least once?
How many loop iterations does your solution take, and can you prove that your solution is optimal (i.e. takes the least number of loop iterations)?

Remember:
The only changing variables are P_g, P_l### (and the loop counter, which you don't have access to).

To clarify:
  • P_g and P_l### are static, i.e. they remain intact between different calls to subroutines. So, e.g., on loop iteration 403 S_032 sets P_l032 to a value (e.g. 001101100010101) before exiting and when S_032 is called the next time on, e.g., loop iteration 742, it can get the value of P_l032 (001101100010101) back.
  • On the other hand, a subroutine on loop iteration 100 will get the value of P_g (0 or1) that the subroutine on iteration 99 set.
  • The subroutines can only be called from the main routine, not recursively from within the subroutines (nice one Frank). If this were allowed the puzzle would have an easy solution, not just an "easy" solution ;-).
The Original Problem:
There are 100 prisoners in solitary cells. There's a central living room with one light bulb; this bulb is initially off. No prisoner can see the light bulb from his or her own cell. Everyday, the warden picks a prisoner equally at random, and that prisoner visits the living room. While there, the prisoner can toggle the bulb if he or she wishes. Also, the prisoner has the option of asserting that all 100 prisoners have been to the living room by now. If this assertion is false, all 100 prisoners are shot. However, if it is indeed true, all prisoners are set free and inducted into MENSA, since the world could always use more smart people. Thus, the assertion should only be made if the prisoner is 100% certain of its validity. The prisoners are allowed to get together one night in the courtyard, to discuss a plan. What plan should they agree on, so that eventually, someone will make a correct assertion?

Solution:
pdf
«1

Comments

  • I am nowhere near nerdly enough to understand any of that moon-speakish gobbeldygook.
  • edited November 2009
    The non gobbledygook version, please keep to yourself for 24 hours:

    There are 100 prisoners in solitary cells. There's a central living room with one light bulb; this bulb is initially off. No prisoner can see the light bulb from his or her own cell. Everyday, the warden picks a prisoner equally at random, and that prisoner visits the living room. While there, the prisoner can toggle the bulb if he or she wishes. Also, the prisoner has the option of asserting that all 100 prisoners have been to the living room by now. If this assertion is false, all 100 prisoners are shot. However, if it is indeed true, all prisoners are set free and inducted into MENSA, since the world could always use more smart people. Thus, the assertion should only be made if the prisoner is 100% certain of its validity. The prisoners are allowed to get together one night in the courtyard, to discuss a plan. What plan should they agree on, so that eventually, someone will make a correct assertion?
    Post edited by Dr. Timo on
  • I am nowhere near nerdly enough to understand any of that moon-speakish gobbeldygook.
    Yes, there is a non-gobbledygook version of this riddle, but to avoid Google spoilage, I'm keeping it to myself for at least 24 hours. I've also clarified some points in the problem to make it slightly easier.
  • the value of the bit in A, corresponding to the subroutines number, e.g. S_003 will get the value of the third bit in A.
    Do you mean the third bit from the left or the right? big-endian? little-endian? two's complement?
  • edited November 2009
    the value of the bit in A, corresponding to the subroutines number, e.g. S_003 will get the value of the third bit in A.
    Do you mean the third bit from the left or the right? big-endian? little-endian? two's complement?
    Dude! I beg of you not to make fun of my usage of programming terms here ;-). A computer program was the easiest analog I could think of and suits itself to the rigidity of a logic puzzle.

    Edit: Wait, was this an attempt to get me to call you a pilkunnussija?
    Post edited by Dr. Timo on
  • I am nowhere near nerdly enough to understand any of that moon-speakish gobbeldygook.
    Me neither. I'm just gonna say that we're artists, and our brains are built for more beautiful things.
  • edited November 2009
    The non gobbledygook version, please keep to yourself for 24 hours:

    There are 100 prisoners in solitary cells. There's a central living room with one light bulb; this bulb is initially off. No prisoner can see the light bulb from his or her own cell. Everyday, the warden picks a prisoner equally at random, and that prisoner visits the living room. While there, the prisoner can toggle the bulb if he or she wishes. Also, the prisoner has the option of asserting that all 100 prisoners have been to the living room by now. If this assertion is false, all 100 prisoners are shot. However, if it is indeed true, all prisoners are set free and inducted into MENSA, since the world could always use more smart people. Thus, the assertion should only be made if the prisoner is 100% certain of its validity. The prisoners are allowed to get together one night in the courtyard, to discuss a plan. What plan should they agree on, so that eventually, someone will make a correct assertion?
    Post edited by Dr. Timo on
  • I am nowhere near nerdly enough to understand any of that moon-speakish gobbeldygook.
    Me neither. I'm just gonna say that we're artists, and our brains are built for more beautiful things.
    If people really are baffled by the description of the problem they can whisper me up for a more clear text version.
  • edited November 2009
    Quick question: are the P_l[n] variables static? (i.e. do they continue to exist and hold the same value once S_[n] has finished?)

    edited: changed notation from angle brackets to square.
    Post edited by Frank on
  • edited November 2009
    Edit: Wait, was this an attempt to get me to call you a pilkunnussija?
    The proper translation is comma fucking.
    Post edited by Anastius on
  • Quick question: are the P_l[n] variables static? (i.e. do they continue to exist and hold the same value once S_[n] has finished?)
    edited: changed notation from angle brackets to square.
    Yes, that is the idea! I'll clarify this in the original post.
  • "Punctuation fornicator" is now my new favorite phrase.
  • edited November 2009
    Edit: Wait, was this an attempt to get me to call you a pilkunnussija?
    The proper translation iscommafucking.
    I adhere to dynamic translations and prefer the sound of punctuation fucker. Although I must say that there is really no word in the English language to quite convey all the subtleties of the word "nussia", hence fornicator as a means to differentiate from the more mundane fucker.
    Post edited by Dr. Timo on
  • We got a similar expression to "punction fornicator" here in Austria, only a lot less vulgar: I-Tüpferl-Reiter which roughly translates to "I-dot rider" as in someone who nitpicks even how the I's are dotted. "Auf etwas herumreiten" is a german expression for being unable to leave something, normally just a small detail, alone.

    BTW, since I just remembered. Rym and Scott, your annunciation of "Karlsruhe" was decent enough :)
  • I adhere to dynamic translations and prefer the sound of punctuation fucker. Although I must say that there is really no word in the English language to quite convey all the subtleties of the word "nussia", hence fornicator as a means to differentiate from the more mundane fucker.
    Fornicate is a step towards the wrong direction. I do agree there should be differentiation between nussija and the common public.
  • edited November 2009
    I take the relatively slow acumulation of comments on this thread as good evidence of people having a hard time figuring this out >:-). If you feel you are stuck and really, really want a hint, here are two (use at your own risk)

    Small hint: You really don't need the loop counter at all for the "easy" solution.

    Humongous hint: The total amount of memory needed for all the P_l###'s is 263 bits for the "easy" solution and about a few kilobits for the hard solution, the "infinite length" is a red herring.

    I'm going to sleep now, have fun!

    Edit: I previously miscalculated the amount of bits neccessary by the minimal solution.
    Post edited by Dr. Timo on
  • edited November 2009
    subroutine S_### {
      if (P_g == 0) {
        P_g = 1;
        P_l###[0] = A[###];
        P_l###[1] = 1;
        if (S_000 && S_001 && ... && S_127)
          return 1;
        else {
          P_g = 0;
          return 0;
        }
      }
      else {
        if (P_l###[1] == 0)
          return 0;
        else
          return 1;
      }
    }
    Pros: Low memory use (each P_l### is only two bits big)
    Cons: Computation is O(n^2)

    EDIT: This is pretty much the same solution Timo posted below as the (cheating) solution, except this only uses 256 bits.
    Post edited by theknoxinator on
  • I'm pretty sure I got the easy method, since I whispered it just before I read Timo's last two hints. But now this is going to be on my mind all day until I can think of how the hard method would work.
  • edited November 2009
    EDIT: This has since been revealed as incorrect, as the with the prisoner metaphor, it effectively allows each prisoner to slip a note to the prisoner at the end of the block who's tracking the notes he's received, and can then tap or not tap on his cell door.

    note 1:
    I'll be modifying the notation for a bit more readability.
    S[n] indicates subroutine number n.
    Gbit is the global bit P_g
    local[n].b indicates the bth bit of subroutine n's local store, starting from b = 0.

    first off, let's clarify your definition of main.
    main {
    loopcounter = 0
    Gbit = 0
    do {
    n = random int from (0..127)
    rv = S[n](A.n, loopcounter)
    loopcounter ++
    } while ( rv == 0 )
    // at this point, an S has returned a 1

    for b = 0 to 127 {
    B.n = local[n].0
    } // This is the bit copy. The assumption here is that main can see the locals of all of the S.
    }


    My first thought is to kind of cheat using recursion.

    S[n] (bit_of_A, loopcounter) {
    if local[n].1 == 0, {
    set local[n].0 = bit_of_A
    set local[n].1 = 1
    call S[n+1 mod 128] ( next bit_of_A, loopcounter)
    }
    return 1
    }


    Each subroutine calls the next one, and so by induction, each sub gets called once (except the first, which gets called again, sees a 1 in its P's second bit, and jumps straight to the return, after which all the previous calls return.)
    This gives a total of 129 subroutine calls, and completely bypasses the loop inside main.

    problem: this only works if A is globally visible. if it's restricted to main, S[n] can't see A to pass it's n+1th bit to the next S.
    -- hence: above idea does not work.
    In the prisoner metaphor, this is allowing each prisoner to let the next prisoner into the room.

    If A is only scoped to main, then each S[n] MUST be called from main, otherwise, they can not get the A.n bit as an argument, as no other function has A to pass to another function.
    In this case, the only way to guarantee that main halts is to ensure that each S will return 1 after a finite number of calls, but this doesn't ensure that all S will be called by main.

    -- second idea. --

    This is going to abuse the purpose of the arguments to S[n].
    Assumption is that the loop counter argument is at least 7 bits long.
    The state of Gbit will have a well-defined meaning at the start of each S, namely:
    0: this S was called by main.
    1: this S was called by something else.
    (meaning, before each call, we have to set Gbit.


    S[n] ( Abit, lc) {
    if n == 0 {
    // we'll be using S[0] as a specialized tracker subroutine.
    if Gbit == 0 {
    local[0].0 = Abit
    local[0].128 = 1
    }
    else { // called by another S
    //here, we use lc as an informational channel, indicating which S called S[0].
    //(this is S[0]'s local copy of lc, so we're not violating main's loopcounter.)
    if lc in (0..127) {
    // bits 0..127 will build up in local[0] a local copy of A, bits 128..255 will index which bits we've gotten.
    if local[0].(lc+128) == 0 {
    local[0].lc = Abit
    local[0].(lc+128) = 1
    }
    }
    }
    if local[0].(128..255) are all 1 {
    return 1
    } else {
    return 0
    }
    }
    else { // n is in (1..127)
    if Gbit == 0 {
    local[n].0 = Abit
    Gbit = 1
    rv = S[0] ( Abit, n ) // send S[0] the bit we got, and where to put it.
    Gbit = 0
    return rv // S[0] will return 1 once all S have reported to it, and this passes the return from S[0] to main.
    } // no one should call anything other than S[0] when Gbit is 1.
    }
    }


    Time factor is constant for each S. S-calls from main will call S[n], who calls S[0]. The only loop is in S[0]'s all 1 check, and that's a constant time test of 128 bits.

    Now, with main being random, there's no way to ensure that all S will be called, but it's a probability thing.
    The first rand roll will always get a new n, with probability 128/128, after k different n have been rolled, the probability of rolling a new n is (128-k)/128.
    This is a Bernoulli process, so the number of rolls before every new number will follow a geometric probability distribution.

    This gives an expected number of rolls before a new n as 1/P(new n), or 128/(128-k).
    Summing these expected numbers of rolls for an expected total calls from main gives 695.44 with a standard deviation of 161.64.
    Worst case is that main perversely never calls a certain n, and thus never halts; best case is no misses, for 128 main calls, 255 total S calls.
    Post edited by Frank on
  • edited November 2009
    Edit: okay, it's really not a formal proof, just justification and time analysis.

    I'm also wasting a good 128 bits of storage, it seems.

    edit again: md5 sum dropped, had to fix an unfinished sentence, CBA to recalculate the md5.

    Edit 3: Since the 24 hour embargo's over, revealing my attempt which kind of broke the conditions.
    Post edited by Frank on
  • Since it's phrased as a programming problem, if anyone is interested we can set up a basic framework for the puzzle.
  • What is the initial value of P_g?
  • edited November 2009
    The initial values of P_g and P_l can be assumed to be 0 (the problem doesn't get much more difficult if the values are initially random).

    Edit: Only four more hours before I start dropping serious hints and any glory of having solved the puzzle diminished ;-).
    Post edited by Dr. Timo on
  • OK, I have to go into town meet some peeps, so I'm cutting the 24 hours short by three. Here is the original problem
    There are 100 prisoners in solitary cells. There's a central living room with one light bulb; this bulb is initially off. No prisoner can see the light bulb from his or her own cell. Everyday, the warden picks a prisoner equally at random, and that prisoner visits the living room. While there, the prisoner can toggle the bulb if he or she wishes. Also, the prisoner has the option of asserting that all 100 prisoners have been to the living room by now. If this assertion is false, all 100 prisoners are shot. However, if it is indeed true, all prisoners are set free and inducted into MENSA, since the world could always use more smart people. Thus, the assertion should only be made if the prisoner is 100% certain of its validity. The prisoners are allowed to get together one night in the courtyard, to discuss a plan. What plan should they agree on, so that eventually, someone will make a correct assertion?
    You may verify for yourself that the essence of the problem is carried over into the puzzle I wrote up.

    Yes, I deliberately framed the problem so that it would not be easy to Google for it. The "100 prisoners and light bulb" puzzle is one of my favorite puzzles since it doesn't lend itself to solution by reduction and induction (i.e. start with 1 prisoner, then 2, then 3 and figure out the pattern) like the famous "blue eyes, brown eyes" puzzle. Sadly the puzzle has become very popular (and I liked it before it went mainstream!!) and thus solutions abound on the interwebs.

    If you still want to figure it out for yourself, here is another hint: The "easy" solution is much, much easier than you'd think and will take around 15000 loop iterations (or 10000 days in the prisoner problem).
  • edited November 2009
    I put together some Python (2.6) for the problem. It's here on pastebin. Currently, the code (by default) tests a "cheat" solution, which is disallowed because CheatSub accesses the main routine's information as to which methods have been called.

    Note that I've simplified all of the 128-bit key stuff down to the idea that we only need to know that each subroutine has been called at least once.

    A solution to the problem consists of one or more subclasses inheriting from Sub, which must define a __call__ method with a single argument (the value of the counter), and a list of these classes (i.e. as uninstantiated types) to use for the subroutines;
    [CheatSub] * 128 is an example of this. Please note the following:
    • The only access to the main routine allowed is via calls to self.main.setGlobal and self.main.getGlobal.
    • The local variables are achieved using instance variables.
    • Class variables are disallowed.
    • The subroutines are not allowed any access to the other subroutines.
    • The first iteration of the loop is number 1, not 0.
    For anyone interested, I have an implementation of the "easy" solution Timo mentioned. I calculate the average number of iterations to be 16905.77 from 1000 runs.
    Post edited by lackofcheese on
  • edited November 2009
    The cheating solution, first suggested by Frank, is an interesting byproduct of my posing this puzzle as a programming question. A more simple approach, using his idea of iterative calls to S, can be constructed as follows (using Frank's pseudo code notations):
    S[n] (Abit, Nloop) {
    if Gbit == 0 { // if called from main
    local[n].0 = Abit // remember the part of A given to me
    local[n].1 = 1 // bit that says "I have been called at least once"
    Gbit = 1
    for m (0..127) { // call all subroutines
    local[n].(2..8) += S[m] (0,0) // local[n].(2..8) is a 7 bit counter
    }
    if local[n].(2..8) == 0b1111111 { // if all routines report "yes"
    return 1 // exit FTW!
    }
    clear local[n].(2..8) // otherwise, cleanup and
    Gbit = 0
    return 0 // back to work
    }
    // so I'm called by another S
    if local[n].1 == 1 { // if I have already been called by M at least once
    return 1 // say "yes"
    }
    return 0 // if not, say no
    }

    This (cheating) solution is optimal in that it will immediately terminate when all subroutines have been called at least once. Also note that you don't need the loop counter!

    This solution uses 128 bits for storing A, 128 bits for the "I have been called" markers, and 7 bits for a counter which can be released after use, for a total of 263 bits. Interestingly, this is exactly one bit more than required by the real non-cheating solution (the "easy" one). This fact, and the way the above routine is constructed should serve as a big hint for people still figuring this out. EDIT: as has been pointed out above, the loop over subroutines can be replaced by a long logical statement thus saving the use of the counter and bringing the required bits down to 256. The real (easy) solution still needs 263 bits.

    Finally, for those who have figured out / Googled / are otherwise familiar with the easy solution: How are you doing on finding the hard solution?
    Post edited by Dr. Timo on
  • edited November 2009
    I think it's been enough time to post the "easy" solution so all you guys can start on trying to figure out the hard solution. I hand the floor to Vhdblood:

    Posted By: Vhdblood: First, the easy solution, so you know I know and all, is to elect a leader. Every time anyone other than the leader goes in, they turn the light on if it is not on and they haven't turned it on before. The leader waits until he's turned it off 99 times and then asserts it.

    This will on average take roughly 10000 days (or ~16000 loops in the computer program equivalent).

    The hard solution cuts this down to around 3700 days, and the super hard solution (not so super hard once you figure out the hard one) can be shown to take ~2000 days. If you consider that it takes on average about 460 days for everyone to have been in the room that last one is not too bad.
    Post edited by Dr. Timo on
  • edited November 2009
    Now that the time is up, here's an implementation of the easy solution in my previous Python setup. It is probably too much of a spoiler as to the hard solution though, so don't look at it unless you don't care. Timo should probably look into it to see if we ought to change it a little with this in mind. Additionally, it makes a minor improvement to the basic easy solution that cuts around 100 days off the average duration: [Spoiler: Where possible, we attempt to make the first person to see someone else's light the leader.]. From 1000 iterations, it took around 16744.28 days on average.

    I'll give a more accurate time analysis of the easy solution shortly, with and without my minor improvement. I'm currently working on the details of the hard solution myself.
    Post edited by lackofcheese on
  • Yeah, I totally didn't understand the coding gobbledygook version of this problem. Fortunately, while I knew of the "prisoner" version of the problem, I had completely forgotten the solution, so I had a little fun working it out in my head again. The easy solution is pretty damned easy, though.

    I have no idea how to proceed on the hard solution, though. I'll have to hunker down and give it some serious thought.
  • edited November 2009
    Time analysis of the easy solution (including my minor improvement) [Spoiler:
    Let us consider a single step of this process to be one where a previously uncalled person turns on the light, and then the leader is called and receives this signal. As Frank said, this is a Bernoulli process, so the expected number of trials for an event to occur is the inverse of the probability of it occurring in a single trial. If n is the number of prisoners, then n-1 such steps must occur. Additionally, if k people other than the leader have been called so far, then the probability of a new person being called is (n-k-1)/n, and so the expected number of trials before this occurs is n/(n-k-1). On the other hand, the chance of the leader being called is always 1/n, so we always expect n trials for the leader to be called.

    Since all of these events must occur consecutively and cannot be concurrent, we can simply add up the expectations to get the total expected time. This is simply the sum from k = 0 to n-2 of [n/(n-k-1) + n], which can be simplified to n(n-1) + sum_1^{n-1}(n/(n-k)).
    For n = 100, this equates to approximately 9000 + 518 = 9518; for n = 128, this equates to approximately 16256 + 694 = 16950.

    As for the minor improvement I made, it introduces the following complication to the first step of the process:
    1) After the first day, there is a (n-1)/n chance that a different person is called, who would then become the leader, and turn the light off. This would conclude the first step after 2 days.
    2) However, there is a 1/n chance that the same person enters the room again. This person leaves the light on, and does not become the leader.
    2a) After this, there is a further (n-1)/n chance that, as before, a different person is called (who turns the light off and becomes the leader), concluding the first step after 3 days. The overall chance of this is (n-1)/n^2
    2b) Otherwise, with overall probability 1/n^2, the same person is called a third time in a row. They must then become the leader, which means that after 3 days we're in the same situation as for the basic easy solution.

    Considering that the first step takes n + n/(n-1) days, the amount saved in comparison to the unimproved solution is:
    E(first step w/o improvement) - E(first step with improvement)
    = n + n/(n-1) - [(n-1)/n * 2 + (n-1)/n^2 * 3 + 1/n^2 * (3 + n + n/(n-1))]
    For n = 100, the unimproved first step takes around 101 days, while the improved first step has an expectation of around 2 days, hence saving 99 days for an overall expectation of 9419 days.
    For n = 128, the unimproved first step takes around 129 days, while the improved first step has an expectation of around 2 days, hence saving 127 days for an overall expectation of 16823 days.
    ]
    Post edited by lackofcheese on
Sign In or Register to comment.