Pascal's Triangle Generator in Python
Clash Royale CLAN TAG#URR8PPP
$begingroup$
So I've been working on a generator for Pascal's triangle in Python. Now, I wouldn't call myself a Python god, so my program is a lot longer than the very confusing ones on the internet. It's more of a logical approach to creating the triangle.
Here's the program:
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
def pascal_next(lst):
return list(chunk_adder(double_chunker(lst)))
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
A simple go-through of how it works:
double_chunker()
splits up a row of Pascal's triangle into the pairs of numbers you would use when adding up to determine the numbers in the next row. This algorithm is little jerry-rigged - I had to add some special exceptions for some numbers on the end of the row to make it work properly.chunk_adder()
adds together a list of chunks generated bydouble_chunker
to determine the next row in the Pascal sequence.pascal_next()
combines bothdouble_chunker()
andchunk_adder()
to, when given one row in Pascal's triangle, determine the next row in the triangle.pascal_triangle()
iteratively creates rows of Pascal's triangle usingpascal_next()
.
So, here are some of my questions:
Is there anything in my program that seems redundant, repetitive, or can be shortened?
Is there any better code practices I should be employing and am not?
And obviously, as always, feel free to provide any other feedback you may have. Thanks in advance!
python
$endgroup$
add a comment |
$begingroup$
So I've been working on a generator for Pascal's triangle in Python. Now, I wouldn't call myself a Python god, so my program is a lot longer than the very confusing ones on the internet. It's more of a logical approach to creating the triangle.
Here's the program:
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
def pascal_next(lst):
return list(chunk_adder(double_chunker(lst)))
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
A simple go-through of how it works:
double_chunker()
splits up a row of Pascal's triangle into the pairs of numbers you would use when adding up to determine the numbers in the next row. This algorithm is little jerry-rigged - I had to add some special exceptions for some numbers on the end of the row to make it work properly.chunk_adder()
adds together a list of chunks generated bydouble_chunker
to determine the next row in the Pascal sequence.pascal_next()
combines bothdouble_chunker()
andchunk_adder()
to, when given one row in Pascal's triangle, determine the next row in the triangle.pascal_triangle()
iteratively creates rows of Pascal's triangle usingpascal_next()
.
So, here are some of my questions:
Is there anything in my program that seems redundant, repetitive, or can be shortened?
Is there any better code practices I should be employing and am not?
And obviously, as always, feel free to provide any other feedback you may have. Thanks in advance!
python
$endgroup$
add a comment |
$begingroup$
So I've been working on a generator for Pascal's triangle in Python. Now, I wouldn't call myself a Python god, so my program is a lot longer than the very confusing ones on the internet. It's more of a logical approach to creating the triangle.
Here's the program:
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
def pascal_next(lst):
return list(chunk_adder(double_chunker(lst)))
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
A simple go-through of how it works:
double_chunker()
splits up a row of Pascal's triangle into the pairs of numbers you would use when adding up to determine the numbers in the next row. This algorithm is little jerry-rigged - I had to add some special exceptions for some numbers on the end of the row to make it work properly.chunk_adder()
adds together a list of chunks generated bydouble_chunker
to determine the next row in the Pascal sequence.pascal_next()
combines bothdouble_chunker()
andchunk_adder()
to, when given one row in Pascal's triangle, determine the next row in the triangle.pascal_triangle()
iteratively creates rows of Pascal's triangle usingpascal_next()
.
So, here are some of my questions:
Is there anything in my program that seems redundant, repetitive, or can be shortened?
Is there any better code practices I should be employing and am not?
And obviously, as always, feel free to provide any other feedback you may have. Thanks in advance!
python
$endgroup$
So I've been working on a generator for Pascal's triangle in Python. Now, I wouldn't call myself a Python god, so my program is a lot longer than the very confusing ones on the internet. It's more of a logical approach to creating the triangle.
Here's the program:
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
def pascal_next(lst):
return list(chunk_adder(double_chunker(lst)))
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
A simple go-through of how it works:
double_chunker()
splits up a row of Pascal's triangle into the pairs of numbers you would use when adding up to determine the numbers in the next row. This algorithm is little jerry-rigged - I had to add some special exceptions for some numbers on the end of the row to make it work properly.chunk_adder()
adds together a list of chunks generated bydouble_chunker
to determine the next row in the Pascal sequence.pascal_next()
combines bothdouble_chunker()
andchunk_adder()
to, when given one row in Pascal's triangle, determine the next row in the triangle.pascal_triangle()
iteratively creates rows of Pascal's triangle usingpascal_next()
.
So, here are some of my questions:
Is there anything in my program that seems redundant, repetitive, or can be shortened?
Is there any better code practices I should be employing and am not?
And obviously, as always, feel free to provide any other feedback you may have. Thanks in advance!
python
python
asked Jan 17 at 11:24
connectyourchargerconnectyourcharger
1586
1586
add a comment |
add a comment |
4 Answers
4
active
oldest
votes
$begingroup$
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
sum
can happilly consume iterable of size 1, it can even consume iterable of size 0:
>>> sum([1])
1
>>> sum()
0
So you can simplify it to:
def chunck_adder(iterable):
for element in iterable:
yield sum(element)
Which is simply
def chunck_adder(iterable):
yield from map(sum, iterable)
So you could simplify pascal_next
instead:
def pascal_next(lst):
return list(map(sum, double_chunker(lst)))
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
The intent is pretty much the same than the pairwise
recipe from itertools
. Except you want to yield the first and last element as well.
Here you have two possibilities:
either yield them manually:
import itertools
def double_chunker(lst):
if not lst:
return
a, b = itertools.tee(lst)
next(b, None)
yield [lst[0]]
yield from zip(a, b)
yield [lst[-1]]But this forces the argument to be a list, or at least to know if its empty and to implement
__getitem__
.or add boundary values to your input so
pairwise
can work properly:import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)Which I recommend because it happily consume any iterable.
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
Instead of relying on the list being constructed, I would explicitly store the current row. I would also turn this into an infinite generator because it really is and maybe provide an helper function for convenience:
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
Full code:
import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)
def pascal_next(iterable):
return list(map(sum, double_chuncker(iterable)))
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
if __name__ == '__main__':
# Testing
for row in pascal_triangle():
print(row, end='')
if (input()):
break
$endgroup$
add a comment |
$begingroup$
Names
I am not fully convinced by the different function names but I have nothing better to suggest for the time being.
Style
Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.
Simplify double_chunker
In double_chunker
, the following condition is never true:
elif i == leng:
yield [lst[-1]]
Also, you don't need to handle explicitly the case:
elif i == 1:
yield [lst[0], lst[1]]
as it is just a particular case for [lst[i-1], lst[i]]
with i == 1
.
Simplify chunk_adder
In chunk_adder
, instead of:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
We can write:
yield sum(i)
Then, we could rewrite the function using generator expressions:
def chunk_adder(lst):
return (sum(i) for i in lst)
Then, it looks like the function is not really needed. We could write:
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
At this stage, we have:
def double_chunker(lst):
for i in range(len(lst)):
if i == 0:
yield [lst[0]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
print(pascal_triangle(8))
More simplification in double_chunker
We could handle the case i == 0
before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).
def double_chunker(lst):
yield [lst[0]]
for i in range(1, len(lst)):
yield [lst[i-1], lst[i]]
yield [lst[-1]]
Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.
$endgroup$
add a comment |
$begingroup$
Is there any better code practices I should be employing and am not?
- The first thing that caught my attention is the missing tests
You should implement a few test cases to ensure that after changes the program does still work as intended
Both the unittest module or doctest are good Python modules for testing, I have used the unittest
as an example
class PascalTriangleTest(unittest.TestCase):
def test_triangle_0(self):
self.assertEqual(
pascal_triangle(0),
[[1]]
)
def test_triangle_1(self):
self.assertEqual(
pascal_triangle(1),
[[1], [1, 1]]
)
def test_triangle_2(self):
self.assertEqual(
pascal_triangle(2),
[[1], [1, 1], [1, 2, 1]]
)
def test_triangle_3(self):
self.assertEqual(
pascal_triangle(3),
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
)
if __name__ == '__main__':
unittest.main()
- The second one would be the missing docstrings
The comments below your code would be a good start to make the docstring for each function.
See PEP257, for docstring conventions
$endgroup$
add a comment |
$begingroup$
Is there anything in my program that seems redundant, repetitive, or can be shortened?
The 22 lines of double_chunker
, chunk_adder
, and pascal_next
can be shortened to
def pascal_next(lst):
return [left + right for (left, right) in zip(lst + [0], [0] + lst)]
$endgroup$
1
$begingroup$
Orreturn [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built insum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
You could also omit the parenthesis:[left + right for left, right in zip(lst + [0], [0] + lst)]
.
$endgroup$
– Graipher
Jan 17 at 15:30
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211677%2fpascals-triangle-generator-in-python%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
$begingroup$
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
sum
can happilly consume iterable of size 1, it can even consume iterable of size 0:
>>> sum([1])
1
>>> sum()
0
So you can simplify it to:
def chunck_adder(iterable):
for element in iterable:
yield sum(element)
Which is simply
def chunck_adder(iterable):
yield from map(sum, iterable)
So you could simplify pascal_next
instead:
def pascal_next(lst):
return list(map(sum, double_chunker(lst)))
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
The intent is pretty much the same than the pairwise
recipe from itertools
. Except you want to yield the first and last element as well.
Here you have two possibilities:
either yield them manually:
import itertools
def double_chunker(lst):
if not lst:
return
a, b = itertools.tee(lst)
next(b, None)
yield [lst[0]]
yield from zip(a, b)
yield [lst[-1]]But this forces the argument to be a list, or at least to know if its empty and to implement
__getitem__
.or add boundary values to your input so
pairwise
can work properly:import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)Which I recommend because it happily consume any iterable.
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
Instead of relying on the list being constructed, I would explicitly store the current row. I would also turn this into an infinite generator because it really is and maybe provide an helper function for convenience:
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
Full code:
import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)
def pascal_next(iterable):
return list(map(sum, double_chuncker(iterable)))
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
if __name__ == '__main__':
# Testing
for row in pascal_triangle():
print(row, end='')
if (input()):
break
$endgroup$
add a comment |
$begingroup$
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
sum
can happilly consume iterable of size 1, it can even consume iterable of size 0:
>>> sum([1])
1
>>> sum()
0
So you can simplify it to:
def chunck_adder(iterable):
for element in iterable:
yield sum(element)
Which is simply
def chunck_adder(iterable):
yield from map(sum, iterable)
So you could simplify pascal_next
instead:
def pascal_next(lst):
return list(map(sum, double_chunker(lst)))
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
The intent is pretty much the same than the pairwise
recipe from itertools
. Except you want to yield the first and last element as well.
Here you have two possibilities:
either yield them manually:
import itertools
def double_chunker(lst):
if not lst:
return
a, b = itertools.tee(lst)
next(b, None)
yield [lst[0]]
yield from zip(a, b)
yield [lst[-1]]But this forces the argument to be a list, or at least to know if its empty and to implement
__getitem__
.or add boundary values to your input so
pairwise
can work properly:import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)Which I recommend because it happily consume any iterable.
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
Instead of relying on the list being constructed, I would explicitly store the current row. I would also turn this into an infinite generator because it really is and maybe provide an helper function for convenience:
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
Full code:
import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)
def pascal_next(iterable):
return list(map(sum, double_chuncker(iterable)))
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
if __name__ == '__main__':
# Testing
for row in pascal_triangle():
print(row, end='')
if (input()):
break
$endgroup$
add a comment |
$begingroup$
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
sum
can happilly consume iterable of size 1, it can even consume iterable of size 0:
>>> sum([1])
1
>>> sum()
0
So you can simplify it to:
def chunck_adder(iterable):
for element in iterable:
yield sum(element)
Which is simply
def chunck_adder(iterable):
yield from map(sum, iterable)
So you could simplify pascal_next
instead:
def pascal_next(lst):
return list(map(sum, double_chunker(lst)))
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
The intent is pretty much the same than the pairwise
recipe from itertools
. Except you want to yield the first and last element as well.
Here you have two possibilities:
either yield them manually:
import itertools
def double_chunker(lst):
if not lst:
return
a, b = itertools.tee(lst)
next(b, None)
yield [lst[0]]
yield from zip(a, b)
yield [lst[-1]]But this forces the argument to be a list, or at least to know if its empty and to implement
__getitem__
.or add boundary values to your input so
pairwise
can work properly:import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)Which I recommend because it happily consume any iterable.
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
Instead of relying on the list being constructed, I would explicitly store the current row. I would also turn this into an infinite generator because it really is and maybe provide an helper function for convenience:
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
Full code:
import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)
def pascal_next(iterable):
return list(map(sum, double_chuncker(iterable)))
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
if __name__ == '__main__':
# Testing
for row in pascal_triangle():
print(row, end='')
if (input()):
break
$endgroup$
def chunk_adder(lst):
for i in lst:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
sum
can happilly consume iterable of size 1, it can even consume iterable of size 0:
>>> sum([1])
1
>>> sum()
0
So you can simplify it to:
def chunck_adder(iterable):
for element in iterable:
yield sum(element)
Which is simply
def chunck_adder(iterable):
yield from map(sum, iterable)
So you could simplify pascal_next
instead:
def pascal_next(lst):
return list(map(sum, double_chunker(lst)))
def double_chunker(lst):
leng = len(lst)
for i in range(leng):
if i == 0:
yield [lst[0]]
elif i == 1:
yield [lst[0], lst[1]]
elif i == leng:
yield [lst[-1]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
The intent is pretty much the same than the pairwise
recipe from itertools
. Except you want to yield the first and last element as well.
Here you have two possibilities:
either yield them manually:
import itertools
def double_chunker(lst):
if not lst:
return
a, b = itertools.tee(lst)
next(b, None)
yield [lst[0]]
yield from zip(a, b)
yield [lst[-1]]But this forces the argument to be a list, or at least to know if its empty and to implement
__getitem__
.or add boundary values to your input so
pairwise
can work properly:import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)Which I recommend because it happily consume any iterable.
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
Instead of relying on the list being constructed, I would explicitly store the current row. I would also turn this into an infinite generator because it really is and maybe provide an helper function for convenience:
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
Full code:
import itertools
def pairwise(iterable):
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def double_chuncker(iterable):
extended = itertools.chain([0], iterable, [0])
return pairwise(extended)
def pascal_next(iterable):
return list(map(sum, double_chuncker(iterable)))
def pascal_triangle():
row = [1]
while True:
yield row
row = pascal_next(row)
def pascal_triangle_up_to(n):
return list(itertools.islice(pascal_triangle(), n))
if __name__ == '__main__':
# Testing
for row in pascal_triangle():
print(row, end='')
if (input()):
break
edited Jan 17 at 13:29
answered Jan 17 at 13:23
Mathias EttingerMathias Ettinger
24.3k33184
24.3k33184
add a comment |
add a comment |
$begingroup$
Names
I am not fully convinced by the different function names but I have nothing better to suggest for the time being.
Style
Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.
Simplify double_chunker
In double_chunker
, the following condition is never true:
elif i == leng:
yield [lst[-1]]
Also, you don't need to handle explicitly the case:
elif i == 1:
yield [lst[0], lst[1]]
as it is just a particular case for [lst[i-1], lst[i]]
with i == 1
.
Simplify chunk_adder
In chunk_adder
, instead of:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
We can write:
yield sum(i)
Then, we could rewrite the function using generator expressions:
def chunk_adder(lst):
return (sum(i) for i in lst)
Then, it looks like the function is not really needed. We could write:
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
At this stage, we have:
def double_chunker(lst):
for i in range(len(lst)):
if i == 0:
yield [lst[0]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
print(pascal_triangle(8))
More simplification in double_chunker
We could handle the case i == 0
before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).
def double_chunker(lst):
yield [lst[0]]
for i in range(1, len(lst)):
yield [lst[i-1], lst[i]]
yield [lst[-1]]
Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.
$endgroup$
add a comment |
$begingroup$
Names
I am not fully convinced by the different function names but I have nothing better to suggest for the time being.
Style
Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.
Simplify double_chunker
In double_chunker
, the following condition is never true:
elif i == leng:
yield [lst[-1]]
Also, you don't need to handle explicitly the case:
elif i == 1:
yield [lst[0], lst[1]]
as it is just a particular case for [lst[i-1], lst[i]]
with i == 1
.
Simplify chunk_adder
In chunk_adder
, instead of:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
We can write:
yield sum(i)
Then, we could rewrite the function using generator expressions:
def chunk_adder(lst):
return (sum(i) for i in lst)
Then, it looks like the function is not really needed. We could write:
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
At this stage, we have:
def double_chunker(lst):
for i in range(len(lst)):
if i == 0:
yield [lst[0]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
print(pascal_triangle(8))
More simplification in double_chunker
We could handle the case i == 0
before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).
def double_chunker(lst):
yield [lst[0]]
for i in range(1, len(lst)):
yield [lst[i-1], lst[i]]
yield [lst[-1]]
Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.
$endgroup$
add a comment |
$begingroup$
Names
I am not fully convinced by the different function names but I have nothing better to suggest for the time being.
Style
Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.
Simplify double_chunker
In double_chunker
, the following condition is never true:
elif i == leng:
yield [lst[-1]]
Also, you don't need to handle explicitly the case:
elif i == 1:
yield [lst[0], lst[1]]
as it is just a particular case for [lst[i-1], lst[i]]
with i == 1
.
Simplify chunk_adder
In chunk_adder
, instead of:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
We can write:
yield sum(i)
Then, we could rewrite the function using generator expressions:
def chunk_adder(lst):
return (sum(i) for i in lst)
Then, it looks like the function is not really needed. We could write:
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
At this stage, we have:
def double_chunker(lst):
for i in range(len(lst)):
if i == 0:
yield [lst[0]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
print(pascal_triangle(8))
More simplification in double_chunker
We could handle the case i == 0
before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).
def double_chunker(lst):
yield [lst[0]]
for i in range(1, len(lst)):
yield [lst[i-1], lst[i]]
yield [lst[-1]]
Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.
$endgroup$
Names
I am not fully convinced by the different function names but I have nothing better to suggest for the time being.
Style
Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.
Simplify double_chunker
In double_chunker
, the following condition is never true:
elif i == leng:
yield [lst[-1]]
Also, you don't need to handle explicitly the case:
elif i == 1:
yield [lst[0], lst[1]]
as it is just a particular case for [lst[i-1], lst[i]]
with i == 1
.
Simplify chunk_adder
In chunk_adder
, instead of:
if len(i) == 1:
yield i[0]
else:
yield sum(i)
We can write:
yield sum(i)
Then, we could rewrite the function using generator expressions:
def chunk_adder(lst):
return (sum(i) for i in lst)
Then, it looks like the function is not really needed. We could write:
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
At this stage, we have:
def double_chunker(lst):
for i in range(len(lst)):
if i == 0:
yield [lst[0]]
else:
yield [lst[i-1], lst[i]]
yield [lst[-1]]
def pascal_next(lst):
return [sum(i) for i in double_chunker(lst)]
def pascal_triangle(rows):
end = [[1]]
for i in range(rows):
end.append(pascal_next(end[-1]))
return end
print(pascal_triangle(8))
More simplification in double_chunker
We could handle the case i == 0
before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).
def double_chunker(lst):
yield [lst[0]]
for i in range(1, len(lst)):
yield [lst[i-1], lst[i]]
yield [lst[-1]]
Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.
answered Jan 17 at 13:22
JosayJosay
25.7k14087
25.7k14087
add a comment |
add a comment |
$begingroup$
Is there any better code practices I should be employing and am not?
- The first thing that caught my attention is the missing tests
You should implement a few test cases to ensure that after changes the program does still work as intended
Both the unittest module or doctest are good Python modules for testing, I have used the unittest
as an example
class PascalTriangleTest(unittest.TestCase):
def test_triangle_0(self):
self.assertEqual(
pascal_triangle(0),
[[1]]
)
def test_triangle_1(self):
self.assertEqual(
pascal_triangle(1),
[[1], [1, 1]]
)
def test_triangle_2(self):
self.assertEqual(
pascal_triangle(2),
[[1], [1, 1], [1, 2, 1]]
)
def test_triangle_3(self):
self.assertEqual(
pascal_triangle(3),
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
)
if __name__ == '__main__':
unittest.main()
- The second one would be the missing docstrings
The comments below your code would be a good start to make the docstring for each function.
See PEP257, for docstring conventions
$endgroup$
add a comment |
$begingroup$
Is there any better code practices I should be employing and am not?
- The first thing that caught my attention is the missing tests
You should implement a few test cases to ensure that after changes the program does still work as intended
Both the unittest module or doctest are good Python modules for testing, I have used the unittest
as an example
class PascalTriangleTest(unittest.TestCase):
def test_triangle_0(self):
self.assertEqual(
pascal_triangle(0),
[[1]]
)
def test_triangle_1(self):
self.assertEqual(
pascal_triangle(1),
[[1], [1, 1]]
)
def test_triangle_2(self):
self.assertEqual(
pascal_triangle(2),
[[1], [1, 1], [1, 2, 1]]
)
def test_triangle_3(self):
self.assertEqual(
pascal_triangle(3),
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
)
if __name__ == '__main__':
unittest.main()
- The second one would be the missing docstrings
The comments below your code would be a good start to make the docstring for each function.
See PEP257, for docstring conventions
$endgroup$
add a comment |
$begingroup$
Is there any better code practices I should be employing and am not?
- The first thing that caught my attention is the missing tests
You should implement a few test cases to ensure that after changes the program does still work as intended
Both the unittest module or doctest are good Python modules for testing, I have used the unittest
as an example
class PascalTriangleTest(unittest.TestCase):
def test_triangle_0(self):
self.assertEqual(
pascal_triangle(0),
[[1]]
)
def test_triangle_1(self):
self.assertEqual(
pascal_triangle(1),
[[1], [1, 1]]
)
def test_triangle_2(self):
self.assertEqual(
pascal_triangle(2),
[[1], [1, 1], [1, 2, 1]]
)
def test_triangle_3(self):
self.assertEqual(
pascal_triangle(3),
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
)
if __name__ == '__main__':
unittest.main()
- The second one would be the missing docstrings
The comments below your code would be a good start to make the docstring for each function.
See PEP257, for docstring conventions
$endgroup$
Is there any better code practices I should be employing and am not?
- The first thing that caught my attention is the missing tests
You should implement a few test cases to ensure that after changes the program does still work as intended
Both the unittest module or doctest are good Python modules for testing, I have used the unittest
as an example
class PascalTriangleTest(unittest.TestCase):
def test_triangle_0(self):
self.assertEqual(
pascal_triangle(0),
[[1]]
)
def test_triangle_1(self):
self.assertEqual(
pascal_triangle(1),
[[1], [1, 1]]
)
def test_triangle_2(self):
self.assertEqual(
pascal_triangle(2),
[[1], [1, 1], [1, 2, 1]]
)
def test_triangle_3(self):
self.assertEqual(
pascal_triangle(3),
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
)
if __name__ == '__main__':
unittest.main()
- The second one would be the missing docstrings
The comments below your code would be a good start to make the docstring for each function.
See PEP257, for docstring conventions
answered Jan 17 at 14:19
LudisposedLudisposed
7,80721960
7,80721960
add a comment |
add a comment |
$begingroup$
Is there anything in my program that seems redundant, repetitive, or can be shortened?
The 22 lines of double_chunker
, chunk_adder
, and pascal_next
can be shortened to
def pascal_next(lst):
return [left + right for (left, right) in zip(lst + [0], [0] + lst)]
$endgroup$
1
$begingroup$
Orreturn [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built insum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
You could also omit the parenthesis:[left + right for left, right in zip(lst + [0], [0] + lst)]
.
$endgroup$
– Graipher
Jan 17 at 15:30
add a comment |
$begingroup$
Is there anything in my program that seems redundant, repetitive, or can be shortened?
The 22 lines of double_chunker
, chunk_adder
, and pascal_next
can be shortened to
def pascal_next(lst):
return [left + right for (left, right) in zip(lst + [0], [0] + lst)]
$endgroup$
1
$begingroup$
Orreturn [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built insum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
You could also omit the parenthesis:[left + right for left, right in zip(lst + [0], [0] + lst)]
.
$endgroup$
– Graipher
Jan 17 at 15:30
add a comment |
$begingroup$
Is there anything in my program that seems redundant, repetitive, or can be shortened?
The 22 lines of double_chunker
, chunk_adder
, and pascal_next
can be shortened to
def pascal_next(lst):
return [left + right for (left, right) in zip(lst + [0], [0] + lst)]
$endgroup$
Is there anything in my program that seems redundant, repetitive, or can be shortened?
The 22 lines of double_chunker
, chunk_adder
, and pascal_next
can be shortened to
def pascal_next(lst):
return [left + right for (left, right) in zip(lst + [0], [0] + lst)]
answered Jan 17 at 14:44
Peter TaylorPeter Taylor
16.2k2860
16.2k2860
1
$begingroup$
Orreturn [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built insum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
You could also omit the parenthesis:[left + right for left, right in zip(lst + [0], [0] + lst)]
.
$endgroup$
– Graipher
Jan 17 at 15:30
add a comment |
1
$begingroup$
Orreturn [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built insum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
You could also omit the parenthesis:[left + right for left, right in zip(lst + [0], [0] + lst)]
.
$endgroup$
– Graipher
Jan 17 at 15:30
1
1
$begingroup$
Or
return [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built in sum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
Or
return [sum(pair) for pair in zip(lst + [0], [0] + lst)]
to make use of the built in sum
$endgroup$
– Ludisposed
Jan 17 at 14:47
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
@Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
$endgroup$
– Peter Taylor
Jan 17 at 15:28
$begingroup$
You could also omit the parenthesis:
[left + right for left, right in zip(lst + [0], [0] + lst)]
.$endgroup$
– Graipher
Jan 17 at 15:30
$begingroup$
You could also omit the parenthesis:
[left + right for left, right in zip(lst + [0], [0] + lst)]
.$endgroup$
– Graipher
Jan 17 at 15:30
add a comment |
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211677%2fpascals-triangle-generator-in-python%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown