6

I have a problem as below, I tried but I couldn't find the right result. I want to solve it in a simple way without using an extra library. I don't have any data to share because I can't establish a correct logic.

4 twins (8 children in total) play with their eyes closed. The children in the randomly distributed group hold each other's hands in pairs when a moment comes. How to write a python script that lists all possibilities and marks the probability that siblings hold each other's hand?

list = ['a1', 'a2', 'b1', 'b2', 'c1', 'c2', 'd1', 'd2']

I want to get a result like this:

[a1a2, b1b2, c1c2, d1d2] - Found
[a1a2, b1b2, c1d1, c2d2] - Not found
[a1a2, b1b2, c1d2, c2d1] - Not found
[a1a2, b1b2, c1d1, c2d2] - Not found
[a1a2, b2b1, c1d1, c2d2] - Not found
...
all possible matches
...

Thanks for help.

etnclp
  • 572
  • 1
  • 5
  • 15
  • 2
    `list` is a builtin type, so it's confusing to use it as a variable name. Perhaps, `list_of_pairs`? – jtlz2 Dec 22 '21 at 12:59
  • "Found", "Not found" ... what does that mean? – President James K. Polk Dec 22 '21 at 14:18
  • Does this answer your question? [Fastest way to get sets of all mutually exclusive pairs that can be formed from a list in python?](https://stackoverflow.com/questions/69553523/fastest-way-to-get-sets-of-all-mutually-exclusive-pairs-that-can-be-formed-from) – Stef Dec 22 '21 at 14:43

2 Answers2

3

Creating all pairings and comparing to correct pairing

First, define a function to generate all pairings, and a function to generate the correct pairing:

def all_pairings(l):
  if len(l) == 0:
    return [[]]
  else:
    return [[(l[0],l[i])] + p for i in range(1,len(l)) for p in all_pairings(l[1:i]+l[i+1:])]

def adjacent_pairing(l):
    it = iter(l)
    return zip(it, it)

Then it's just a for-loop:


children = ['a1', 'a2', 'b1', 'b2', 'c1', 'c2', 'd1', 'd2']

correct_pairing = set(adjacent_pairing(children))

for pairing in all_pairings(children):
    sentence = 'Found' if set(pairing) == correct_pairing else 'Not found'
    print(', '.join(''.join(pair) for pair in pairing), ' -- ', sentence)

Output:

a1a2, b1b2, c1c2, d1d2  --  Found
a1a2, b1b2, c1d1, c2d2  --  Not found
a1a2, b1b2, c1d2, c2d1  --  Not found
a1a2, b1c1, b2c2, d1d2  --  Not found
...
a1d2, a2d1, b1b2, c1c2  --  Not found
a1d2, a2d1, b1c1, b2c2  --  Not found
a1d2, a2d1, b1c2, b2c1  --  Not found

Shuffling the pairings

I suggest using random.shuffle to shuffle the possible pairings before iterating (otherwise, the correct pairing is always the first pairing generated, which is a bit boring).

import random

l = list(all_pairings(children))
random.shuffle(l)
for pairing in l:
    sentence = 'Found' if set(pairing) == correct_pairing else 'Not found'
    print(', '.join(''.join(pair) for pair in pairing), ' -- ', sentence)

Output:

a1a2, b1d2, b2d1, c1c2  --  Not found
a1d1, a2d2, b1c2, b2c1  --  Not found
a1b2, a2c2, b1c1, d1d2  --  Not found
...
a1b1, a2c2, b2d1, c1d2  --  Not found
a1a2, b1b2, c1c2, d1d2  --  Found
a1b2, a2c2, b1d2, c1d1  --  Not found
...
a1c2, a2d2, b1b2, c1d1  --  Not found
a1c2, a2d2, b1c1, b2d1  --  Not found
a1b1, a2b2, c1c2, d1d2  --  Not found

Probability of finding the correct pairing

Assuming all pairings are equiprobable, the probability of finding the correct pairing when picking one pairing randomly is 1 divided by the total number of distinct possible pairings.

How many distinct possible pairings are there?

Choosing an ordered pairing, where the order of the pairs matter, and the order of the two children in each pair matter, is the same as choosing a permutation. It is well known that there are N! possible permutations of N children.

How many ordered pairings correspond to each unordered pairing?

There are 2 possible ways to order the 2 children in each pair; thus there are 2 ** (N / 2) ways to order the 2 children of all pairs.

There are (N / 2)! possible ways to order the N / 2 pairs.

Thus, each pairing corresponds to (N / 2)! * 2 ** (N / 2) ordered pairings.

Thus, there must be N! / ( (N / 2)! * 2 ** (N / 2) ) distinct possible pairings of N children.

For your 8 children, that's 8! / (4! * 2**4) == 105.

The probability of picking the correct pairing, when picking uniformly at random amongst all distinct possible pairings, is 1 over that number:

just slightly under 1% in the case of 8 children.

Expected number of correct pairs in a random pairing

Let's pick a pairing at random among all distinct possible pairings. How many of the pairs in that pairing will be correct, in average?

We can count the number of correct pairs in each pairing in our python program:

for pairing in all_pairings(children):
    nb_correct_pairs = len(set(pairing) & correct_pairing)
    print(', '.join(''.join(pair) for pair in pairing), ' -- ', nb_correct_pairs)

Output:

a1a2, b1b2, c1c2, d1d2  --  4
a1a2, b1b2, c1d1, c2d2  --  2
a1a2, b1b2, c1d2, c2d1  --  2
a1a2, b1c1, b2c2, d1d2  --  2
a1a2, b1c1, b2d1, c2d2  --  1
a1a2, b1c1, b2d2, c2d1  --  1
a1a2, b1c2, b2c1, d1d2  --  2
a1a2, b1c2, b2d1, c1d2  --  1
...
a1d2, a2c1, b1d1, b2c2  --  0
a1d2, a2c2, b1b2, c1d1  --  1
a1d2, a2c2, b1c1, b2d1  --  0
a1d2, a2c2, b1d1, b2c1  --  0
a1d2, a2d1, b1b2, c1c2  --  2
a1d2, a2d1, b1c1, b2c2  --  0
a1d2, a2d1, b1c2, b2c1  --  0

The average number is actually easy to calculate with mathematics.

Let us consider one particular child in a random pairing, for instance child a1. What is the probability that child a1 will hold their twin's hand? Since there are N-1 other children, and by symmetry all children are equally likely, the probability that child a1 will hold their twin's hand is 1/(N-1).

Of course, this applies to all children, not just a1. Thus, in average, 1/(N-1) of the children will hold their own twin's hand. Since there are N children, then in average, N/(N-1) == 1 + 1/(N-1) children will hold their own twin's hand. Since there are 2 children per pair, that means that in average, there will be N / (2(N-1)) == (1 + 1/(N-1)) / 2 correct pairs in a random pairing.

In the case of N == 8, this means 4/7 ≈ 0.571 correct pairs per pairing.

Let us verify experimentally:

l = list(all_pairings(children))
total_correct_pairs = sum(len(set(pairing) & correct_pairing) for pairing in l)
n_pairings = len(l)
expected_correct_pairs = total_correct_pairs / n_pairings
print('Expected number of correct pairs: ', expected_correct_pairs)

Output:

Expected number of correct pairs:  0.5714285714285714
Stef
  • 13,242
  • 2
  • 17
  • 28
0

If an included library is okay for you, you could try to use random.choice().

I also added a small part which estimates the probability of finding the right twin.

Try the following:


    import random
    
    twins= ['a1', 'a2', 'b1', 'b2', 'c1', 'c2', 'd1', 'd2']
    twinsToFind= ['a1','a2', 'b1', 'b2', 'c1', 'c2', 'd1', 'd2']
    
    # Loop over the list
    for i in range(0, 4):
    
        searcher = twins[0]
    
        # remove the searching twin, since he shouldn't find "himself"
        twinsToFind.remove(searcher)
        # also remove him from the list, since he won't search again
        twins.remove(searcher)
    
        # variable for the probability of finding the right twin
        prob = 0
    
        # loop over to second list to check how possible it is to find the right twin
    
        for y in twinsToFind:
            if searcher[0] == y[0]:
                prob =+1
    
        # estimating the probability
    
        prob = prob/len(twinsToFind)
    
        # printing the probability
        # casting it to inc gets rid of the digits behind the comma
        # multiplying it with 100 gives the probability in percent
    
        print("The probability to find the right twin is: "+str(int(prob*100))+"%")
    
        # find a random twin
    
        found = random.choice(twinsToFind)
    
        # print a message if it's the right twin
    
        if searcher[0] == found[0]:
            print("==> Right twin found: "+searcher+found)
    
        # remove the "found" twin from the list, because he won't search again
    
        twins.remove(found)
    
        # remove the found twin from the second list, because he shouldn't be found again 
        # (yeah, sounds a bit dark, but you get it, I guess)
    
         twinsToFind.remove(found)
         print("Found:"+searcher+"<->"+found)

Phil
  • 111
  • 1
  • 4
  • It's generally a bad idea to modify an iterable while iterating over it - see e.g. https://stackoverflow.com/questions/6260089/strange-result-when-removing-item-from-a-list-while-iterating-over-it – match Dec 22 '21 at 12:59
  • @match Yeah, you are right, I did it quick and dirty. I fixed that. – Phil Dec 22 '21 at 13:25
  • Is something wrong with the indentation? There's a `for i in range(0, 4):` with nothing in the loop – Stef Dec 23 '21 at 10:28
  • you are right @Stef – Phil Dec 23 '21 at 17:55