This is a slight rework of a previous question that wasn't quite solved. I am working on a python challenge to create a game of Texas Holdem.
As one of my functions, I am creating a function to check for a "straight" hand.
The simple explanation is that the function will take in a list of 7 cards, sort their [integer] values, and check to see if there is a sequence of 5 or longer.
My function DOES work, but it seems a tad more complicated than is necessary:
def is_straight(hand): #Hand is a list of 7 card objects with integer value attributes
#sorts the list of cards in the hand based on their [integer] values
hand.sort(key = attrgetter("value"))
#While loop to check for sequences in list of cards
counter = 0
sequence = 1
sequences_list = []
while counter < len(hand)-1:
#Iterate through the list of int values and, if the value of 1 index == value + 1 of next index, increment sequence number for every sequence
if hand[counter+1].value == hand[counter].value + 1:
sequence += 1
else:
#When sequence breaks, append the number of the sequence to list and reset sequence counter
sequences_list.append(sequence)
sequence = 1
counter += 1
#after while loop, append the last sequence
sequences_list.append(sequence)
#Check that sequence has reached 5 or higher
return max(sequences_list) >= 5
My question is: How could I achieve the same result with a much more condensed code? is there a module (or some other function/trick) that can shortcut this process a bit for more efficient/readable code?
For example, could I import a sequence counter module, break down my "hand" list into a list of values, and return a check that there is a sequence of numbers counting up that has a length of 5 or greater?
E.G.:
import "imaginary_sequence_counter"
def is_straight(hand):
values = [x.value for x in hand]
values.sort()
seq_list = lst(imaginary_sequence_counter(values)
return max(seq_list) >= 5
That's a bit of a silly example, but I hope it clarifies what I'm looking for.
Many thanks for any advice!
As the values are in a small range (1 to 13 I suppose), you can use some binary bit magic.
Let every bit in a single number represent a distinct card value, then "turn on" the bits that correspond to each card in the given hand. Finally check whether there are 5 consecutive 1-bits:
def is_straight(hand):
bitmask = 0
for card in hand:
bitmask |= 1 << card.value
return "11111" in bin(bitmask)
You could use itertools.groupby to count consecutive increasing sequences from the sorted set of card values in the hand:
from itertools import groupby
hand = [1,2,2,3,5,6,7,8,9]
straights = [len(list(g)) for _,g in
groupby(enumerate(sorted(set(hand))),key=lambda h:h[1]-h[0]) ]
print(straights) # [3, 5]
This works by grouping card values based on the difference with their index in the sorted set. Consecutive cards will have the same difference with their index and will end up in the same group. So the length of each group is the length of the sequence.
Related
I am working on a code in Python 2 that partitions a set of 13 elements using integer partitions, then evaluating the different combinations they can have (order does not matter). I have seen the ways people do this by using recursive functions to calculate every partition in a set retroactively, but for what I'm working on I'm taking a different approach.
I'm working with the logic that the different ways a set can be partitioned is determined by the integer partitions of a set. For a set of 4 elements, it can be partitioned in these ways:
[1,1,1,1]
[1,1,2]
[2,2]
[1,3]
[4]
Every number stands for the length of a subset in the partition. Using this info, I can then calculate all of the combinations that can be used with these different integer partitions. If I add the number of combinations from each partition together, I should receive the Bell number (the number of possible partitions in a set). For a list of 4 elements, the Bell number should be 15.
My code runs through the subset lengths in each partition, sets the length of the set to n and the subset length to r, then calculates the combinations in the specific subset. When it goes to the next subset, it subtracts the previous r from n to account for it lessening the amount of combinations available, as n gets smaller when a subset is already defined.
My code, however, is lackluster. When inputting 4 as the length of the set, it outputs 16 (instead of 15). When inputting 5, it outputs 48 (instead of 52). When inputting 13, it outputs 102,513 (instead of 27,644,437). I need it to be exact rather than an estimate.
This is in part because of if elem != 1: not properly accounting for a list of all ones or a list of one subset. It's also in part because it doesn't account for repeats of a combination when appearing in a subset. In [2,2] for a list of 4 elements, it considers the subset to contain 6 combinations when in reality it contains 3.
I'm stuck on how to solve this issue, as I only know enough Python to get by. The way the code currently outputs is how I prefer it to output, obviously without the errors.
The recursive function that calculates the integer partitions is from Nicolas Blanc, and the rest was coded by myself. Important links: Bell number, Partition of a set
import math
in_par = []
stack = []
bell = 0
def partitions(remainder, start_number = 1):
if remainder == 0:
in_par.append(list(stack))
#print stack
else:
for nb_to_add in range(start_number, remainder+1):
stack.append(nb_to_add)
partitions(remainder - nb_to_add, nb_to_add)
stack.pop()
x = partitions(13) # <------- input element count here
for part in in_par:
part.reverse()
combinations = 0
n = 13 # <------- input element count here
for i,elem in enumerate(part):
r = elem
combo = 0
if elem != 1:
if i != (len(part) - 1):
combo = math.factorial(n) / (math.factorial(r) * math.factorial(n-r))
n = n - elem
combinations = combinations + combo
bell = bell + combinations
part.append([combinations])
print part
#print str(bell)
print "Bell Number: " + str(bell)
This question already has answers here:
Elegant Python code for Integer Partitioning [closed]
(11 answers)
Closed 1 year ago.
I'm writing a python function that takes an integer value between 3 and 200 as input, calculates the number of sums using unique nonzero numbers that will equal the number and prints the output.
For example; with 3 as input 1 will be printed because only 1 + 2 will give 3, with 6 as input 3 will be printed because 1+2+3, 1+5 and 2+4 equal 6.
My code works well only for numbers less than 30 after which it starts getting slow. How do I optimize my code to run efficiently for all input between 3 and 200.
from itertools import combinations
def solution(n):
count = 0
max_terms = 0
num = 0
for i in range(1,n):
if num + i <= n:
max_terms += 1
num = num + i
for terms in range(2,max_terms + 1):
for sample in list(combinations(list(range(1,n)),terms)):
if sum(sample) == n:
count += 1
print(count)
Generating all combinations is indeed not very efficient as most will not add up to n.
Instead, you could use a recursive function, which can be called after taking away one partition (i.e. one term of the sum), and will solve the problem for the remaining amount, given an extra indication that future partitions should be greater than the one just taken.
To further improve the efficiency, you can use memoization (dynamic programming) to avoid solving the same sub problem multiple times.
Here is the code for that:
def solution(n, least=1, memo={}):
if n < least:
return 0
key = (n, least)
if key in memo: # Use memoization
return memo[key]
# Counting the case where n is not partitioned
# (But do not count it when it is the original number itself)
count = int(least > 1)
# Counting the cases where n is partitioned
for i in range(least, (n + 1) // 2):
count += solution(n - i, i + 1)
memo[key] = count
return count
Tested the code with these arguments. The comments list the sums that are counted:
print(solution(1)) # none
print(solution(2)) # none
print(solution(3)) # 1+2
print(solution(4)) # 1+3
print(solution(5)) # 1+4, 2+3
print(solution(6)) # 1+2+3, 1+5, 2+4
print(solution(7)) # 1+2+4, 1+6, 2+5, 3+4
print(solution(8)) # 1+2+5, 1+3+4, 1+7, 2+6, 3+5
print(solution(9)) # 1+2+6, 1+3+5, 2+3+4, 1+8, 2+7, 3+6, 4+5
print(solution(10)) # 1+2+3+4, 1+2+7, 1+3+6, 1+4+5, 2+3+5, 1+9, 2+8, 3+7, 4+6
your question isn't clear enough. So, I'm making some assumptionns...
So, what you want is to enter a number. say 4 and then, figure out the total combinations where two different digits add up to that number. If that is what you want, then the answer is quite simple.
for 4, lets take that as 'n'. 'n' has the combinations 1+3,2+2.
for n as 6, the combos are - 1+5,2+4,3+3.
You might have caught a pattern. (4 and 6 have half their combinations) also, for odd numbers, they have combinations that are half their previous even number. i.e. - 5 has (4/2)=2 combos. i.e. 1+4,2+3 so...
the formula to get the number for comnbinations are -
(n)/2 - this is if you want to include same number combos like 2+2 for 4 but, exclude combos with 0. i.e. 0+4 for 4
(n+1)/2 - this works if you want to exclude either the combos with 0 i.e. 0+4 for 4 or the ones with same numbers i.e. 2+2 for 4.
(n-1)/2 - here, same number combos are excluded. i.e. 2+2 wont be counted as a combo for n as 4. also, 0 combos i.e. 0+4 for 4 are excluded.
n is the main number. in these examples, it is '4'. This will work only if n is an integer and these values after calculations are stored as an integer.
3 number combos are totally different. I'm sure there's a formula for that too.
I have been stuck on this problem for a few days now. The program will have a set of 8 numbers like coins = [1,0,3,0,2,1,0,1] and I am trying to write it to return to me every possible set of these numbers. Such as where each element is its own count of coins and what it is returning is every possible combination of coins I can have.
For example [0,0,3,0,2,1,0,1] , [1,0,2,0,2,1,0,1] , [1,0,1,0,1,1,0,1] etc etc until all combinations are stored.
Any help is greatly appreciated
If I understood well, [1,0,3,0,2,1,0,1] means you have 8 coins, 1 coin of denomination A, 3 of denomination C, 2 of denomination E, 1 of denomination F and 1 of denomination G, and you want to get all subsets of this set of coins.
You could use recursion to solve the problem for a list that is shorter (without the first entry) and then take the Cartesian product of the sets, returned by the recursive call, with all possible values for the entry that was left out. At the deepest level of recursion, the list will be empty, and then obviously there is only one combination to return: an empty list.
def combinations(coins):
if len(coins) == 0:
return [coins]
return [[i] + combi for i in range(coins[0]+1) for combi in combinations(coins[1:])]
# example call:
print(combinations([2,0,3]))
Without using list comprehension, it would look like this:
def combinations(coins):
if len(coins) == 0:
return [coins]
res = []
for i in range(coins[0]+1):
for combi in combinations(coins[1:]):
res.append([i] + combi)
return res
I'm new to Python and I am trying to generate a list of 4 random numbers with integers between 1 and 9. The list must contain no repeating integers.
The issue I am having is that the program doesn't output exactly 4 numbers everytime. Sometimes it generates 3 numbers or 2 numbers and I can't figure out how to fix it.
My code:
import random
lst = []
for i in range(5):
r = random.randint(1,9)
if r not in lst: lst.append(r)
print(lst)
Is there a way to do it without the random.sample? This code is part of a larger assignment for school and my teacher doesn't want us using the random.sample or random.shuffle functions.
Your code generates 5 random numbers, but they are not necessarily unique. If a 2 is generated and you already have 2 in list you don't append it, while you should really be generating an alternative digit that hasn't been used yet.
You could use a while loop to test if you already have enough numbers:
result = [] # best not to use list as a variable name!
while len(result) < 5:
digit = random.randint(1, 9)
if digit not in result:
result.append(digit)
but that's all more work than really needed, and could in theory take forever (as millions of repeats of the same 4 initial numbers is still considered random). The standard library has a better method for just this task.
Instead, you can use random.sample() to take 5 unique numbers from a range() object:
result = random.sample(range(1, 10), 5)
This is guaranteed to produce 5 values taken from the range, without duplicate digits, and it does so in 5 steps.
Use random.sample:
import random
random.sample(range(1, 10), 4)
This generates a list of four random values between 1 to 9 with no duplicates.
Your issue is, you're iterating 5 times, with a random range of 1-9. That means you have somewhere in the neighborhood of a 50/50 chance of getting a repeat integer, which your conditional prevents from being appended to your list.
This will serve you better:
def newRunLst():
lst = []
while len(lst) < 4:
r = random.randint(1,9)
if r not in lst: lst.append(r)
print lst
if random list needed is not too small (compared to the total list) then can
generate an indexed DataFrame of random numbers
sort it and
select from the top ... like
(pd.DataFrame([(i,np.random.rand()) for i in range(10)]).sort_values(by=1))[0][:5].sort_index()
I tried to create a function for generating a set number (numbersToChoose) of values between two other values (startFrom and stopAt) but for some reason the iterator (?) in the second for-loop (line 7), in this case a seems to be randomly generated even though I don't see any reason for that.
def chooseRandomNumbers(numbersToChoose, startFrom, stopAt):
numbers = [(randrange(startFrom, stopAt))] # initialize empty list of numbers
for n in range(numbersToChoose-1): # choose random number from 1 to 49 a total of 7 times
x = randrange(startFrom, stopAt+1)
for a in numbers: # check for already existing elements in "numbers"
if x == numbers[a]: # if new random number already exists in "numbers" discard and run loop again
n -= 1 # decreases n by 1 so a new (hopefully not already existing) number can be generated
else: # generated numbers does not exist in "numbers" yet
numbers.append(x) # appends randomly chosen number to list "numbers"
return numbers # returns said list "numbers"
Any advice on how to deal with this is greatly appreciated.
Also please tell me if possible anything else bad in the code (just started python).
Your code to check if generated number is already in the list is wrong.
for a in numbers, in this loop, you are using numbers[a] while a is a member of list but not the index of the member.
use in to test if a number is in list:
from random import randrange
def chooseRandomNumbers(numbersToChoose, startFrom, stopAt):
numbers = []
for n in range(numbersToChoose):
x = randrange(startFrom, stopAt+1)
if not x in numbers:
numbers.append(x)
else:
n -= 1
return numbers
or simply:
from random import sample
def chooseRandomNumbers(numbersToChoose, startFrom, stopAt):
return sample(range(startFrom,stopAt+1),numbersToChoose)