Related
I want to have a list made out of 100 items, each item being a value from 0-31 inclusive. I then want to be able to take one of these lists, and know what seed/input is necessary to randomly generate that exact list.
Using some suitable Linear Congruential Generator:
You could use this research paper by Pierre L'Ecuyer:
Tables_of_linear_congruential_generators_of_different_sizes_and_good_lattice_structure
The lowest power of 2 modulus for which the paper gives examples of (decently pseudo-random) LCGs is 230, which is close to 1 billion. See table 4 of the paper. Just pick one of those LCGs, say:
un+1 = ((116646453 * un) + 5437) mod 230
Each of your items is exactly 5 bits wide. If you decide to group your items 6 by 6, each group is exactly 30 bits wide, so can be considered as one state of this modulo 230 LCG.
From a initial group of 6 items, one step of the LCG will generate the next group, that is the next 6 items. And the paper tells you that the serie will look reasonably random overall.
Hence, you can regard the first 6 items as your “seed”, as you can reconstruct the whole list from its leftmost 6 items.
Even assuming that for the sake of obfuscation you started the visible part of the list after the seed, you would still have only about one billion possible seeds to worry about. Any decent computer would be able to find the left-hidden seed by brute force within a handful of seconds, by simulating the LCG for every possible seed and comparing with the actual list.
Sample Python code:
One can start by creating a class that, given a seed, supplies an unlimited serie of items between 0 and 31:
class LEcuyer30:
def __init__ (self, seed):
self.seed = seed & ((1 << 30) - 1)
self.currGroup = self.seed
self.itemCount = 6
def __toNextGroup(self):
nextGroup = ((116646453 * self.currGroup) + 5437) % (1 << 30)
self.currGroup = nextGroup
self.itemCount = 6
def getItem(self):
if (self.itemCount <= 0):
self.__toNextGroup()
# extract 5 bits:
word = self.currGroup >> (5 * (self.itemCount - 1))
self.itemCount -= 1
return (word & 31)
Test code:
We can create a sequence of 20 items and print it:
# Main program:
randomSeed = 514703103
rng = LEcuyer30(randomSeed)
itemList = []
for k in range(20):
item = rng.getItem()
itemList.append(item)
print("itemList = %s" % itemList)
Program output:
itemList = [15, 10, 27, 15, 23, 31, 1, 10, 5, 15, 16, 8, 4, 16, 24, 31, 7, 5, 8, 19]
I am trying to solve the usaco problem combination lock where you are given a two lock combinations. The locks have a margin of error of +- 2 so if you had a combination lock of 1-3-5, the combination 3-1-7 would still solve it.
You are also given a dial. For example, the dial starts at 1 and ends at the given number. So if the dial was 50, it would start at 1 and end at 50. Since the beginning of the dial is adjacent to the end of the dial, the combination 49-1-3 would also solve the combination lock of 1-3-5.
In this program, you have to output the number of distinct solutions to the two lock combinations. For the record, the combination 3-2-1 and 1-2-3 are considered distinct, but the combination 2-2-2 and 2-2-2 is not.
I have tried creating two functions, one to check whether three numbers match the constraints of the first combination lock and another to check whether three numbers match the constraints of the second combination lock.
a,b,c = 1,2,3
d,e,f = 5,6,7
dial = 50
def check(i,j,k):
i = (i+dial) % dial
j = (j+dial) % dial
k = (k+dial) % dial
if abs(a-i) <= 2 and abs(b-j) <= 2 and abs(c-k) <= 2:
return True
return False
def check1(i,j,k):
i = (i+dial) % dial
j = (j+dial) % dial
k = (k+dial) % dial
if abs(d-i) <= 2 and abs(e-j) <= 2 and abs(f-k) <= 2:
return True
return False
res = []
count = 0
for i in range(1,dial+1):
for j in range(1,dial+1):
for k in range(1,dial+1):
if check(i,j,k):
count += 1
res.append([i,j,k])
if check1(i,j,k):
count += 1
res.append([i,j,k])
print(sorted(res))
print(count)
The dial is 50 and the first combination is 1-2-3 and the second combination is 5-6-7.
The program should output 249 as the count, but it instead outputs 225. I am not really sure why this is happening. I have added the array for display purposes only. Any help would be greatly appreciated!
You're going to a lot of trouble to solve this by brute force.
First of all, your two check routines have identical functionality: just call the same routine for both combinations, giving the correct combination as a second set of parameters.
The critical logic problem is handling the dial wrap-around: you miss picking up the adjacent numbers. Run 49 through your check against a correct value of 1:
# using a=1, i=49
i = (1+50)%50 # i = 1
...
if abs(1-49) <= 2 ... # abs(1-49) is 48. You need it to show up as 2.
Instead, you can check each end of the dial:
a_diff = abs(i-a)
if a_diff <=2 or a_diff >= (dial-2) ...
Another way is to start by making a list of acceptable values:
a_vals = [(a-oops) % dial] for oops in range(-2, 3)]
... but note that you have to change the 0 value to dial. For instance, for a value of 1, you want a list of [49, 50, 1, 2, 3]
With this done, you can check like this:
if i in a_vals and j in b_vals and k in c_vals:
...
If you want to upgrade to the itertools package, you can simply generate all desired combinations:
combo = set(itertools.product(a_list, b_list_c_list) )
Do that for both given combinations and take the union of the two sets. The length of the union is the desired answer.
I see the follow-up isn't obvious -- at least, it's not appearing in the comments.
You have 5*5*5 solutions for each combination; start with 250 as your total.
Compute the sizes of the overlap sets: the numbers in each triple that can serve for each combination. For your given problem, those are [3],[4],[5]
The product of those set sizes is the quantity of overlap: 1*1*1 in this case.
The overlapping solutions got double-counted, so simply subtract the extra from 250, giving the answer of 249.
For example, given 1-2-3 and 49-6-6, you would get sets
{49, 50, 1}
{4}
{4, 5}
The sizes are 3, 1, 2; the product of those numbers is 6, so your answer is 250-6 = 244
Final note: If you're careful with your modular arithmetic, you can directly compute the set sizes without building the sets, making the program very short.
Here is one approach to a semi-brute-force solution:
import itertools
#The following code assumes 0-based combinations,
#represented as tuples of numbers in the range 0 to dial - 1.
#A simple wrapper function can be used to make the
#code apply to 1-based combos.
#The following function finds all combos which open lock with a given combo:
def combos(combo,tol,dial):
valids = []
for p in itertools.product(range(-tol,1+tol),repeat = 3):
valids.append(tuple((x+i)%dial for x,i in zip(combo,p)))
return valids
#The following finds all combos for a given iterable of target combos:
def all_combos(targets,tol,dial):
return set(combo for target in targets for combo in combos(target,tol,dial))
For example, len(all_combos([(0,1,2),(4,5,6)],2,50)) evaluate to 249.
The correct code for what you are trying to do is the following:
dial = 50
a = 1
b = 2
c = 3
d = 5
e = 6
f = 7
def check(i,j,k):
if (abs(a-i) <= 2 or (dial-abs(a-i)) <= 2) and \
(abs(b-j) <= 2 or (dial-abs(b-j)) <= 2) and \
(abs(c-k) <= 2 or (dial-abs(c-k)) <= 2):
return True
return False
def check1(i,j,k):
if (abs(d-i) <= 2 or (dial-abs(d-i)) <= 2) and \
(abs(e-j) <= 2 or (dial-abs(e-j)) <= 2) and \
(abs(f-k) <= 2 or (dial-abs(f-k)) <= 2):
return True
return False
res = []
count = 0
for i in range(1,dial+1):
for j in range(1,dial+1):
for k in range(1,dial+1):
if check(i,j,k):
count += 1
res.append([i,j,k])
elif check1(i,j,k):
count += 1
res.append([i,j,k])
print(sorted(res))
print(count)
And the result is 249, the total combinations are 2*(5**3) = 250, but we have the duplicates: [3, 4, 5]
I am not sure of inverse is the proper name, but I think it is.
This example will clarify what I need:
I have a max height, 5 for example, and so height can range from 0 to 4. In this case we're talking integers, so the options are: 0, 1, 2, 3, 4.
What I need, given an input ranging from 0 up to (and including) 4, is to get the inverse number.
Example:
input: 3
output: 1
visual:
0 1 2 3 4
4 3 2 1 0
I know I can do it like this:
position_list = list(range(5))
index_list = position_list[::-1]
index = index_list[3]
But this will probably use unnecessary memory, and probably unnecessary cpu usage creating two lists. The lists will be deleted after these lines of code, and will recreated every time the code is ran (within method). I'd rather find a way not needing the lists at all.
What is an efficient way to achieve the same? (while still keeping the code readable for someone new to the code)
Isn't it just max - in...?
>>> MAX=4
>>> def calc(in_val):
... out_val = MAX - in_val
... print('%s -> %s' % ( in_val, out_val ))
...
>>> calc(3)
3 -> 1
>>> calc(1)
1 -> 3
You just need to subtract from the max:
def return_inverse(n, mx):
return mx - n
For the proposed example:
position_list = list(range(5))
mx = max(position_list)
[return_inverse(i, mx) for i in position_list]
# [4, 3, 2, 1, 0]
You have maximum heigth, let's call it max_h.
Your numbers are counted from 0, so they are in [0; max_h - 1]
You want to find the complementation number that becomes max_h in sum with input number
It is max_h - 1 - your_number:
max_height = 5
input_number = 2
for input_number in range(5):
print('IN:', input_number, 'OUT:', max_height - input_number - 1)
IN: 1 OUT: 3
IN: 2 OUT: 2
IN: 3 OUT: 1
IN: 4 OUT: 0
Simply compute the reverse index and then directly access the corresponding element.
n = 5
inp = 3
position_list = list(range(n))
position_list[n-1-inp]
# 1
You can just derive the index from the list's length and the desired position, to arrive at the "inverse":
position_list = list(range(5))
position = 3
inverse = position_list[len(position_list)-1-position]
And:
for i in position_list:
print(i, position_list[len(position_list)-1-i])
In this case, you can just have the output = 4-input. If it's just increments of 1 up to some number a simple operation like that should be enough. For example, if the max was 10 and the min was 5, then you could just do 9-input+5. The 9 can be replaced by the max-1 and the 5 can be replaced with the min.
So max-1-input+min
Basically trying to figure out how to create a for loop that generates a range around a the main number "x" based on "n"
x = 10 # x = Actual
n = 5
because
Actual = input("What's the Actual") # Enter 10
Target = input("What's the Target") # Enter 15
n = Target - Actual # 5 = 15 - 10
Since Actual is 10
I would like to see..
5, 6, 7, 8, 9 , 10, 11, 12, 13, 14, 15
The code is:
n = 2
def price(sprice):
for i in range(n*2):
sprice = sprice + 1
print(sprice)
price(200)
This code shows 201,202,203,204 and the actual is 200.
I want to see 198,199,200,201,202 because n = 2 and when multiply by 2 = 4 which shows a range of 4 values around 200
According to the docs, range can accept two argument that specify the start (inclusive) and end (exclusive) of the interval. So you can get an interval in the form [start, stop).
You would like to create the interval [Actual - n, Actual + n], so just translate it almost literally to Python, bearing in mind that range excludes the second argument from that range, so you should add one to it:
>>> list(range(Actual - n, Actual + n + 1))
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
ForceBru has already shown a pythonic solution to your problem. I only want to add that your original code works as intended after some minor tweaks:
n = 2
def price(sprice):
sprice -= n # short way to say: sprice = sprice - n
for i in range(n*2+1): # +1 required as in 1-argument range it is exclusive
sprice = sprice + 1
print(sprice)
price(200)
Output:
199
200
201
202
203
Note that Python recognizes * is to be executed before + independently from its order. Hence you might write 1+n*2 in place of n*2+1.
Some assumptions:
One deck of 52 cards is used
Picture cards count as 10
Aces count as 1 or 11
The order is not important (ie. Ace + Queen is the same as Queen + Ace)
I thought I would then just sequentially try all the possible combinations and see which ones add up to 21, but there are way too many ways to mix the cards (52! ways). This approach also does not take into account that order is not important nor does it account for the fact that there are only 4 maximum types of any one card (Spade, Club, Diamond, Heart).
Now I am thinking of the problem like this:
We have 11 "slots". Each of these slots can have 53 possible things inside them: 1 of 52 cards or no card at all. The reason it is 11 slots is because 11 cards is the maximum amount of cards that can be dealt and still add up to 21; more than 11 cards would have to add up to more than 21.
Then the "leftmost" slot would be incremented up by one and all 11 slots would be checked to see if they add up to 21 (0 would represent no card in the slot). If not, the next slot to the right would be incremented, and the next, and so on.
Once the first 4 slots contain the same "card" (after four increments, the first 4 slots would all be 1), the fifth slot could not be that number as well since there are 4 numbers of any type. The fifth slot would then become the next lowest number in the remaining available cards; in the case of four 1s, the fifth slot would become a 2 and so on.
How would you do approach this?
divide and conquer by leveraging the knowledge that if you have 13 and pick a 10 you only have to pick cards to sum to 3 left to look at ... be forwarned this solution might be slow(took about 180 seconds on my box... it is definately non-optimal) ..
def sum_to(x,cards):
if x == 0: # if there is nothing left to sum to
yield []
for i in range(1,12): # for each point value 1..11 (inclusive)
if i > x: break # if i is bigger than whats left we are done
card_v = 11 if i == 1 else i
if card_v not in cards: continue # if there is no more of this card
new_deck = cards[:] # create a copy of hte deck (we do not want to modify the original)
if i == 1: # one is clearly an ace...
new_deck.remove(11)
else: # remove the value
new_deck.remove(i)
# on the recursive call we need to subtract our recent pick
for result in sum_to(x-i,new_deck):
yield [i] + result # append each further combination to our solutions
set up your cards as follows
deck = []
for i in range(2,11): # two through ten (with 4 of each)
deck.extend([i]*4)
deck.extend([10]*4) #jacks
deck.extend([10]*4) #queens
deck.extend([10]*4) #kings
deck.extend([11]*4) # Aces
then just call your function
for combination in sum_to(21,deck):
print combination
unfortunately this does allow some duplicates to sneak in ...
in order to get unique entries you need to change it a little bit
in sum_to on the last line change it to
# sort our solutions so we can later eliminate duplicates
yield sorted([i] + result) # append each further combination to our solutions
then when you get your combinations you gotta do some deep dark voodoo style python
unique_combinations = sorted(set(map(tuple,sum_to(21,deck))),key=len,reverse=0)
for combo in unique_combinations: print combo
from this cool question i have learned the following (keep in mind in real play you would have the dealer and other players also removing from the same deck)
there are 416 unique combinations of a deck of cards that make 21
there are 300433 non-unique combinations!!!
the longest number of ways to make 21 are as follows
with 11 cards there are 1 ways
[(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3)]
with 10 cards there are 7 ways
with 9 cards there are 26 ways
with 8 cards there are 54 ways
with 7 cards there are 84 ways
with 6 cards there are 94 ways
with 5 cards there are 83 ways
with 4 cards there are 49 ways
with 3 cards there are 17 ways
with 2 cards there are 1 ways
[(10, 11)]
there are 54 ways in which all 4 aces are used in making 21!!
there are 106 ways of making 21 in which NO aces are used !!!
keep in mind these are often suboptimal plays (ie considering A,10 -> 1,10 and hitting )
Before worrying about the suits and different cards with value 10 lets figure out how many different value combinations resulting to 21 there are. For example 5, 5, 10, 1 is one such combination. The following function takes in limit which is the target value, start which indicates the lowest value that can be picked and used which is the list of picked values:
def combinations(limit, start, used):
# Base case
if limit == 0:
return 1
# Start iteration from lowest card to picked so far
# so that we're only going to pick cards 3 & 7 in order 3,7
res = 0
for i in range(start, min(12, limit + 1)):
# Aces are at index 1 no matter if value 11 or 1 is used
index = i if i != 11 else 1
# There are 16 cards with value of 10 (T, J, Q, K) and 4 with every
# other value
available = 16 if index == 10 else 4
if used[index] < available:
# Mark the card used and go through combinations starting from
# current card and limit lowered by the value
used[index] += 1
res += combinations(limit - i, i, used)
used[index] -= 1
return res
print combinations(21, 1, [0] * 11) # 416
Since we're interested about different card combinations instead of different value combinations the base case in above should be modified to return number of different card combinations that can be used to generate a value combination. Luckily that's quite easy task, Binomial coefficient can be used to figure out how many different combinations of k items can be picked from n items.
Once the number of different card combinations for each value in used is known they can be just multiplied with each other for the final result. So for the example of 5, 5, 10, 1 value 5 results to bcoef(4, 2) == 6, value 10 to bcoef(16, 1) == 16 and value 1 to bcoef(4, 1) == 4. For all the other values bcoef(x, 0) results to 1. Multiplying those values results to 6 * 16 * 4 == 384 which is then returned:
import operator
from math import factorial
def bcoef(n, k):
return factorial(n) / (factorial(k) * factorial(n - k))
def combinations(limit, start, used):
if limit == 0:
combs = (bcoef(4 if i != 10 else 16, x) for i, x in enumerate(used))
res = reduce(operator.mul, combs, 1)
return res
res = 0
for i in range(start, min(12, limit + 1)):
index = i if i != 11 else 1
available = 16 if index == 10 else 4
if used[index] < available:
used[index] += 1
res += combinations(limit - i, i, used)
used[index] -= 1
return res
print combinations(21, 1, [0] * 11) # 186184
So I decided to write the script that every possible viable hand can be checked. The total number comes out to be 188052. Since I checked every possible combination, this is the exact number (as opposed to an estimate):
import itertools as it
big_list = []
def deck_set_up(m):
special = {8:'a23456789TJQK', 9:'a23456789', 10:'a2345678', 11:'a23'}
if m in special:
return [x+y for x,y in list(it.product(special[m], 'shdc'))]
else:
return [x+y for x,y in list(it.product('a23456789TJQKA', 'shdc'))]
deck_dict = {'as':1,'ah':1,'ad':1,'ac':1,
'2s':2,'2h':2,'2d':2,'2c':2,
'3s':3,'3h':3,'3d':3,'3c':3,
'4s':4,'4h':4,'4d':4,'4c':4,
'5s':5,'5h':5,'5d':5,'5c':5,
'6s':6,'6h':6,'6d':6,'6c':6,
'7s':7,'7h':7,'7d':7,'7c':7,
'8s':8,'8h':8,'8d':8,'8c':8,
'9s':9,'9h':9,'9d':9,'9c':9,
'Ts':10,'Th':10,'Td':10,'Tc':10,
'Js':10,'Jh':10,'Jd':10,'Jc':10,
'Qs':10,'Qh':10,'Qd':10,'Qc':10,
'Ks':10,'Kh':10,'Kd':10,'Kc':10,
'As':11,'Ah':11,'Ad':11,'Ac':11}
stop_here = {2:'As', 3:'8s', 4:'6s', 5:'4h', 6:'3c', 7:'3s', 8:'2h', 9:'2s', 10:'2s', 11:'2s'}
for n in range(2,12): # n is number of cards in the draw
combos = it.combinations(deck_set_up(n), n)
stop_point = stop_here[n]
while True:
try:
pick = combos.next()
except:
break
if pick[0] == stop_point:
break
if n < 8:
if len(set([item.upper() for item in pick])) != n:
continue
if sum([deck_dict[card] for card in pick]) == 21:
big_list.append(pick)
print n, len(big_list) # Total number hands that can equal 21 is 188052
In the output, the the first column is the number of cards in the draw, and the second number is the cumulative count. So the number after "3" in the output is the total count of hands that equal 21 for a 2-card draw, and a 3-card draw. The lower case a is a low ace (1 point), and uppercase A is high ace. I have a line (the one with the set command), to make sure it throws out any hand that has a duplicate card.
The script takes 36 minutes to run. So there is definitely a trade-off between execution time, and accuracy. The "big_list" contains the solutions (i.e. every hand where the sum is 21)
>>>
================== RESTART: C:\Users\JBM\Desktop\bj3.py ==================
2 64
3 2100
4 14804
5 53296
6 111776
7 160132
8 182452
9 187616
10 188048
11 188052 # <-- This is the total count, as these numbers are cumulative
>>>