Python Randomly assign a list from a set number - python

What i'm trying to do is make a list that gets filled with different combinations of numbers (not even) that all add up to a pre defined number.
Example, if I have the a variable total = 50 as well as a list that holds 7 numbers, each time I generate and print the list in a loop, the results will be completly different with some being huge and others near empty or empty. I dont want any restrictions for the range of the value (could come as 0 or the entire 50, and next time may even be all balanced).
Is this possible?
Thanks
EDIT: I've gotten to here, but it seems to prioritize the ending, how can I make each variable have an equal chance of high or low numbers?
`import random
tot = 50
size = 7
s = 0
run = 7
num = {}
while run > 0:
num[run] = random.randint(s,tot)
tot -= num[run]
run -= 1
print(str(num))
`

Disclaimer: I don't mind what this code is meant to be.
from random import randint, seed
seed(345)
def bizarre(total, slots):
tot = total
acc = []
for _ in range(slots-1):
r = randint(0,tot)
tot -= r
acc.append(r)
acc.append(total-sum(acc))
return acc
# testing code
for i in range(10):
tot = randint(50,80)
n = randint(5,10)
b = bizarre(tot, n)
print "%3d %3d %s -> %d" % (tot, n, b, sum(b))
Output
73 5 [73, 0, 0, 0, 0] -> 73
54 6 [36, 5, 9, 0, 3, 1] -> 54
60 7 [47, 6, 6, 1, 0, 0, 0] -> 60
69 7 [3, 48, 15, 3, 0, 0, 0] -> 69
72 8 [36, 18, 18, 0, 0, 0, 0, 0] -> 72
65 8 [17, 32, 13, 3, 0, 0, 0, 0] -> 65
54 7 [33, 13, 0, 2, 4, 1, 1] -> 54
54 6 [7, 11, 26, 3, 5, 2] -> 54
67 7 [62, 5, 0, 0, 0, 0, 0] -> 67
67 8 [28, 25, 1, 0, 10, 3, 0, 0] -> 67

If you want a list of n random numbers that add up to a variable x, create n-1 random numbers. Then last number is the difference between x and the n-1 random numbers. For example, if you want a list of size three that adds up to 5 create two numbers randomly, 1 and 2. 1+2 = 3, 5-3 = 2, so the list is 1,2,2.

Related

Creating an M*N matrix of unique numbers from 1-90

