I want to pseudo-randomly create a list with 48 entries -- 24 zeros and 24 ones -- where the same value never occurs three times in a row. I have the following code:
import random
l = list()
for i in range(48):
if len(l) < 2:
l.append(random.choice([0,1]))
else:
if l[i-1] == l[i-2]:
if l[i-1] == 0:
l.append(1)
else:
l.append(0)
else:
l.append(random.choice([0,1]))
But sometimes the count of 0s and 1s is uneven.
Getting uniformity without using rejection is tricky.
The rejection approach is straightforward, something like
def brute(n):
seq = [0]*n+[1]*n
while True:
random.shuffle(seq)
if not any(len(set(seq[i:i+3])) == 1 for i in range(len(seq)-2)):
break
return seq
which will be very slow at large n but is reliable.
There's probably a slick way to take a non-rejection sample where it's almost trivial, but I couldn't see it and instead I fell back on methods which work generally. You can make sure that you're uniformly sampling the space if at each branch point, you weight the options by the number of successful sequences you generate if you take that choice.
So, we use dynamic programming to make a utility which counts the number of possible sequences, and extend to the general case where we have (#zeroes, #ones) bits left, and then use this to provide the weights for our draws. (We could actually refactor this into one function but I think they're clearer if they're separate, even if it introduces some duplication.)
from functools import lru_cache
import random
def take_one(bits_left, last_bits, choice):
# Convenience function to subtract a bit from the bits_left
# bit count and shift the last bits seen.
bits_left = list(bits_left)
bits_left[choice] -= 1
return tuple(bits_left), (last_bits + (choice,))[-2:]
#lru_cache(None)
def count_seq(bits_left, last_bits=()):
if bits_left == (0, 0):
return 1 # hooray, we made a valid sequence!
if min(bits_left) < 0:
return 0 # silly input
if 0 in bits_left and max(bits_left) > 2:
return 0 # short-circuit if we know it won't work
tot = 0
for choice in [0, 1]:
if list(last_bits).count(choice) == 2:
continue # can't have 3 consec.
new_bits_left, new_last_bits = take_one(bits_left, last_bits, choice)
tot += count_seq(new_bits_left, new_last_bits)
return tot
def draw_bits(n):
bits_left = [n, n]
bits_drawn = []
for bit in range(2*n):
weights = []
for choice in [0, 1]:
if bits_drawn[-2:].count(choice) == 2:
weights.append(0) # forbid this case
continue
new_bits_left, new_last_bits = take_one(bits_left, tuple(bits_drawn[-2:]), choice)
weights.append(count_seq(new_bits_left, new_last_bits))
bit_drawn = random.choices([0, 1], weights=weights)[0]
bits_left[bit_drawn] -= 1
bits_drawn.append(bit_drawn)
return bits_drawn
First, we can see how many such valid sequences there are:
In [1130]: [count_seq((i,i)) for i in range(12)]
Out[1130]: [1, 2, 6, 14, 34, 84, 208, 518, 1296, 3254, 8196, 20700]
which is A177790 at the OEIS, named
Number of paths from (0,0) to (n,n) avoiding 3 or more consecutive east steps and 3 or more consecutive north steps.
which if you think about it is exactly what we have, treating a 0 as an east step and a 1 as a north step.
Our random draws look good:
In [1145]: draw_bits(4)
Out[1145]: [0, 1, 1, 0, 1, 0, 0, 1]
In [1146]: draw_bits(10)
Out[1146]: [0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0]
and are quite uniform:
In [1151]: Counter(tuple(draw_bits(4)) for i in range(10**6))
Out[1151]:
Counter({(0, 0, 1, 0, 1, 0, 1, 1): 29219,
(1, 0, 1, 0, 0, 1, 0, 1): 29287,
(1, 1, 0, 0, 1, 0, 1, 0): 29311,
(1, 0, 1, 0, 1, 0, 1, 0): 29371,
(1, 0, 1, 0, 1, 1, 0, 0): 29279,
(0, 1, 0, 1, 0, 0, 1, 1): 29232,
(0, 1, 0, 1, 1, 0, 1, 0): 29824,
(0, 1, 1, 0, 0, 1, 1, 0): 29165,
(0, 1, 1, 0, 1, 0, 0, 1): 29467,
(1, 1, 0, 0, 1, 1, 0, 0): 29454,
(1, 0, 1, 1, 0, 0, 1, 0): 29338,
(0, 0, 1, 1, 0, 0, 1, 1): 29486,
(0, 1, 1, 0, 1, 1, 0, 0): 29592,
(0, 0, 1, 1, 0, 1, 0, 1): 29716,
(1, 1, 0, 1, 0, 0, 1, 0): 29500,
(1, 0, 0, 1, 0, 1, 0, 1): 29396,
(1, 0, 1, 0, 0, 1, 1, 0): 29390,
(0, 1, 1, 0, 0, 1, 0, 1): 29394,
(0, 1, 1, 0, 1, 0, 1, 0): 29213,
(0, 1, 0, 0, 1, 0, 1, 1): 29139,
(0, 1, 0, 1, 0, 1, 1, 0): 29413,
(1, 0, 0, 1, 0, 1, 1, 0): 29502,
(0, 1, 0, 1, 0, 1, 0, 1): 29750,
(0, 1, 0, 0, 1, 1, 0, 1): 29097,
(0, 0, 1, 1, 0, 1, 1, 0): 29377,
(1, 1, 0, 0, 1, 0, 0, 1): 29480,
(1, 1, 0, 1, 0, 1, 0, 0): 29533,
(1, 0, 0, 1, 0, 0, 1, 1): 29500,
(0, 1, 0, 1, 1, 0, 0, 1): 29528,
(1, 0, 1, 0, 1, 0, 0, 1): 29511,
(1, 0, 0, 1, 1, 0, 0, 1): 29599,
(1, 0, 1, 1, 0, 1, 0, 0): 29167,
(1, 0, 0, 1, 1, 0, 1, 0): 29594,
(0, 0, 1, 0, 1, 1, 0, 1): 29176})
Coverage is also correct, in that we can recover the A177790 counts by randomly sampling (and with some luck):
In [1164]: [len(set(tuple(draw_bits(i)) for _ in range(20000))) for i in range(9)]
Out[1164]: [1, 2, 6, 14, 34, 84, 208, 518, 1296]
Here's a reasonably efficient solution that gives you fairly random output that obeys the constraints, although it doesn't cover the full solution space.
We can ensure that the number of zeroes and ones are equal by ensuring that the number of single zeros equals the number of single ones, and the number of pairs of zeros equals the number of pairs of ones. In a perfectly random output list we'd expect the number of singles to be roughly double the number of pairs. This algorithm makes that exact: each list has 12 singles of each type, and 6 pairs.
Those run lengths are stored in a list named runlengths. On each round, we shuffle that list to get the sequence of run lengths for the zeros, and shuffle it again to get the sequence of run lengths for the ones. We then fill the output list by alternating between runs of zeroes and ones.
To check that the lists are correct we use the sum function. If there are equal numbers of zeroes and ones the sum of a list is 24.
from random import seed, shuffle
seed(42)
runlengths = [1] * 12 + [2] * 6
bits = [[0], [1]]
for i in range(10):
shuffle(runlengths)
a = runlengths[:]
shuffle(runlengths)
b = runlengths[:]
shuffle(bits)
out = []
for u, v in zip(a, b):
out.extend(bits[0] * u)
out.extend(bits[1] * v)
print(i, ':', *out, ':', sum(out))
output
0 : 0 0 1 0 0 1 1 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 : 24
1 : 0 1 0 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 0 1 0 1 1 0 1 : 24
2 : 0 0 1 1 0 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 0 1 : 24
3 : 0 0 1 0 1 1 0 0 1 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 0 1 1 0 1 0 1 0 1 0 1 : 24
4 : 1 1 0 0 1 0 1 0 1 0 1 1 0 1 1 0 1 0 1 1 0 1 0 1 0 0 1 0 1 0 0 1 0 1 0 0 1 1 0 0 1 0 1 1 0 0 1 0 : 24
5 : 1 1 0 1 0 1 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 0 1 1 0 0 1 1 0 0 1 0 1 0 1 0 1 0 0 : 24
6 : 1 0 1 0 0 1 0 0 1 0 0 1 1 0 0 1 1 0 1 0 0 1 1 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 0 1 0 1 1 0 1 0 1 0 : 24
7 : 0 1 0 0 1 1 0 1 0 0 1 0 1 0 1 0 1 0 1 0 1 1 0 1 1 0 1 0 0 1 0 0 1 0 1 1 0 1 1 0 1 1 0 0 1 0 0 1 : 24
8 : 0 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 0 1 1 0 1 0 1 0 0 1 0 0 1 0 1 1 0 1 0 1 1 0 1 1 0 0 1 0 1 : 24
9 : 1 0 1 1 0 1 1 0 1 1 0 1 0 0 1 0 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 0 : 24
Here is a simple code that obeys your constraints:
import random
def run():
counts = [24, 24]
last = [random.choice([0, 1]), random.choice([0, 1])]
counts[last[0]] -= 1
counts[last[1]] -= 1
while sum(counts) > 0:
can_pick_ones = sum(last[-2:]) < 2 and counts[1] > 0.33 * (48 - len(last))
can_pick_zeros = sum(last[-2:]) > 0 and counts[0] > 0.33 * (48 - len(last))
if can_pick_ones and can_pick_zeros:
value = random.choice([0, 1])
elif can_pick_ones:
value = 1
elif can_pick_zeros:
value = 0
counts[value] -= 1
last.append(value)
return last
for i in range(4):
r = run()
print(sum(r), r)
Output
24 [1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0]
24 [0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1]
24 [0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1]
24 [1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0]
Rationale
At each step of the while loop you can either choose 1, choose 0 or both. You can choose:
1 if the last two elements are not one and the counts of 1 is larger than 1/3 the amount of remaining slots: sum(last[-2:]) < 2 and counts[1] > 0.33 * (48 - len(last))
0 if the last two elements are not 0 and the counts of 0 is larger than 1/3 the amount of remaining slots: sum(last[-2:]) > 0 and counts[0] > 0.33 * (48 - len(last))
Both if you can choose 1 or 0
The sum of the two last elements can be 0, 1 or 2, if equals 0 it means that the last two elements were 0 so you can only pick 0 if sum(last[-2:]) > 0. If equals 2 it means that the last two elements where 1, so you can only pick 1 if sum(last[-2:]) < 2. Finally you need to check that the amount of elements of both 1 and 0 are at least a third of the remaining positions to assign, otherwise you are going to be forced to create a run of three consecutive equal elements.
Suppose i have a string "abcdefghijklmnopqrstuvwxyz"and i want to initialize dictionary keys with those values.
alphabet = 'abcdefghijklmnopqrstuvwxyz'
alphabetDict = dict()
for char in alphabet:
alphabetDict[char] = 0
Is there a better way of doing that?
You can use dict.fromkeys() method -
>>> s = 'abcdefghijklmnopqrstuvwxyz'
>>> alphaDict = dict.fromkeys(s,0)
>>> alphaDict
{'m': 0, 'p': 0, 'i': 0, 'n': 0, 'd': 0, 'w': 0, 'k': 0, 'y': 0, 's': 0, 'b': 0, 'h': 0, 't': 0, 'u': 0, 'q': 0, 'g': 0, 'l': 0, 'e': 0, 'a': 0, 'j': 0, 'c': 0, 'o': 0, 'f': 0, 'v': 0, 'x': 0, 'z': 0, 'r': 0}
From documentation -
fromkeys(seq[, value])
Create a new dictionary with keys from seq and values set to value.
fromkeys() is a class method that returns a new dictionary. value defaults to None.
Please note, you should not use this if value is something mutable like list or another dict , etc. As the value is only evaluted once when you call the method fromkeys() , and all keys point to the same object.
You can use this for immutable types as value like int, str , etc.
Also, you do not need to specify the s or alphabet string, you can instead use string.ascii_lowercase . Example -
>>> import string
>>> alphaDict = dict.fromkeys(string.ascii_lowercase,0)
>>> alphaDict
{'m': 0, 'p': 0, 'i': 0, 'n': 0, 'd': 0, 'w': 0, 'k': 0, 'y': 0, 's': 0, 'b': 0, 'h': 0, 't': 0, 'u': 0, 'q': 0, 'g': 0, 'l': 0, 'e': 0, 'a': 0, 'j': 0, 'c': 0, 'o': 0, 'f': 0, 'v': 0, 'x': 0, 'z': 0, 'r': 0}
You can use dictionary comprehensions in Python.
alphabetDict = {char: 0 for char in alphabet}
Dictionaries (Python Docs)
There is a minor difference between this answer and Anand's above. Dict comprehensions evaluate the value for every key, while fromkeys only does it once. If you're using things like ints, this poses no problem. However, if you do
d = {key: [] for key in <some set>}
d[3].append(5)
print(d[2])
gives you
[]
and you have distinct lists, while
d = dict.fromkeys(<some set>, [])
d[3].append(5)
print(d[2])
gives you
[5]
will map all the keys to the same list.
Yes, you can do that in one line using dictionary comprehensions.
In [1]: alphabet = 'abcdefghijklmnopqrstuvwxyz'
In [2]: {x:0 for x in alphabet} # dictionary comprehension
Out[2]:
{'a': 0,
'b': 0,
'c': 0,
'd': 0,
'e': 0,
'f': 0,
'g': 0,
'h': 0,
'i': 0,
'j': 0,
'k': 0,
'l': 0,
'm': 0,
'n': 0,
'o': 0,
'p': 0,
'q': 0,
'r': 0,
's': 0,
't': 0,
'u': 0,
'v': 0,
'w': 0,
'x': 0,
'y': 0,
'z': 0}
Tried with another approach.
dict(zip(alphabets, '0'*len(alphabets)))
If you need a dictionary with different values instead of a constant value, you may create one like below with the use of random module:
>>> import random
>>> alphabet = 'abcdefghijklmnopqrstuvwxyz'
>>> my_dict = dict([ (ch, random.randint(1,len(alphabet)) ) for ch in alphabet ] )
>>> my_dict
{'a': 17, 'b': 15, 'c': 3, 'd': 5, 'e': 5, 'f': 13, 'g': 7, 'h': 1, 'i': 3, 'j': 12, 'k': 11, 'l': 7, 'm': 8, 'n': 23, 'o': 15, 'p': 7, 'q': 9, 'r': 19, 's': 17, 't': 22, 'u': 20, 'v': 24, 'w': 26, 'x': 14, 'y': 7, 'z': 24}
>>>
I creates dictionaries like above when I need a dictionary with random values for testing purposes.
Another way to create a dictionary with each char of a text with character count.
>>> char_count = lambda text, char: text.count(char)
>>> text = "Genesis 1 - 1 In the beginning God created the heavens and the earth. 2 Now the earth was formless and desolate, and there was darkness upon the surface of the watery deep, and God's active force was moving about over the surface of the waters."
>>> my_dict = dict( [ ( char, char_count(text, char) ) for char in text ] )
>>> my_dict
{'G': 3, 'e': 32, 'n': 13, 's': 15, 'i': 5, ' ': 45, '1': 2, '-': 1, 'I': 1, 't': 17, 'h': 12, 'b': 2, 'g': 3, 'o': 12, 'd': 10, 'c': 5, 'r': 12, 'a': 19, 'v': 4, '.': 2, '2': 1, 'N': 1, 'w': 6, 'f': 6, 'm': 2, 'l': 2, ',': 2, 'k': 1, 'u': 4, 'p': 2, 'y': 1, "'": 1}
Explanation:
1. lambda function counts number of occurrences of a characters.
2. Call lambda function for each character in text to get the count of that particular character.
Note: You may improve this code to avoid duplicate calls for repeated characters.
Using dictionary comprehension may be easier than all above:
{ char:(text.count(char)) for char in text }
In order to avoid duplication as mentioned by #Robert Ranjan , we do it this way
>>> import string
>>> char_count = lambda text, char: text.count(char)
>>> allAscii = list(string.printable)
>>> # alphabet = 'abcdefghijklmnopqrstuvwxyz'
>>> text = "Genesis 1 - 1 In the beginning God created the heavens and the earth. 2 Now the earth was formless and desolate, and * # there was darkness upon the surface of the watery deep, and God's active force was moving about over the surface of the waters."
>>> # my_dict = dict( [ ( char, char_count(text, char) ) for char in alphabet]
>>> my_dict = dict( [ ( char, char_count(text, char) ) for char in allAscii]
>>> for eachKey in my_dict:
print(repr(eachKey), ': ', my_dict[eachKey], ' ', end=' || ')
'0' : 0 || '1' : 2 || '2' : 1 || '3' : 0 || '4' : 0 || '5' : 0 || '6' : 0 || '7' : 0 || '8' : 0 || '9' : 0 || 'a' : 19 || 'b' : 2 || 'c' : 5 || 'd' : 10 || 'e' : 32 || 'f' : 6 || 'g' : 3 || 'h' : 12 || 'i' : 5 || 'j' : 0 || 'k' : 1 || 'l' : 2 || 'm' : 2 || 'n' : 13 || 'o' : 12 || 'p' : 2 || 'q' : 0 || 'r' : 12 || 's' : 15 || 't' : 17 || 'u' : 4 || 'v' : 4 || 'w' : 6 || 'x' : 0 || 'y' : 1 || 'z' : 0 || 'A' : 0 || 'B' : 0 || 'C' : 0 || 'D' : 0 || 'E' : 0 || 'F' : 0 || 'G' : 3 || 'H' : 0 || 'I' : 1 || 'J' : 0 || 'K' : 0 || 'L' : 0 || 'M' : 0 || 'N' : 1 || 'O' : 0 || 'P' : 0 || 'Q' : 0 || 'R' : 0 || 'S' : 0 || 'T' : 0 || 'U' : 0 || 'V' : 0 || 'W' : 0 || 'X' : 0 || 'Y' : 0 || 'Z' : 0 || '!' : 0 || '"' : 0 || '#' : 0 || '$' : 0 || '%' : 0 || '&' : 0 || "'" : 1 || '(' : 0 || ')' : 0 || '*' : 1 || '+' : 0 || ',' : 2 || '-' : 1 || '.' : 2 || '/' : 0 || ':' : 0 || ';' : 0 || '<' : 0 || '=' : 0 || '>' : 0 || '?' : 0 || '#' : 1 || '[' : 0 || '\\' : 0 || ']' : 0 || '^' : 0 || '_' : 0 || '`' : 0 || '{' : 0 || '|' : 0 || '}' : 0 || '~' : 0 || ' ' : 47 || '\t' : 0 || '\n' : 0 || '\r' : 0 || '\x0b' : 0 || '\x0c' : 0 ||
>>>