Index out of range when using lambda [duplicate] - python

This question already has answers here:
Sort a list by the number of occurrences of the elements in the list [duplicate]
(4 answers)
Accessing the list while being sorted
(2 answers)
Closed 5 years ago.
I'm a little confused about lambda operators in Python right now. The following (working) code sorts a list of tuples after the number of occurrences of a tuples first element over the first elements of all tuples:
tuples = [(2, 1, 8, 4), (3, 4, 8, 1), (3, 8, 1, 4), (4, 1, 8, 3),
(4, 8, 1, 3), (8, 8, 3, 1), (8, 1, 3, 4), (8, 4, 1, 3),
(8, 4, 3, 1)]
temp = list(zip(*tuples))
tuples.sort(key=lambda x: temp[0].count(x[0])
,reverse=True)
print(tuples)
However, if I now try to skip the creating of "temp", i.e write this:
tuples = [(2, 1, 8, 4), (3, 4, 8, 1), (3, 8, 1, 4), (4, 1, 8, 3),
(4, 8, 1, 3), (8, 8, 3, 1), (8, 1, 3, 4), (8, 4, 1, 3),
(8, 4, 3, 1)]
tuples.sort(key=lambda x: list(zip(*tuples))[0].count(x[0])
,reverse=True)
print(tuples)
It throws an error:
Traceback (most recent call last):
File "E:\Python-Programms\Sorting", line 6, in <module>
,reverse=True)
File "E:\Python-Programms\Sorting", line 5, in <lambda>
tuples.sort(key=lambda x: list(zip(*tuples)) [0].count(x[0])
IndexError: list index out of range
Why does this error occur?

If you used a vanilla function and printed the list while it is being sorted, you'll notice the list is cleared out during the sort operation (AFAIK this applies to CPython). There isn't an index zero for an empty list:
def f(x):
print (tuples)
return ...
tuples.sort(key=f ,reverse=True)
[]
[]
[]
[]
[]
[]
[]
[]
[]
A peek into the CPython source leaves us with a useful comment that explains this behaviour:
static PyObject *
list_sort_impl(PyListObject *self, PyObject *keyfunc, int reverse)
{
...
/* The list is temporarily made empty, so that mutations performed
* by comparison functions can't affect the slice of memory we're
* sorting (allowing mutations during sorting is a core-dump
* factory, since ob_item may change).
*/
...
}
To your original problem, instead of calling list.count repeatedly, which is very inefficient, you can build a counter and then use that for sorting:
from collections import Counter
c = Counter([x[0] for x in tuples])
tuples.sort(key=lambda x: c[x[0]], reverse=True)

The list
list(zip(*tuples))
in your lambda function is not a constant one - it evaluates again and again in every sorting step - every time when your lambda function is called.
1st sorting step is OK - the lambda function is exactly what you wanted. But then raises a problem.
The tuples list is during sorting in an unstable state, maybe empty, maybe something else - the sorting algorithm has freedom in it. Its only duty is so that the sorted list will be in the right state after performing the complete sort.
2nd sorting step evaluates the value of your lambda function on the basis of this unstable list - who knows its current value?
So using sorted list itself in the key function is not a very happy decision.

Related

Merging a tuple filled my any number of tuples in one tuple and returning that sorted

First question for this Problem is: How can I call a function with a tuple of tuples, my programm should be able to handle any number of tuples.
Second question: I found a way, but with defined tuples in my code and it works, so I need a hint how to call the function with a tuple with any number of tuples in it.
My code so far:
def merge(tuples):
tuples = ((2, 3, 4), (1, 6), (5, 1, 7))
largest_tuple = len(tuples[0])
for i in tuples:
largest_tuple = max(largest_tuple, len(i))
new_tuples = []
for i in range(largest_tuple):
tup = []
for j in tuples:
if i < len(j):
tup.append(j[i])
tup.sort()
new_tuples = new_tuples+tup
new_tuple = tuple(new_tuples)
print(new_tuple)
For example:
merge((2, 3, 4), (1, 6), (5, 1, 7))
return:
(1, 2, 5, 1, 3, 6, 4, 7)
Use A Recursive Function:
merged_tuple = [] # Here is where the solution goes
def merge(tuple_set):
for value in tuple_set: # Looping over the current tuple
if type(value) == tuple: # if that value is a tuple
merge(value) # Perform the function all over again
else:
merged_tuple.append(value) # Add the number to the solution set
return tuple(sorted(merged_tuple))

Quickest way to remove mirror opposites from a list

Say I have a list of tuples [(0, 1, 2, 3), (4, 5, 6, 7), (3, 2, 1, 0)], I would like to remove all instances where a tuple is reversed e.g. removing (3, 2, 1, 0) from the above list.
My current (rudimentary) method is:
L = list(itertools.permutations(np.arange(x), 4))
for ll in L:
if ll[::-1] in L:
L.remove(ll[::-1])
Where time taken increases exponentially with increasing x. So if x is large this takes ages! How can I speed this up?
Using set comes to mind:
L = set()
for ll in itertools.permutations(np.arange(x), 4):
if ll[::-1] not in L:
L.add(ll)
or even, for slightly better performance:
L = set()
for ll in itertools.permutations(np.arange(x), 4):
if ll not in L:
L.add(ll[::-1])
The need to keep the first looks like it forces you to iterate with a contitional.
a = [(0, 1, 2, 3), (4, 5, 6, 7), (3, 2, 1, 0)]
s = set(); a1 = []
for t in a:
if t not in s:
a1.append(t)
s.add(t[::-1])
Edit: The accepted answer addresses the example code (i.e. the itertools permutations sample). This answers the generalized question for any list (or iterable).

Sorting out strands in python

I have a list of pairs of numbers with the list sorted by the number on the right- eg:
[(7, 1)
(6, 2)
(5, 3)
(8, 5)
(9, 7)
(4, 9)]
and I want to get out the strands that are linked. A strand is defined as:
x->y->z
where tuples exist:
(y, x)
(z, y)
The strands in the above example are:
1->7->9->4
2->6
3->5->8
in the above example. I cannot think of any sensible code; as simple iteration with a counting variable will cause significant repeats. Please give me some pointers.
There's an easier way to do this than a real linked list. Since there's no real need for traversal, you can simply build regular lists as you go.
ts = [(7, 1),
(6, 2),
(5, 3),
(8, 5),
(9, 7),
(4, 9)]
def get_strands(tuples):
'''builds a list of lists of connected x,y tuples
get_strands([(2,1), (3,2), (4,3)]) -> [[1,2,3,4]]
Note that this will not handle forked or merging lists intelligently
'''
lst = []
for end, start in tuples:
strand = next((strand for strand in lst if strand[-1]==start), None)
# give me the sublist that ends with `start`, or None
if strand is None:
lst.append([start, end]) # start a new strand
else:
strand.append(end)
return lst
Demo:
In [21]: get_strands(ts)
Out[21]: [[1, 7, 9, 4], [2, 6], [3, 5, 8]]
I think the most complete solution is to create a graph from your data and then perform a topological sort on it. It will provide your expected result as long as the your graph doesn't have any cycles.

Readable way to form pairs while available [duplicate]

This question already has answers here:
Iterating over every two elements in a list [duplicate]
(22 answers)
Closed 7 years ago.
I'm trying to turn a list into pairs, but only for as long as possible (i.e. my list can be odd, in that case I want to ignore the last element).
E.g. my input is x = [0, 1, 2, 3, 4], which I would want to turn into [(0, 1), (2, 3)]. Similarly, x = [0, 1, 2, 3, 4, 5] should become [(0, 1), (2, 3), (4, 5)].
What I'm currently doing is [(x[i], x[i+1]) for i in range(0, len(x), 2)]. This breaks, as range(0, len(x), 2) still includes x[-1] if len(x) is odd. Note that something of the form [(l, r) for l, r in ...] would also be preferable, rather than having to fiddle with indices.
Bonus points: Here's some more context. I'm not completely ignoring the last element of an odd sequence, of course. I'm applying a function to each pair, but I do not want to apply this function H to the singleton element. Currently, I'm doing the following:
next_layer = [H(layer[i], layer[i+1]) for i in range(0, len(layer), 2)]
if len(layer) & 1: # if there is a lone node left on this layer
next_layer.append(layer[-1])
An extra elegant solution would incorporate this into the above as well.
Use a zip
This function returns a list of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The returned list is truncated in length to the length of the shortest argument sequence.
>>> a = [1, 2, 3, 4, 5]
>>> b = [0, 1, 2, 3, 4, 5]
>>> zip(a[::2], a[1::2])
[(1, 2), (3, 4)]
>>> zip(b[::2], b[1::2])
[(0, 1), (2, 3), (4, 5)]

How flatten a list of lists one step

I have a list of lists of tuples
A= [ [(1,2,3),(4,5,6)], [(7,8,9),(8,7,6),(5,4,3)],[(2,1,0),(1,3,5)] ]
The outer list can have any number of inner lists, the inner lists can have any number of tuples, a tuple always has 3 integers.
I want to generate all combination of tuples, one from each list:
[(1,2,3),(7,8,9),(2,1,0)]
[(1,2,3),(7,8,9),(1,3,5)]
[(1,2,3),(8,7,6),(2,1,0)]
...
[(4,5,6),(5,4,3),(1,3,5)]
A simple way to do it is to use a function similar to itertools.poduct()
but it must be called like this
itertools.product([(1,2,3),(4,5,6)], [(7,8,9),(8,7,6),(5,4,3)],[(2,1,0),(1,3,5)])
i.e the outer list is removed. And I don't know how to do that. Is there a better way to generate all combinations of tuples?
itertools.product(*A)
For more details check the python tutorial
This works for your example, if there is only one level of nested lists (no lists of lists of lists):
itertools.product(*A)
you can probably call itertools.product like so:
itertools.product(*A) # where A is your list of lists of tuples
This way it expands your list's elements into arguments for the function you are calling.
Late to the party but ...
I'm new to python and come from a lisp background. This is what I came up with (check out the var names for lulz):
def flatten(lst):
if lst:
car,*cdr=lst
if isinstance(car,(list)):
if cdr: return flatten(car) + flatten(cdr)
return flatten(car)
if cdr: return [car] + flatten(cdr)
return [car]
Seems to work. Test:
A = [ [(1,2,3),(4,5,6)], [(7,8,9),(8,7,6),(5,4,3)],[(2,1,0),(1,3,5)] ]
flatten(A)
Result:
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (8, 7, 6), (5, 4, 3), (2, 1, 0), (1, 3, 5)]
Note: the line car,*cdr=lst only works in Python 3.0
This is not exactly one step, but this would do what you want if for some reason you don't want to use the itertools solution:
def crossprod(listoflists):
if len(listoflists) == 1:
return listoflists
else:
result = []
remaining_product = prod(listoflists[1:])
for outertupe in listoflists[0]:
for innercombo in remaining_product[0]:
newcombo = [outertupe]
newcombo.append(innercombo)
result.append(newcombo)
return result
def flatten(A)
answer = []
for i in A:
if type(i) == list:
ans.extend(i)
else:
ans.append(i)
return ans
This may also be achieved using list comprehension.
In [62]: A = [ [(1,2,3),(4,5,6)], [(7,8,9),(8,7,6),(5,4,3)],[(2,1,0),(1,3,5)] ]
In [63]: improved_list = [num for elem in A for num in elem]
In [64]: improved_list
Out[64]: [(1, 2, 3), (4, 5, 6), (7, 8, 9), (8, 7, 6), (5, 4, 3), (2, 1, 0), (1, 3, 5)]

Categories

Resources