I want to develop an algorithm in python that returns an 18x9 matrix randomly populated with numbers from 1 to 90 using certain rules/conditions.
Rules
#1 - Maintain an array of 18x9 between 1 and 90.
#2 - First column contains 1-10, second column contains 11-20, etc.
#3 - Each row must have 5 numbers. Other columns should be set to 0.
#4 - Numbers must be arranged in ascending order from top to bottom in a column
What I have done so far?
import numpy as np
columns = 9
rows = 18
n_per_row = 5
matrix = np.zeros((rows, columns), dtype=int)
# Keep track of available places at each row.
available_places = {k: n_per_row for k in range(rows)}
# Shuffle order in which we fill the columns.
col_order = np.arange(columns)
np.random.shuffle(col_order)
for column in col_order:
# Indices of available rows.
indices = [c for c in available_places if available_places[c]]
# Sample which row to use of the available.
indices = np.random.choice(indices, size=min(len(indices), 10), replace=False)
# print(indices)
# Values for this column.
col_values = np.random.choice(list(np.arange(1, 10+1)), size=min(len(indices), 10), replace=False) + column*10
# Fill in ascending order.
matrix[sorted(indices), column] = sorted(col_values)
for idx in indices:
available_places[idx] -= 1
print(matrix)
Result
[[ 0 0 0 31 0 51 0 71 81]
[ 1 11 0 0 0 52 61 72 0]
[ 0 0 21 32 41 0 62 73 0]
[ 0 0 22 33 0 0 0 74 82]
[ 0 12 23 0 42 0 63 0 83]
[ 2 13 24 34 0 53 0 0 0]
[ 3 0 0 0 43 54 64 0 84]
[ 4 0 0 0 44 55 65 0 85]
[ 5 14 0 0 45 0 66 0 86]
[ 6 0 25 35 46 0 0 75 0]
[ 7 15 26 36 0 0 0 0 87]
[ 8 16 0 0 47 0 0 76 88]
[ 0 17 27 0 48 0 0 77 89]
[ 0 18 0 0 49 56 67 78 0]
[ 9 0 28 39 0 57 0 79 0]
[ 0 0 29 0 50 58 68 80 0]
[ 0 19 30 40 0 59 69 0 0]
[10 20 0 0 0 60 70 0 90]]
Expected Result: https://images.meesho.com/images/products/56485141/snksv_512.jpg
Final result according to the 4 rules
5 values per row
10 values per column starting with 1,11,21, etc in ascending order
( Notice these rules are not ok for a bingo as seen in the image )
============ final matrix ===============
--------------------------------
[1, 11, 21, 31, 41, 0, 0, 0, 0]
[2, 12, 0, 32, 42, 0, 61, 0, 0]
[0, 13, 0, 33, 0, 0, 62, 71, 81]
[3, 0, 0, 34, 0, 0, 63, 72, 82]
[0, 0, 22, 0, 0, 51, 64, 73, 83]
[4, 14, 23, 35, 0, 52, 0, 0, 0]
[5, 0, 24, 0, 43, 53, 0, 0, 84]
[6, 15, 0, 36, 44, 54, 0, 0, 0]
[7, 0, 0, 37, 0, 0, 65, 74, 85]
[0, 0, 0, 0, 45, 55, 66, 75, 86]
[8, 16, 25, 0, 0, 0, 67, 76, 0]
[0, 0, 26, 0, 46, 56, 0, 77, 87]
[9, 17, 0, 0, 0, 0, 68, 78, 88]
[10, 18, 0, 0, 0, 57, 0, 79, 89]
[0, 19, 27, 38, 47, 0, 0, 80, 0]
[0, 20, 28, 39, 48, 58, 0, 0, 0]
[0, 0, 29, 0, 49, 59, 69, 0, 90]
[0, 0, 30, 40, 50, 60, 70, 0, 0]
--------------------------------
Principles :
Establish first a matrix with 0 and 1 set as placeholders for future values.
Randomize 0 or 1 per cell in the matrix, but survey # of 1 in a row and # of 1 in a col to respect constraints.
As it could happen that random gives not enough 1 early, the both constraints cannot be satisfied at first try. Prog retry automatically and traces each try for observation. (max seen in my tests : 10 loops, mean : <=3 loops)
Once a satisfactory matrix of 0 & 1 is obtained, replace each 1 by the corresponding value for each col.
A solution :
import random
# #1 - Maintain an array of 18x9 (between 1 and 90)
maxRow = 18
maxCol = 9
# #2 - First column contains 1-10, second column contains 11-20, etc.
# ie first column 1 start from 1 and have 10 entries, column 2 start from 11 and have 10 entries, etc.
origins = [i*10 +1 for i in range(maxCol)] #[1, 11, 21, 31, 41, 51, 61, 71, 81]
maxInCol = [10 for i in range(maxCol)] #[10, 10, 10, 10, 10, 10, 10, 10, 10]
# comfort : display matrix
def showMatrix():
print('--------------------------------')
for row in range(len(matrix)):
print(matrix[row])
print('--------------------------------')
# comfort : count #values in a col
def countInCol(col):
count = 0
for row in range(maxRow):
count+=matrix[row][col]
return count
# verify the rules of 5 per row and 10 per cols
def verify():
ok = True
showMatrix()
# count elements in a col
for col in range(maxCol):
count = 0
for row in range(maxRow):
count+= matrix[row][col]
if(count!= maxInCol[col]):
print ('*** wrong # of elements in col {0} : {1} instead of {2}'.format(col, count,maxInCol[col]))
ok = False
# count elements in a row
for row in range(maxRow):
count = 0
for col in range(maxCol):
count+= matrix[row][col]
if(count!=5):
print('***** wrong # of elements in row {0} : {1}'.format(row, count))
ok = False
if (not ok): print( '********************************************')
return ok
# -- main ----
# need to iterate in case of no more value to complete a col
tour = 1
maxTour = 100 #security limit
while True:
# prepare a matrix of rows of cols of 0
matrix = [[0 for i in range(maxCol)] for i in range(18)]
# begin to fill some places with 1 instead of 0
for row in range(maxRow):
count = 0
for col in range(maxCol):
if (count==5): break # line is already full with 5 elt
# random a 0 or 1
placeHolder = random.choice([0,1])
# if the remaining cols of this row needs to be 1 to complete at 5/row
if (5-count) == (maxCol-col):
placeHolder = 1 # must complete the row
else:
inCol = countInCol(col)
# 10 places max in col
if (inCol)==maxInCol[col]: placeHolder = 0 # this col is full
# constraint : if the remaining rows of this col need to be 1 to complete the expected 10 values
if(maxRow-row) == (maxInCol[col]-inCol): placeHolder = 1
matrix[row][col] = placeHolder
count+= placeHolder
#-------- some case are not correct . prog loops
if verify():
print(' ok after {0} loop(s)'.format(tour))
break
# security infinite loop
if (tour>=maxTour): break
tour +=1
# now replace the placeholders by successive values per col
print('\n============ final matrix ===============')
for row in range(maxRow):
for col in range(maxCol):
if matrix[row][col]==1:
matrix[row][col] = origins[col]
origins[col]+=1
showMatrix()
HTH

