Slicing nested list by index in python - python

I have a nested list with unequal length:
[[1,2,3],[4,5],[6,7,8]] and I have a start_index=(i,j) and end_index=(a,b) and I need to print all elements between start_index and end_index. For example if start_index=(1,1) and end_index=(2,2) then I will print (5,6,7,8)

You can use the following function:
def nested_index(arr, start, end):
res = arr[start[0]][start[1]:]
for i in range(start[0] + 1, end[0]):
res.extend(arr[i])
res.extend(arr[end[0]][:end[1] + 1])
return res
>>> print(nested_index([[1,2,3],[4,5],[6,7,8]], (1, 1), (2, 2)))
[5, 6, 7, 8]

Related

Python split list before and after specific value

I want to split a list into tuples after and before a specific value.
Example
Input:
list1 = [2, 1, 1, 2, 1, 2, 1, 1]
print(some_func(list1, 2))
Output:
>> [(2,1,1), (1,1,2,1), (1,2,1,1)]
so like I want every tuple to be sliced by the '2' but also keep other values in the tuple. How can I achieve this easily?
Any help is appreciated
def split_on(lst, val):
try:
# get a tuple between the start of lst and the second occurrence of val
first_idx = lst.index(val)
remainder = lst[first_idx + 1:]
second_idx = remainder.index(val) + (first_idx + 1)
# and recur with the rest of the list beyond the first occurrence
return [tuple(lst[:second_idx])] + split_on(remainder, val)
except ValueError:
# base case: there's zero or one occurrences of val,
# so we just return the whole lst as a tuple
return [tuple(lst)]
split_on([2,1,1,2,1,2,1,1], 2)
# [(2, 1, 1), (1, 1, 2, 1), (1, 2, 1, 1)]
Note that this is not a terribly efficient solution, and for very large lists will start to get pretty slow, since list slicing is kind of expensive as an operation. Something in itertools might help with a different, more efficicient approach.
You could find the indexes of the 2s and then pair each index with the one that is two over to form sub ranges:
def neighbors(aList,value):
indices = [-1] + [i for i,v in enumerate(aList) if v == value] + [len(aList)]
return [ tuple(aList[s+1:e]) for s,e in zip(indices,indices[2:]) ]
list1 = [2, 1, 1, 2, 1, 2, 1, 1]
print(neighbors(list1,2))
[(2, 1, 1), (1, 1, 2, 1), (1, 2, 1, 1)]
Note that this will return an empty list if the value is not in aList. You will have to add a condition to return the whole list instead if len(indices)<3: return [tuple(aList)] if that's what you want it to do.

How can I concatenate numbers from two lists by indices into a tuple using yield?(Python)

