I am trying to create a function that returns a dictionary that describes a pascal triangle.
For example,
pascal(3)
would give me
{1: [1], 2: [1,1], 3: [1,2,1]}
I currently know how to create a function that returns the list of elements
in a certain row for n equal to or greater than 2
def pascal(n):
if n == 0:
return {}
elif n == 1:
return {1:[1]}
else:
row = [1] + [list(pascal(n-1))[i] + list(pascal(n-1))[i+1] for i in range(n-2)] + [1]
return row
With this function,
pascal(3)
gives me
[1,2,1]
Is it possible to change my function in such a way that
pascal(3)
returns the desired result of
{1: [1], 2: [1,1], 3: [1,2,1]}
Any help would be appreciated.
You can use zip to pair the returning list from the recursive call with the same list but at one index apart, padded with 0:
def pascal(n):
if n == 1:
return {1: [1]}
p = pascal(n - 1)
p[n] = list(map(sum, zip([0] + p[n - 1], p[n - 1] + [0])))
return p
so that:
for n in range(1, 6):
print(pascal(n))
outputs:
{1: [1]}
{1: [1], 2: [1, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1], 5: [1, 4, 6, 4, 1]}
If you are open to an iterative solution, I cooked up up the following.
from itertools import chain
def pascal(n):
pad = (0,)
result = {1: [1]}
for i in range(2, n + 1):
previous = list(chain(pad, result[i - 1], pad))
result[i] = [sum(pair) for pair in zip(previous, previous[1:])]
return result
Demo:
>>> for n in range(1, 6):
...: print(pascal(n))
...:
...:
{1: [1]}
{1: [1], 2: [1, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1], 5: [1, 4, 6, 4, 1]}
With a bit more lines, but also better memory efficiency:
from itertools import chain, tee
def pascal(n):
pad = (0,)
result = {1: [1]}
for i in range(2, n + 1):
previous = chain(pad, result[i - 1], pad)
c1, c2 = tee(previous)
next(c2)
result[i] = [sum(pair) for pair in zip(c1, c2)]
return result
Lastly, having a dict with consecutive integer keys is not very useful, you could just use a list into which you index starting at 0. Final solution:
def pascal(n):
pad = (0,)
result = [[1]]
for i in range(1, n):
previous = chain(pad, result[i - 1], pad)
c1, c2 = tee(previous)
next(c2)
result.append([sum(pair) for pair in zip(c1, c2)])
return result
Demo:
>>> for n in range(1, 6):
...: print(pascal(n))
...:
[[1]]
[[1], [1, 1]]
[[1], [1, 1], [1, 2, 1]]
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]
edit: improved efficiency by not creating two tuples per iteration, instantiating pad once is enough.
I would be careful using recursion like that - it's very inefficient. You are calling the function twice in a loop in the function body. It's important to think about how many times the function will be called to evaluate certain values of n.
It's obvious that when n = 1, the function is called once.
When n = 2, the function is called once, and then the function calls itself twice for a total of 3 calls.
For n = 3 the function is called once, and then the functions calls itself twice, and then these two calls each call the function four times... So that's 11 calls.
So the number of calls is numCalls = 1 + 2 + 2*4 + 2*4*6 + ... + 2*4*6*...*2n)
This sequence grows extremely fast... When n is 20 that's 1308293051285742128434781 Calls
Recursion isn't always evil, you just have to be careful, this solution calls itself n times:
def genPascalDict(nMax):
if nMax < 2:
return {1: [1]}
else:
pascalDict = genPascalDict(nMax - 1)
lastRow = pascalDict[nMax - 1]
pascalDict[nMax] = [1] + [lastRow[n + 1] + lastRow[nMax - n - 2] for n in range(nMax - 2)] + [1]
return pascalDict
You can make it fast while building your dict as a side effect:
_cache = {}
def pascal(n):
try:
result = _cache[n]
except KeyError:
if n == 0:
result = []
elif n == 1:
result = [1]
else:
previous = pascal(n - 1)
result = [1] + [previous[i] + previous[i + 1] for i in range(n - 2)] + [1]
_cache[n] = result
return result
pascal(500)
print(_cache)
You don't need to compute pascal(n) more than once: it's not like it changes. So remember what your final answer was by storing it in a caching dictionary, said dictionary being what you actually wanted in the first place.
This takes about .08s to build the dictionary on my laptop.
You can use a closure with recursion:
def pascal(n:int) -> dict:
def _pascal(_s, _e, _last, _base={1:[1], 2:[1, 1]}):
return _last if not _e-_s else _pascal(_s+1, _e, {**_last, **{_s:_base.get(_s, [1, *[_last[_s-1][i]+_last[_s-1][i+1] for i in range(len(_last)-1)], 1])}})
return _pascal(1, n+1, {})
print(pascal(3))
Output:
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
Related
I would like to create a random dictionary starting from the following array:
list = [1,2,3,4,5]
In particular, I would like to have as keys of the dictionary all the elements of the array and as corresponding keys, randomly pick some values from that array except the value that corresponds to the key
An example of expected output should be something like:
Randomdict = {1: [2, 4], 2: [1,3,5] 3: [2] 4: [2,3,5] 5: [1,2,3,4]}
And last but not least all keys should have at least 1 value
It can be done with the random module and comprehensions:
from random import sample, randrange
d = {i: sample([j for j in lst if i != j], randrange(1, len(lst) - 1))
for i in lst}
If you first use random.seed(0) for reproducible data, you will get:
{1: [3, 2], 2: [4, 3], 3: [2, 4], 4: [3, 1]}
{1: [3], 2: [1], 3: [4, 1], 4: [1, 3]}
{1: [3, 2], 2: [3, 4], 3: [4], 4: [2, 3]}
Something like this? Might needs some tweaks
from random import randrange, sample
q = [1, 2, 3, 4, 5]
a = {}
for i in q:
n = randrange(len(q)-1)
a[i] = sample(q, n)
print(a)
I am trying to write a function that accepts a natural number n and returns a list of lists arranged in ascending order with integers by the number in the input.
For example: factorial_list (4) → [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]
For some reason I'm missing something in the way I wrote the code and would love to get an opinion on how the solution can be completed.
Thank you so much everyone
def f_l(n):
return help([], [], n)
def help(lst, s_l, n):
if n <= 1:
s_l.append(n)
lst.append(s_l)
return lst
return lst + help(lst, s_l, n-1)
def factorial_list(num):
num_range = [i for i in range(1, num+1)]
return [num_range[:num] for num in num_range]
Above is a simple algorithm for your problem.
factorial_list(4)
output: [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]
A naive approach to your problem is
lis = []
def fun(n):
for i in range(1, n + 1):
l = []
for j in range(1, i + 1):
l.append(j)
lis.append(l)
return lis
When called as fun(4), it outputs [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]
To simplfy the answer of #RonMarcelino, I suggest you this one-line styled solution:
def factorial_list(num):
return [list(range(1, n+1)) for n in range(1, num+1)]
print(factorial_list(4))
# [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]]
Extending on my comment, if you want to do with your recursive way, you need to use
def f_l(n):
return help([], [], n)
def help(lst, s_l, n):
if n < 1:
return [1]
help(lst, s_l, n-1)
s_l.append(n)
lst.append(s_l.copy())
return lst
With f_l(4), you will get [[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]].
If the objective is to return only list of lists arranged in ascending order with integers by the number in the input the following code will help:
def fact(n):
fact_list = []
temp = n
while temp:
int_list = []
for i in range(1, temp+1):
int_list.append(i)
else:
int_list.sort()
fact_list.append(int_list)
temp -= 1
else:
fact_list.sort()
return fact_list
print fact(4) ## -> python 2.x
print(fact(4)) ## -> python 3.x
Given an array of integers, I want to determine the number of distinct groups of integers wherein the values ascend.
Given array myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
There are 4 distinct groups of integers wherein the values ascend. i.e.
[1, 2, 3, 4], [2, 2, 3], [1, 2] and [2]
Can some with experience guide me on how to achieve this in python?
Another possible answer (also assuming [1, 4] is supposed to be there instead of [2]):
In [14]: def find_ascending_groups(my_list):
...: groups = []
...: current_group = [my_list[0]]
...: for i in range(1, len(my_list)):
...: if current_group[-1] <= my_list[i]:
...: current_group.append(my_list[i])
...: else:
...: if len(current_group) > 1:
...: groups.append(current_group)
...: current_group = [my_list[i]]
...: if len(current_group) > 1:
...: groups.append(current_group)
...: print(groups)
...:
In [15]: find_ascending_groups(myList)
[[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4]]
Here is one way to do it:
group = []
list_of_groups = []
myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
for i in range(len(myList)):
if i == len(myList)-1:
if len(group) > 0:
group.append(myList[i])
list_of_groups.append(group)
else:
if myList[i] <= myList[i+1]:
group.append(myList[i])
else:
group.append(myList[i])
if len(group) > 1:
list_of_groups.append(group)
group = []
print(list_of_groups)
Result with different test cases -
myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
Output: [[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4]
myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 5]
Output: [[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4, 5]]
myList = [1, 2]
Output: [[1, 2]]
myList = [1, 2, 3, 4]
Output: [[1, 2, 3, 4]]
myList = [5, 2, 3, 4]
Output: [[2, 3, 4]]
myList = [5, 2]
Output: []
Assuming [1, 4] is supposed to be there instead of [2], you can do something like this:
n [48]: ascending_ints = []
...: t = []
...: start_idx = 0
...: while start_idx < len(myList) - 1:
...: first = True
...: for end_idx in range(start_idx + 1, len(myList), 1):
...: if myList[end_idx] >= myList[start_idx]:
...: if first:
...: t.append(myList[start_idx])
...: first = False
...: t.append(myList[end_idx])
...: else:
...: if t:
...: ascending_ints.append(t)
...: t = []
...: start_idx = end_idx
...: break
...: start_idx += 1
...:
In [49]: ascending_ints
Out[49]: [[1, 2, 3, 4], [2, 2, 3], [1, 2], [1, 4]]
This just makes the start_idx start at 0, end_idx begin at 1 and increment both. If myList[end_idx] >= myList[start_idx] then it appends it to t. If it's the first time it's happened for this iteration, it needs to also include the value at start_idx. Once the condition is no longer true, check if anything is in t, if it is, append it to ascending_ints, reset t, set start_idx = end_idx and break the for-loop.
Here is another solution that is more efficient in Python.
def find_ascending_groups(l):
groups = []
last_ascending = (l[0] <= l[1])
l.append(l[-1] - 1) # Add an additional element to make sure the last group is appended
start = 0
for i in range(1, len(l)):
is_ascending = (l[i - 1] <= l[i])
if is_ascending != last_ascending:
if is_ascending:
start = i - 1 # l[i - 1] is a local minimum, set group start
else:
groups.append(l[start:i]) # l[i - 1] is a local maximum, generate group
last_ascending = is_ascending
l.pop() # Remove added element
return groups
EDIT
I wrote a small split_when function which was recently merged into the more-itertools library. split_when takes an iterable and a function. The function takes all pairs of consecutive elements and returns whether the iterable should be splitted between those elements:
>>> myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
>>> import more_itertools
>>> list(more_itertools.split_when(myList, lambda x, y: x > y))
[[1, 2, 3, 4], [3], [2, 2, 3], [1, 2], [1, 4], [2]]
# 4>3 3>2 3>1 2>1 4>2
PREVIOUS ANSWER
For the record, you can use a fold, that is functools.reduce in Python:
>>> myList = [1, 2, 3, 4, 3, 2, 2, 3, 1, 2, 1, 4, 2]
>>> import functools
>>> functools.reduce(lambda acc, x: acc[:-1] + [acc[-1]+[x]] if acc and x >= acc[-1][-1] else acc + [[x]], myList, [])
[[1, 2, 3, 4], [3], [2, 2, 3], [1, 2], [1, 4], [2]]
Not very readable though, but if you write the lambda as a distinct function, you get a better idea of what's happening:
>>> def f(acc, x):
... if acc: # there is at least one closed group
... *prev, cur = acc # closed groups and current group
... *_, y = cur # y is the last element of the current group
... if x >= y: # if ascending
... return prev + [cur + [x]] # return with x added to the current group
...
... return acc + [[x]] # otherwise, return with x in a new group
...
>>> functools.reduce(f, myList, [])
[[1, 2, 3, 4], [3], [2, 2, 3], [1, 2], [1, 4], [2]]
I represent a Matrix by a dictionary and assuming that I have a dictionary that the keys are the row indices, how can I convert it (in-place seems tricky) to a dictionary that the keys will be the column indices?
Here is my attempt:
by_row = {}
N = 3
M = 5
for i in range(1, N):
row = []
for j in range(1, M):
row.append(j)
by_row[i] = row
print by_row
by_column = {}
for i in range(1, M):
col = []
for j in range(1, N):
col.append(by_row[j][i - 1])
by_column[i] = col
print by_column
but I am looking for something more elegant and Pythonic. Here my_dict_1 has the row indices as keys, while my_dict_2 has the column indices as keys. Output:
{1: [1, 2, 3, 4], 2: [1, 2, 3, 4]}
{1: [1, 1], 2: [2, 2], 3: [3, 3], 4: [4, 4]}
Use the enumerate() function to number your columns, and iterate over the keys in sorted order:
by_column = {}
for rownum, row in sorted(by_row.iteritems()):
for colnum, value in enumerate(row, 1):
by_column.setdefault(colnum, []).append(value)
This assumes your dictionary has all row numbers present, and all rows are of equal length.
Demo:
>>> N, M = 2, 4 # 2 rows, each 4 columns
>>> by_row = {r: range(1, M + 1) for r in range(1, N + 1)}
>>> by_row
{1: [1, 2, 3, 4], 2: [1, 2, 3, 4]}
>>> by_column = {}
>>> for rownum, row in sorted(by_row.iteritems()):
... for colnum, value in enumerate(row, 1):
... by_column.setdefault(colnum, []).append(value)
...
>>> by_column
{1: [1, 1], 2: [2, 2], 3: [3, 3], 4: [4, 4]}
Here's a super pythonic way for you:
>>> by_row = {1: [1, 2, 3, 4], 2: [1, 2, 3, 4]}
>>> by_column = {n+1:[row[n] for row in by_row.values()] for n in range(len(by_row[1]))}
>>> by_column
{1: [1, 1], 2: [2, 2], 3: [3, 3], 4: [4, 4]}
What's the most efficient (in time) way of checking if two relatively short (about 3-8 elements) lists are shifted copies of one another? And if so, determine and return the offset?
Here is the example code and output I'd like:
>>> def is_shifted_copy(list_one, list_two):
>>> # TODO
>>>
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [3, 2, 1])
None
>>> is_shifted_copy([1, 2, 3], [1])
None
>>> is_shifted_copy([1, 1, 2], [2, 1, 1])
1
Lists may have duplicate entries. If more than one offset is valid, return any offset.
here's a simple iterator version that does the job in 2n iterations (n being the length of list)
import itertools
def is_shifted_copy(list1, list2):
if len(list1) != len(list2):
return False
iterator = iter(list2)
for i, item in enumerate(itertools.chain(list1, list1)):
try:
if item == iterator.next():
continue
else:
iterator = iter(list2)
except StopIteration:
return i - len(list2)
else:
return False
print is_shifted_copy([1, 2, 3], [1, 2, 3]) #0
print is_shifted_copy([1, 2, 3], [3, 1, 2]) #2
print is_shifted_copy([1, 2, 3], [3, 2, 1]) #False
print is_shifted_copy([1, 2, 3], [2, 3, 1]) #1
print is_shifted_copy([1, 1, 2], [2, 1, 1]) #2
print is_shifted_copy([1, 2, 3], [1]) #False
print is_shifted_copy([1, 2, 1], [2, 1, 1]) #1
print is_shifted_copy([1, 1, 1], [1, 1, 1]) #0
and from your specification,
shouldn't is_shifted_copy([1, 1, 2], [2, 1, 1]) return 2?
Searching two copies of the first list allows us to avoid performing excessive concatenation:
def is_shifted_copy(l1, l2):
l1l1 = l1 * 2
n = len(l1)
return next((i for i in range(n) if l1l1[i:i + n] == l2), None)
Here is a solution based on indexes and slicing:
>>> def is_shifted_copy(l1, l2):
try:
return [l1[-i:] + l1[:-i] for i in range(len(l1))].index(l2)
except ValueError:
return None
The result is as expected:
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [2, 1, 3])
None
Below is a modified version of NPE's solution, which checks for all possible match positions. It compares favorably to thkang's (and ecatmur's) clever iterator solutions:
import itertools as IT
def is_shifted_copy_thkang(list1, list2):
N = len(list1)
if N != len(list2):
return None
elif N == 0:
return 0
next_item = iter(list2).next
for i, item in enumerate(IT.chain(list1, list1)):
try:
if item == next_item():
continue
else:
next_item = iter(list2).next
except StopIteration:
return -i % N
else:
return None
def is_shifted_copy_NPE(list1, list2):
N = len(list1)
if N != len(list2):
return None
elif N == 0:
return 0
pos = -1
first = list1[0]
while pos < N:
try:
pos = list2.index(first, pos+1)
except ValueError:
break
if (list2 + list2)[pos:pos+N] == list1:
return pos
return None
def is_shifted_copy_ecatmur(l1, l2):
l1l1 = l1 * 2
n = len(l1)
return next((-i % n for i in range(n) if l1l1[i:i + n] == l2), None)
tests = [
# ([], [], 0),
([1, 2, 3], [1, 2, 3], 0),
([1, 2, 3], [3, 1, 2], 1),
([1, 2, 3], [2, 3, 1], 2),
([1, 2, 3], [3, 2, 1], None),
([1, 2, 3], [1], None),
([1, 1, 2], [2, 1, 1], 1),
([1,2,3,1,3,2], [1,3,2,1,2,3], 3)
]
for list1, list2, answer in tests:
print(list1, list2, answer)
assert is_shifted_copy_thkang(list1, list2) == answer
assert is_shifted_copy_NPE(list1, list2) == answer
assert is_shifted_copy_ecatmur(list1, list2) == answer
In [378]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 3.5 us per loop
In [377]: %timeit is_shifted_copy_ecatmur([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 2.37 us per loop
In [379]: %timeit is_shifted_copy_NPE([1, 2, 3], [3, 1, 2])
1000000 loops, best of 3: 1.13 us per loop
Note: I changed the return value in is_shifted_copy_thkang and is_shifted_copy_ecatmur so that all three versions would pass the tests in the original post.
I benchmarked the functions with and without the change and found it does not significantly affect the performance of the functions.
For example, with return i:
In [384]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 3.38 us per loop
With return -i % N:
In [378]: %timeit is_shifted_copy_thkang([1, 2, 3], [3, 1, 2])
100000 loops, best of 3: 3.5 us per loop
Flawed solution
Unfortunately, the solution by thkang and the modified version from unutbu fail for surprisingly simple inputs.
For example, we (correctly) get an integer result for the lists [1, 2, 1] and [1, 1, 2]:
>>> is_shifted_copy([1, 2, 1], [1, 1, 2])
2
However, when swapping the arguments, we get the (incorrect) result False:
>>> is_shifted_copy([1, 1, 2], [1, 2, 1])
False
Similarly, other lists that are shifted copies aren't treated correctly:
>>> is_shifted_copy([1, 2, 2], [2, 1, 2])
False
>>> is_shifted_copy([1, 1, 2, 1], [1, 2, 1, 1])
False
>>> is_shifted_copy([1, 2, 1, 3, 3], [3, 1, 2, 1, 3])
False
To understand the source of this problem, let me reproduce the current version of thkang's solution (with a modified call to next for Python 3):
import itertools
def is_shifted_copy(list1, list2):
if len(list1) != len(list2):
return False
iterator = iter(list2)
for i, item in enumerate(itertools.chain(list1, list1)):
try:
if item == next(iterator):
continue
else:
iterator = iter(list2) # Reset iterator
except StopIteration:
return i - len(list2)
else:
return False
Now, the following happens for a call like is_shifted_copy([1, 2, 2], [2, 1, 2]):
i
item
next(iterator)
Result
0
1
2^
reset iterator
1
2
2^
continue
2
2
1
reset iterator
3
1
2^
reset iterator
4
2
2^
continue
5
2
1
reset iterator, return False
^ First element of list2.
As we can see, the repeated elements in the input lists cause the iterator itertools.chain(list1, list1) to be exhausted before we even have the chance to get to the final element of list2.
Possible fix
If we insist on using iterators (to prevent copying of potentially large input lists, for example), we'd have to ensure the iterator for the first list isn't exhausted before we can make a comparison with the elements from the second list. The only approach I can currently think of that guarantees a non-exhausted first iterator is creating a new iterator for all possible shifted versions of list1:
import itertools
def is_shifted_copy(list1, list2):
length = len(list1)
if len(list2) != length:
return
for i in range(length):
iterator1 = itertools.islice(itertools.cycle(list1), i, i + length + 1)
iterator2 = iter(list2)
for item in iterator1:
try:
if item != next(iterator2):
break
except StopIteration:
return -i % length
return None
>>> is_shifted_copy([1, 2, 3], [1, 2, 3])
0
>>> is_shifted_copy([1, 2, 3], [3, 1, 2])
1
>>> is_shifted_copy([1, 2, 3], [2, 3, 1])
2
>>> is_shifted_copy([1, 2, 3], [3, 2, 1])
None
>>> is_shifted_copy([1, 2, 3], [1])
None
>>> is_shifted_copy([1, 1, 2], [2, 1, 1])
1
>>> is_shifted_copy([1, 2, 1], [1, 1, 2])
1
>>> is_shifted_copy([1, 1, 2], [1, 2, 1])
2
>>> is_shifted_copy([1, 2, 2], [2, 1, 2])
1
>>> is_shifted_copy([1, 1, 2, 1], [1, 2, 1, 1])
3
>>> is_shifted_copy([1, 2, 1, 3, 3], [3, 1, 2, 1, 3])
1
At best, the above algorithm finishes in O(n) steps. The algorithm performs worst, namely O(n^2) in time, for input lists with mostly duplicate elements.