How to get index of multiple, possibly different, elements in numpy?

I have a numpy array with many rows in it that look roughly as follows:
0, 50, 50, 2, 50, 1, 50, 99, 50, 50
50, 2, 1, 50, 50, 50, 98, 50, 50, 50
0, 50, 50, 98, 50, 1, 50, 50, 50, 50
0, 50, 50, 50, 50, 99, 50, 50, 2, 50
2, 50, 50, 0, 98, 1, 50, 50, 50, 50
I am given a variable n<50. Each row, of length 10, has the following in it:
Every number from 0 to n, with one possibly missing. In the example above, n=2.
Possibly a 98, which will be in the place of the missing number, if there is a number missing.
Possibly a 99, which will be in the place of the missing number, if there is a number missing, and there is not already a 98.
Many 50's.
What I want to get is an array with all the indices of the 0s in the first row, all the indices of the 1s in the second row, all the indices of the 2s in the third row, etc. For the above example, my desired output is this:
0, 6, 0, 0, 3
5, 2, 5, 5, 5
3, 1, 3, 8, 0
You may have noticed the catch: sometimes, exactly one of the numbers is replaced either by a 98, or a 99. It's pretty easy to write a for loop which determines which number, if any, was replaced, and uses that to get the array of indices.
Is there a way to do this with numpy?
The follwing numpy solution rather aggressively uses the assumptions listed in OP. If they are not 100% guaranteed some more checks may be in order.
The mildly clever bit (even if I say so myself) here is to use the data array itself for finding the right destinations of their indices. For example, all the 2's need their indices stored in row 2 of the output array. Using this we can bulk store most of the indices in a single operation.
Example input is in array data:
n = 2
y,x = data.shape
out = np.empty((y,n+1),int)
# find 98 falling back to 99 if necessary
# and fill output array with their indices
# if neither exists some nonsense will be written but that does no harm
# most of this will be overwritten later
out.T[...] = ((data-98)&127).argmin(axis=1)
# find n+1 lowest values in each row
idx = data.argpartition(n,axis=1)[:,:n+1]
# construct auxiliary indexer
yr = np.arange(y)[:,None]
# put indices of low values where they belong
out[yr,data[yr,idx[:,:-1]]] = idx[:,:-1]
# ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
# the clever bit
# rows with no missing number still need the last value
nomiss, = (data[range(y),idx[:,n]] == n).nonzero()
out[nomiss,n] = idx[nomiss,n]
# admire
print(out.T)
outputs:
[[0 6 0 0 3]
[5 2 5 5 5]
[3 1 3 8 0]]
I don't think you're getting away without a for-loop here. But here's how you could go about it.
For each number in n, find all of the locations where it is known. Example:
locations = np.argwhere(data == 1)
print(locations)
[[0 5]
[1 2]
[2 5]
[4 5]]
You can then turn this into a map for easy lookup per number in n:
known = {
i: dict(np.argwhere(data == i))
for i in range(n + 1)
}
pprint(known)
{0: {0: 0, 2: 0, 3: 0, 4: 3},
1: {0: 5, 1: 2, 2: 5, 4: 5},
2: {0: 3, 1: 1, 3: 8, 4: 0}}
Do the same for the unknown numbers:
unknown = dict(np.argwhere((data == 98) | (data == 99)))
pprint(unknown)
{0: 7, 1: 6, 2: 3, 3: 5, 4: 4}
And now for each location in the result, you can lookup the index in the known list and fallback to the unknown.
result = np.array(
[
[known[i].get(j, unknown.get(j)) for j in range(len(data))]
for i in range(n + 1)
]
)
print(result)
[[0 6 0 0 3]
[5 2 5 5 5]
[3 1 3 8 0]]
Bonus: Getting fancy with dictionary constructor and unpacking:
from collections import OrderedDict
unknown = np.argwhere((data == 98) | (data == 99))
results = np.array([
[*OrderedDict((*unknown, *np.argwhere(data == i))).values()]
for i in range(n + 1)
])
print(results)

Numpy where() using a condition that changes with the items position in the array