I need to make a "generator function" which will take 2 lists and concatenate numbres from two lists by indices into a tuple. For example:
l1 = [3, 2, 1]
l2 = [4, 3, 2]
The result of the first iteration will be
(3, 4)
The result of the second iteration will be
(2, 3)
And the third
(1, 2)
And also, one of lists may have more numbers than the other one. In that case I need to write condition "if one of the lists ended while iterating, then no further iterations are performed." (using try, except)
I know, that generator functions use yield instead of return, but I have no idea how to write this fuction...
I did this
def generate_something(l1, l2):
l3 = tuple(tuple(x) for x in zip(l1, l2))
return l3
output is
((3, 4), (2, 3), (1, 2))
It works, but this is not geterator function, there is no yield, there is no first-,second-,etc- iterations. I hope you can help me...
May be this help for you:
def generate_something_2(l1, l2):
# if one of the lists ended while iterating, then no further iterations are performed.
# For this get minimal len of list
min_i = len(l1) if len(l1) < len(l2) else len(l2)
for i in range(0,min_i):
yield (l1[i], l2[i])
l1 = [3, 2, 1, 1]
l2 = [4, 3, 2]
l3 = tuple(generate_something_2(l1, l2))
# Result: ((3, 4), (2, 3), (1, 2))
print(l3)
you can create your own generator:
def iterate_lists(l1,l2):
for i,item in enumerate(l1):
yield l1[i],l2[i]
or as a variable by generator comprehension:
iterate_lists = (li[i],l2[i] for i,item in enumerate(l1))
about if the lengthes arent equal its not clear to me what exactly you want to do, without the message you can just go on the smaller list...
just change the for i,item in enumerate(l1) with for i in range(min((len(l1),len(l2)))
Use a while loop and keep track of the index position, when a IndexError is raised you've hit the end of the shortest list and stop:
def generate_something(l1, l2):
idx = 0
while True:
try:
yield l1[idx], l2[idx]
idx += 1
except IndexError:
break
l1 = [3, 2, 1]
l2 = [4, 3, 2]
g = generate_something(l1, l2)
print(list(g))
Output:
[(3, 4), (2, 3), (1, 2)]

Arbitrary number of nested loops dependent on the previous loop in Python

I'm trying to figure out how to iterate over an arbitrary number of loops where each loop depends on the most recent outer loop. The following code is an example of what I want to do:
def function(z):
n = int(log(z))
tupes = []
for i_1 in range(1, n):
for i_2 in range(1, i_1):
...
...
...
for i_n in range(1, i_{n - 1}):
if i_1*i_2*...*i_n > z:
tupes.append((i_1, i_2,..., i_n))
return tupes
While I'd like this to work for any z > e**2, it's sufficient for it to work for zs up to e**100. I know that if I take the Cartesian product of the appropriate ranges that I'll end up with a superset of the tuples I desire, but I'd like to obtain only the tuples I seek.
If anyone can help me with this, I'd greatly appreciate it. Thanks in advance.
Combinations can be listed in ascending order; in fact, this is the default behavior of itertools.combinations.
The code:
for i1 in range(1,6):
for i2 in range(1,i1):
for i3 in range(1,i2):
print (i3, i2, i1)
# (1, 2, 3)
# (1, 2, 4)
# ...
# (3, 4, 5)
Is equivalent to the code:
from itertools import combinations
for combination in combinations(range(1,6), 3):
print combination
# (1, 2, 3)
# (1, 2, 4)
# ...
# (3, 4, 5)
Using the combinations instead of the Cartesian product culls the sample space down to what you want.
The logic in your question implemented recursively (note that this allows for duplicate tuples):
import functools
def f(n, z, max_depth, factors=(), depth=0):
res = []
if depth == max_depth:
product = functools.reduce(lambda x, y: x*y, factors, 1)
if product > z:
res.append(factors)
else:
for i in range(1, n):
new_factors = factors + (i,)
res.extend(f(i, z, factors=new_factors, depth=depth+1, max_depth=max_depth))
return res
z = np.e ** 10
n = int(np.log(z))
print(f(n, z, max_depth=8))
yields
[(8, 7, 6, 5, 4, 3, 2, 1),
(9, 7, 6, 5, 4, 3, 2, 1),
(9, 8, 6, 5, 4, 3, 2, 1),
(9, 8, 7, 5, 4, 3, 2, 1),
(9, 8, 7, 6, 4, 3, 2, 1),
(9, 8, 7, 6, 5, 3, 2, 1),
(9, 8, 7, 6, 5, 4, 2, 1),
(9, 8, 7, 6, 5, 4, 3, 1),
(9, 8, 7, 6, 5, 4, 3, 2)]
As zondo suggested, you'll need to use a function and recursion to accomplish this task. Something along the lines of the following should work:
def recurse(tuplesList, potentialTupleAsList, rangeEnd, z):
# No range to iterate over, check if tuple sum is large enough
if rangeEnd = 1 and sum(potentialTupleAsList) > z:
tuplesList.append(tuple(potentialTupeAsList))
return
for i in range(1, rangeEnd):
potentialTupleAsList.append(i)
recurse(tuplesList, potentialTupleAsList, rangeEnd - 1, z)
# Need to remove item you used to make room for new value
potentialTupleAsList.pop(-1)
Then you could call it as such to get the results:
l = []
recurse(l, [], int(log(z)), z)
print l
Your innermost loop can (if reached at all) only go over range(1, 1). Since the endpoint is not included, the loop will not iterate over any values. The shortest implementation of your function is thus:
def function(z):
return []
If you are content with tuples of length smaller than n, then I propose the following solution:
import math
def function(z):
def f(tuples, loop_variables, product, end):
if product > z:
tuples.append(loop_variables)
for i in range(end - 1, 0, -1):
f(tuples, loop_variables + (i,), product * i, i)
n = int(math.log(z))
tuples = []
f(tuples, (), 1, n)
return tuples
The time complexity is not good though: With n nested loops over O(n) elements, we are on the order of n**n steps.

Pythonic way to add a list of vectors

I am trying to create a method (sum) that takes a variable number of vectors and adds them in. For educational purposes, I have written my own Vector class, and the underlying data is stored in an instance variable named data.
My code for the #classmethod sum works (for each of the vectors passed in, loop through each element in the data variable and add it to a result list), but it seems non-Pythonic, and wondering if there is a better way?
class Vector(object):
def __init__(self, data):
self.data = data
#classmethod
def sum(cls, *args):
result = [0 for _ in range(len(args[0].data))]
for v in args:
if len(v.data) != len(result): raise
for i, element in enumerate(v.data):
result[i] += element
return cls(result)
itertools.izip_longest may come very handy in your situation:
a = [1, 2, 3, 4]
b = [1, 2, 3, 4, 5, 6]
c = [1, 2]
lists = (a, b, c)
result = [sum(el) for el in itertools.izip_longest(*lists, fillvalue=0)]
And here you got what you wanted:
>>> result
[3, 6, 6, 8, 5, 6]
What it does is simply zips up your lists together, by filling empty value with 0. e.g. izip_longest(a, b) would be [(1, 1), (2, 2), (3, 0), (4, 0)]. Then just sums up all the values in each tuple element of the intermediate list.
So here you go step by step:
>>> lists
([1, 2, 3, 4], [1, 2, 3, 4, 5, 6], [1, 2])
>>> list(itertools.izip_longest(*lists, fillvalue=0))
[(1, 1, 1), (2, 2, 2), (3, 3, 0), (4, 4, 0), (0, 5, 0), (0, 6, 0)]
So if you run a list comprehension, summing up all sub-elements, you get your result.
Another thing that you could do (and that might be more "pythonic") would be to implement the __add__ magic method, so you can use + and sum directly on vectors.
class Vector(object):
def __init__(self, data):
self.data = data
def __add__(self, other):
if isinstance(other, Vector):
return Vector([s + o for s, o in zip(self.data, other.data)])
if isinstance(other, int):
return Vector([s + other for s in self.data])
raise TypeError("can not add %s to vector" % other)
def __radd__(self, other):
return self.__add__(other)
def __repr__(self):
return "Vector(%r)" % self.data
Here, I also implemented addition of Vector and int, adding the number on each of the Vector's data elements, and the "reverse addition" __radd__, to make sum work properly.
Example:
>>> v1 = Vector([1,2,3])
>>> v2 = Vector([4,5,6])
>>> v3 = Vector([7,8,9])
>>> v1 + v2 + v3
Vector([12, 15, 18])
>>> sum([v1,v2,v3])
Vector([12, 15, 18])
args = [[1, 2, 3],
[10, 20, 30],
[7, 3, 15]]
result = [sum(data) for data in zip(*args)]
# [18, 25, 48]
Is this what you want?

how to find the max number of items in a list such that certain pairs are not together in the output?

I have a list of numbers
l = [1,2,3,4,5]
and a list of tuples which describe which items should not be in the output together.
gl_distribute = [(1, 2), (1,4), (1, 5), (2, 3), (3, 4)]
the possible lists are
[1,3]
[2,4,5]
[3,5]
and I want my algorithm to give me the second one [2,4,5]
I was thinking to do it recursively.
In the first case (t1) I call my recursive algorithm with all the items except the 1st, and in the second case (t2) I call it again removing the pairs from gl_distribute where the 1st item appears.
Here is my algorithm
def check_distribute(items, distribute):
i = sorted(items[:])
d = distribute[:]
if not i:
return []
if not d:
return i
if len(remove_from_distribute(i, d)) == len(d):
return i
first = i[0]
rest = items[1:]
distr_without_first = remove_from_distribute([first], d)
t1 = check_distribute(rest, d)
t2 = check_distribute(rest, distr_without_first)
t2.append(first)
if len(t1) >= len(t2):
return t1
else:
return t2
The remove_from_distribute(items, distr_list) removes the pairs from distr_list that include any of the items in items.
def remove_from_distribute(items, distribute_list):
new_distr = distribute_list[:]
for item in items:
for pair in distribute_list:
x, y = pair
if x == item or y == item and pair in new_distr:
new_distr.remove((x,y))
if new_distr:
return new_distr
else:
return []
My output is [4, 5, 3, 2, 1] which obviously is not correct. Can you tell me what I am doing wrong here? Or can you give me a better way to approach this?
I will suggest an alternative approach.
Assuming your list and your distribution are sorted and your list is length of n, and your distribution is length of m.
First, create a list of two tuples with all valid combinations. This should be a O(n^2) solution.
Once you have the list, it's just a simple loop through the valid combination and find the longest list. There are probably some better solutions to further reduce the complexity.
Here are my sample codes:
def get_valid():
seq = [1, 2, 3, 4, 5]
gl_dist = [(1, 2), (1,4), (1, 5), (2, 3), (3, 4)]
gl_index = 0
valid = []
for i in xrange(len(seq)):
for j in xrange(i+1, len(seq)):
if gl_index < len(gl_dist):
if (seq[i], seq[j]) != gl_dist[gl_index] :
valid.append((seq[i], seq[j]))
else:
gl_index += 1
else:
valid.append((seq[i], seq[j]))
return valid
>>>> get_valid()
[(1, 3), (2, 4), (2, 5), (3, 5), (4, 5)]
def get_list():
total = get_valid()
start = total[0][0]
result = [start]
for i, j in total:
if i == start:
result.append(j)
else:
start = i
return_result = list(result)
result = [i, j]
yield return_result
yield list(result)
raise StopIteration
>>> list(get_list())
[[1, 3], [2, 4, 5], [3, 5], [4, 5]]
I am not sure I fully understand your output as I think 4,5 and 5,2 should be possible lists as they are not in the list of tuples:
If so you could use itertools to get the combinations and filter based on the gl_distribute list using sets to see if any two numbers in the different combinations in combs contains two elements that should not be together, then get the max
combs = (combinations(l,r) for r in range(2,len(l)))
final = []
for x in combs:
final += x
res = max(filter(lambda x: not any(len(set(x).intersection(s)) == 2 for s in gl_distribute),final),key=len)
print res
(2, 4, 5)

Categories

Resources