I'm trying to build a grid world using numpy.
The grid is 4*4 and laid out in a square.
The first and last squares (i.e. 1 and 16) are terminal squares.
At each time step you can move one step in any direction either: up, down , left or right.
Once you enter one of the terminal squares no further moves are possible and the game terminates.
The first and last columns are the left and right edges of the square whilst the first and last rows represent the top and bottom edges.
If you are on an edge, for example the left one and attempt to move left, instead of moving left you stay in the square you started in. Similarly you remain in the same square if you try and cross any of the other edges.
Although the grid is a square I've implemented it as an array.
States_r calculates the position of the states after a move right. 1 and 16 stay where they are because they are terminal states (note the code uses zero based counting so 1 and 16 are 0 and 15 respectively in the code).
The rest of the squares are in increased by one. The code for states_r works however those squares on the right edge i.e. (4, 8, 12) should also stay where they are but states_r code doesn't do that.
State_l is my attempt to include the edge condition for the left edge of the square. The logic is the same the terminal states (1, 16) should not move nor should those squares on the left edge (5, 9, 13). I think the general logic is correct but it's producing an error.
states = np.arange(16)
states_r = states[np.where((states + 1 <= 15) & (states != 0), states + 1, states)]
states_l = states[np.where((max(1, (states // 4) * 4) <= states - 1) & (states != 15), states - 1, states)]
The first example states_r works, it handles the terminal state but does not handle the edge condition.
The second example is my attempt to include the edge condition, however it is giving me the following error:
"The truth value of an array with more than one element is ambiguous."
Can someone please explain how to fix my code?
Or alternatively suggest another solution,ideally I want the code to be fast (so I can scale it up) so I want to avoid for loops if possible?
If I understood correctly you want arrays which indicate for each state where the next state is, depending on the move (right, left, up, down).
If so, I guess your implementation of state_r is not quit right. I would suggest to switch to a 2D representation of your grid, because a lot of the things you describe are easier and more intuitive to handle if you have x and y directly (at least for me).
import numpy as np
n = 4
states = np.arange(n*n).reshape(n, n)
states_r, states_l, states_u, states_d = (states.copy(), states.copy(),
states.copy(), states.copy())
states_r[:, :n-1] = states[:, 1:]
states_l[:, 1:] = states[:, :n-1]
states_u[1:, :] = states[:n-1, :]
states_d[:n-1, :] = states[1:, :]
# up [[ 0, 1, 2, 3],
# left state right [ 0, 1, 2, 3],
# down [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]]
#
# [[ 0, 0, 1, 2], [[ 0, 1, 2, 3], [[ 1, 2, 3, 3],
# [ 4, 4, 5, 6], [ 4, 5, 6, 7], [ 5, 6, 7, 7],
# [ 8, 8, 9, 10], [ 8, 9, 10, 11], [ 9, 10, 11, 11],
# [12, 12, 13, 14]] [12, 13, 14, 15]] [13, 14, 15, 15]]
#
# [[ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [12, 13, 14, 15]]
If you want to exclude the terminal states, you can do something like this:
terminal_states = np.zeros((n, n), dtype=bool)
terminal_states[0, 0] = True
terminal_states[-1, -1] = True
states_r[terminal_states] = states[terminal_states]
states_l[terminal_states] = states[terminal_states]
states_u[terminal_states] = states[terminal_states]
states_d[terminal_states] = states[terminal_states]
If you prefer the 1D approach:
import numpy as np
n = 4
states = np.arange(n*n)
valid_s = np.ones(n*n, dtype=bool)
valid_s[0] = False
valid_s[-1] = False
states_r = np.where(np.logical_and(valid_s, states % n < n-1), states+1, states)
states_l = np.where(np.logical_and(valid_s, states % n > 0), states-1, states)
states_u = np.where(np.logical_and(valid_s, states > n-1), states-n, states)
states_d = np.where(np.logical_and(valid_s, states < n**2-n), states+n, states)
Another way of doing it without preallocating arrays:
states = np.arange(16).reshape(4,4)
states_l = np.hstack((states[:,0][:,None],states[:,:-1],))
states_r = np.hstack((states[:,1:],states[:,-1][:,None]))
states_d = np.vstack((states[1:,:],states[-1,:]))
states_u = np.vstack((states[0,:],states[:-1,:]))
To get them all in 1-D, you can always flatten()/ravel()/reshape(-1) the 2-D arrays.
[[ 0 1 2 3]
[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[ 0 0 1 2] [[ 0 1 2 3] [[ 1 2 3 3]
[ 4 4 5 6] [ 4 5 6 7] [ 5 6 7 7]
[ 8 8 9 10] [ 8 9 10 11] [ 9 10 11 11]
[12 12 13 14]] [12 13 14 15]] [13 14 15 15]]
[[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]
[12 13 14 15]]
And for corners you can do:
states_u[-1,-1] = 15
states_l[-1,-1] = 15

Generating random numbers to obtain a fixed sum(python) [duplicate]

This question already has answers here:
Generate random numbers summing to a predefined value
(7 answers)
Closed 4 years ago.
I have the following list:
Sum=[54,1536,36,14,9,360]
I need to generate 4 other lists, where each list will consist of 6 random numbers starting from 0, and the numbers will add upto the values in sum. For eg;
l1=[a,b,c,d,e,f] where a+b+c+d+e+f=54
l2=[g,h,i,j,k,l] where g+h+i+j+k+l=1536
and so on upto l6. And I need to do this in python. Can it be done?
Generating a list of random numbers that sum to a certain integer is a very difficult task. Keeping track of the remaining quantity and generating items sequentially with the remaining available quantity results in a non-uniform distribution, where the first numbers in the series are generally much larger than the others. On top of that, the last one will always be different from zero because the previous items in the list will never sum up to the desired total (random generators usually use open intervals in the maximum). Shuffling the list after generation might help a bit but won't generally give good results either.
A solution could be to generate random numbers and then normalize the result, eventually rounding it if you need them to be integers.
import numpy as np
totals = np.array([54,1536,36,14]) # don't use Sum because sum is a reserved keyword and it's confusing
a = np.random.random((6, 4)) # create random numbers
a = a/np.sum(a, axis=0) * totals # force them to sum to totals
# Ignore the following if you don't need integers
a = np.round(a) # transform them into integers
remainings = totals - np.sum(a, axis=0) # check if there are corrections to be done
for j, r in enumerate(remainings): # implement the correction
step = 1 if r > 0 else -1
while r != 0:
i = np.random.randint(6)
if a[i,j] + step >= 0:
a[i, j] += step
r -= step
Each column of a represents one of the lists you want.
Hope this helps.
This might not be the most efficient way but it will work
totals = [54, 1536, 36, 14]
nums = []
x = np.random.randint(0, i, size=(6,))
for i in totals:
while sum(x) != i: x = np.random.randint(0, i, size=(6,))
nums.append(x)
print(nums)
[array([ 3, 19, 21, 11, 0, 0]), array([111, 155, 224, 511, 457,
78]), array([ 8, 5, 4, 12, 2, 5]), array([3, 1, 3, 2, 1, 4])]
This is a way more efficient way to do this
totals = [54,1536,36,14,9,360, 0]
nums = []
for i in totals:
if i == 0:
nums.append([0 for i in range(6)])
continue
total = i
temp = []
for i in range(5):
val = np.random.randint(0, total)
temp.append(val)
total -= val
temp.append(total)
nums.append(temp)
print(nums)
[[22, 4, 16, 0, 2, 10], [775, 49, 255, 112, 185, 160], [2, 10, 18, 2,
0, 4], [10, 2, 1, 0, 0, 1], [8, 0, 0, 0, 0, 1], [330, 26, 1, 0, 2, 1],
[0, 0, 0, 0, 0, 0]]

Python Recursion in pascal's triangle [duplicate]

As a learning experience for Python, I am trying to code my own version of Pascal's triangle. It took me a few hours (as I am just starting), but I came out with this code:
pascals_triangle = []
def blank_list_gen(x):
while len(pascals_triangle) < x:
pascals_triangle.append([0])
def pascals_tri_gen(rows):
blank_list_gen(rows)
for element in range(rows):
count = 1
while count < rows - element:
pascals_triangle[count + element].append(0)
count += 1
for row in pascals_triangle:
row.insert(0, 1)
row.append(1)
pascals_triangle.insert(0, [1, 1])
pascals_triangle.insert(0, [1])
pascals_tri_gen(6)
for row in pascals_triangle:
print(row)
which returns
[1]
[1, 1]
[1, 0, 1]
[1, 0, 0, 1]
[1, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 0, 1]
[1, 0, 0, 0, 0, 0, 0, 1]
However, I have no idea where to go from here. I have been banging my head against the wall for hours. I want to emphasize that I do NOT want you to do it for me; just push me in the right direction. As a list, my code returns
[[1], [1, 1], [1, 0, 1], [1, 0, 0, 1], [1, 0, 0, 0, 1], [1, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 1], [1, 0, 0, 0, 0, 0, 0, 1]]
Thanks.
EDIT: I took some good advice, and I completely rewrote my code, but I am now running into another problem. Here is my code.
import math
pascals_tri_formula = []
def combination(n, r):
return int((math.factorial(n)) / ((math.factorial(r)) * math.factorial(n - r)))
def for_test(x, y):
for y in range(x):
return combination(x, y)
def pascals_triangle(rows):
count = 0
while count <= rows:
for element in range(count + 1):
[pascals_tri_formula.append(combination(count, element))]
count += 1
pascals_triangle(3)
print(pascals_tri_formula)
However, I am finding that the output is a bit undesirable:
[1, 1, 1, 1, 2, 1, 1, 3, 3, 1]
How can I fix this?
OK code review:
import math
# pascals_tri_formula = [] # don't collect in a global variable.
def combination(n, r): # correct calculation of combinations, n choose k
return int((math.factorial(n)) / ((math.factorial(r)) * math.factorial(n - r)))
def for_test(x, y): # don't see where this is being used...
for y in range(x):
return combination(x, y)
def pascals_triangle(rows):
result = [] # need something to collect our results in
# count = 0 # avoidable! better to use a for loop,
# while count <= rows: # can avoid initializing and incrementing
for count in range(rows): # start at 0, up to but not including rows number.
# this is really where you went wrong:
row = [] # need a row element to collect the row in
for element in range(count + 1):
# putting this in a list doesn't do anything.
# [pascals_tri_formula.append(combination(count, element))]
row.append(combination(count, element))
result.append(row)
# count += 1 # avoidable
return result
# now we can print a result:
for row in pascals_triangle(3):
print(row)
prints:
[1]
[1, 1]
[1, 2, 1]
Explanation of Pascal's triangle:
This is the formula for "n choose k" (i.e. how many different ways (disregarding order), from an ordered list of n items, can we choose k items):
from math import factorial
def combination(n, k):
"""n choose k, returns int"""
return int((factorial(n)) / ((factorial(k)) * factorial(n - k)))
A commenter asked if this is related to itertools.combinations - indeed it is. "n choose k" can be calculated by taking the length of a list of elements from combinations:
from itertools import combinations
def pascals_triangle_cell(n, k):
"""n choose k, returns int"""
result = len(list(combinations(range(n), k)))
# our result is equal to that returned by the other combination calculation:
assert result == combination(n, k)
return result
Let's see this demonstrated:
from pprint import pprint
ptc = pascals_triangle_cell
>>> pprint([[ptc(0, 0),],
[ptc(1, 0), ptc(1, 1)],
[ptc(2, 0), ptc(2, 1), ptc(2, 2)],
[ptc(3, 0), ptc(3, 1), ptc(3, 2), ptc(3, 3)],
[ptc(4, 0), ptc(4, 1), ptc(4, 2), ptc(4, 3), ptc(4, 4)]],
width = 20)
[[1],
[1, 1],
[1, 2, 1],
[1, 3, 3, 1],
[1, 4, 6, 4, 1]]
We can avoid repeating ourselves with a nested list comprehension:
def pascals_triangle(rows):
return [[ptc(row, k) for k in range(row + 1)] for row in range(rows)]
>>> pprint(pascals_triangle(15))
[[1],
[1, 1],
[1, 2, 1],
[1, 3, 3, 1],
[1, 4, 6, 4, 1],
[1, 5, 10, 10, 5, 1],
[1, 6, 15, 20, 15, 6, 1],
[1, 7, 21, 35, 35, 21, 7, 1],
[1, 8, 28, 56, 70, 56, 28, 8, 1],
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1],
[1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1],
[1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1],
[1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1],
[1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 1],
[1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 1]]
Recursively defined:
We can define this recursively (a less efficient, but perhaps more mathematically elegant definition) using the relationships illustrated by the triangle:
def choose(n, k): # note no dependencies on any of the prior code
if k in (0, n):
return 1
return choose(n-1, k-1) + choose(n-1, k)
And for fun, you can see each row take progressively longer to execute, because each row has to recompute nearly each element from the prior row twice each time:
for row in range(40):
for k in range(row + 1):
# flush is a Python 3 only argument, you can leave it out,
# but it lets us see each element print as it finishes calculating
print(choose(row, k), end=' ', flush=True)
print()
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
1 10 45 120 210 252 210 120 45 10 1
1 11 55 165 330 462 462 330 165 55 11 1
1 12 66 220 495 792 924 792 495 220 66 12 1
1 13 78 286 715 1287 1716 1716 1287 715 286 78 13 1
1 14 91 364 1001 2002 3003 3432 3003 2002 1001 364 91 14 1
1 15 105 455 1365 3003 5005 6435 6435 5005 3003 1365 455 105 15 1
1 16 120 560 1820 4368 8008 11440 12870 11440 8008 4368 1820 560 120 16 1
1 17 136 680 2380 6188 12376 19448 24310 24310 19448 12376 6188 2380 680 136 17 1
1 18 153 816 3060 8568 18564 31824 43758 48620 43758 31824 18564 8568 3060 816 ...
Ctrl-C to quit when you get tired of watching it, it gets very slow very fast...
I know you want to implement yourself, but the best way for me to explain is to walk through an implementation. Here's how I would do it, and this implementation relies on my fairly complete knowledge of how Python's functions work, so you probably won't want to use this code yourself, but it may get you pointed in the right direction.
def pascals_triangle(n_rows):
results = [] # a container to collect the rows
for _ in range(n_rows):
row = [1] # a starter 1 in the row
if results: # then we're in the second row or beyond
last_row = results[-1] # reference the previous row
# this is the complicated part, it relies on the fact that zip
# stops at the shortest iterable, so for the second row, we have
# nothing in this list comprension, but the third row sums 1 and 1
# and the fourth row sums in pairs. It's a sliding window.
row.extend([sum(pair) for pair in zip(last_row, last_row[1:])])
# finally append the final 1 to the outside
row.append(1)
results.append(row) # add the row to the results.
return results
usage:
>>> for i in pascals_triangle(6):
... print(i)
...
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
Without using zip, but using generator:
def gen(n,r=[]):
for x in range(n):
l = len(r)
r = [1 if i == 0 or i == l else r[i-1]+r[i] for i in range(l+1)]
yield r
example:
print(list(gen(15)))
output:
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1], [1, 6, 15, 20, 15, 6, 1], [1, 7, 21, 35, 35, 21, 7, 1], [1, 8, 28, 56, 70, 56, 28, 8, 1], [1, 9, 36, 84, 126, 126, 84, 36, 9, 1], [1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1], [1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1], [1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1], [1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 1], [1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 1]]
DISPLAY AS TRIANGLE
To draw it in beautiful triangle(works only for n < 7, beyond that it gets distroted. ref draw_beautiful for n>7)
for n < 7
def draw(n):
for p in gen(n):
print(' '.join(map(str,p)).center(n*2)+'\n')
eg:
draw(10)
output:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
for any size
since we need to know the max width, we can't make use of generator
def draw_beautiful(n):
ps = list(gen(n))
max = len(' '.join(map(str,ps[-1])))
for p in ps:
print(' '.join(map(str,p)).center(max)+'\n')
example (2) :
works for any number:
draw_beautiful(100)
Here is my attempt:
def generate_pascal_triangle(rows):
if rows == 1: return [[1]]
triangle = [[1], [1, 1]] # pre-populate with the first two rows
row = [1, 1] # Starts with the second row and calculate the next
for i in range(2, rows):
row = [1] + [sum(column) for column in zip(row[1:], row)] + [1]
triangle.append(row)
return triangle
for row in generate_pascal_triangle(6):
print row
Discussion
The first two rows of the triangle is hard-coded
The zip() call basically pairs two adjacent numbers together
We still have to add 1 to the beginning and another 1 to the end because the zip() call only generates the middle of the next row
# combining the insights from Aaron Hall and Hai Vu,
# we get:
def pastri(n):
rows = [[1]]
for _ in range(1, n+1):
rows.append([1] +
[sum(pair) for pair in zip(rows[-1], rows[-1][1:])] +
[1])
return rows
# thanks! learnt that "shape shifting" data,
# can yield/generate elegant solutions.
def pascal(n):
if n==0:
return [1]
else:
N = pascal(n-1)
return [1] + [N[i] + N[i+1] for i in range(n-1)] + [1]
def pascal_triangle(n):
for i in range(n):
print pascal(i)
Beginner Python student here. Here's my attempt at it, a very literal approach, using two For loops:
pascal = [[1]]
num = int(input("Number of iterations: "))
print(pascal[0]) # the very first row
for i in range(1,num+1):
pascal.append([1]) # start off with 1
for j in range(len(pascal[i-1])-1):
# the number of times we need to run this loop is (# of elements in the row above)-1
pascal[i].append(pascal[i-1][j]+pascal[i-1][j+1])
# add two adjacent numbers of the row above together
pascal[i].append(1) # and cap it with 1
print(pascal[i])
Here is an elegant and efficient recursive solution. I'm using the very handy toolz library.
from toolz import memoize, sliding_window
#memoize
def pascals_triangle(n):
"""Returns the n'th row of Pascal's triangle."""
if n == 0:
return [1]
prev_row = pascals_triangle(n-1)
return [1, *map(sum, sliding_window(2, prev_row)), 1]
pascals_triangle(300) takes about 15 ms on a macbook pro (2.9 GHz Intel Core i5). Note that you can't go much higher without increasing the default recursion depth limit.
I am cheating from the popular fibonacci sequence solution. To me, the implementation of Pascal's triangle would have the same concept of fibonacci's. In fibonacci we use a single number at a time and add it up to the previous one. In pascal's triangle use a row at a time and add it up to the previous one.
Here is a complete code example:
>>> def pascal(n):
... r1, r2 = [1], [1, 1]
... degree = 1
... while degree <= n:
... print(r1)
... r1, r2 = r2, [1] + [sum(pair) for pair in zip(r2, r2[1:]) ] + [1]
... degree += 1
Test
>>> pascal(3)
[1]
[1, 1]
[1, 2, 1]
>>> pascal(4)
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
>>> pascal(6)
[1]
[1, 1]
[1, 2, 1]
[1, 3, 3, 1]
[1, 4, 6, 4, 1]
[1, 5, 10, 10, 5, 1]
Note: to have the result as a generator, change print(r1) to yield r1.
# call the function ! Indent properly , everything should be inside the function
def triangle():
matrix=[[0 for i in range(0,20)]for e in range(0,10)] # This method assigns 0's to all Rows and Columns , the range is mentioned
div=20/2 # it give us the most middle columns
matrix[0][div]=1 # assigning 1 to the middle of first row
for i in range(1,len(matrix)-1): # it goes column by column
for j in range(1,20-1): # this loop goes row by row
matrix[i][j]=matrix[i-1][j-1]+matrix[i-1][j+1] # this is the formula , first element of the matrix gets , addition of i index (which is 0 at first ) with third value on the the related row
# replacing 0s with spaces :)
for i in range(0,len(matrix)):
for j in range(0,20):
if matrix[i][j]==0: # Replacing 0's with spaces
matrix[i][j]=" "
for i in range(0,len(matrix)-1): # using spaces , the triangle will printed beautifully
for j in range(0,20):
print 1*" ",matrix[i][j],1*" ", # giving some spaces in two sides of the printing numbers
triangle() # calling the function
would print something like this
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
Here is a simple way of implementing the pascal triangle:
def pascal_triangle(n):
myList = []
trow = [1]
y = [0]
for x in range(max(n,0)):
myList.append(trow)
trow=[l+r for l,r in zip(trow+y, y+trow)]
for item in myList:
print(item)
pascal_triangle(5)
Python zip() function returns the zip object, which is the iterator of tuples where the first item in each passed iterator is paired together, and then the second item in each passed iterator are paired together. Python zip is the container that holds real data inside.
Python zip() function takes iterables (can be zero or more), makes an iterator that aggregates items based on the iterables passed, and returns the iterator of tuples.
I did this when i was working with my son on intro python piece. It started off as rather simple piece, when we targeted -
1
1 2
1 2 3
1 2 3 4
However, as soon as we hit the actual algorithm, complexity overshot our expectations. Anyway, we did build this -
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
Used some recursion -
def genRow(row:list) :
# print(f"generatig new row below {row}")
# printRow(row)
l = len(row) #2
newRow : list = []
i = 0
# go through the incoming list
while i <= l:
# print(f"working with i = {i}")
# append an element in the new list
newRow.append(1)
# set first element of the new row to 1
if i ==0:
newRow[i] = 1
# print(f"1:: newRow = {newRow}")
# if the element is in the middle somewhere, add the surroundng two elements in
# previous row to get the new element
# e.g. row 3[2] = row2[1] + row2[2]
elif i <= l-1:
# print(f"2:: newRow = {newRow}")
newRow[i] = row[i-1] + row[i]
else:
# print(f"3 :: newRow = {newRow}")
newRow[i] = 1
i+=1
# print(newRow)
return newRow
def printRow(mx : int, row:list):
n = len(row)
spaces = ' ' *((mx - n)*2)
print(spaces,end=' ')
for i in row:
print(str(i) + ' ',end = ' ')
print(' ')
r = [1,1]
mx = 7
printRow(mx,[1])
printRow(mx,r)
for a in range(1,mx-1):
# print(f"working for Row = {a}")
if len(r) <= 2:
a1 = genRow(r)
r=a1
else:
a2 = genRow(a1)
a1 = a2
printRow(mx,a1)
Hopefully it helps.

Categories

Resources