Converting a nested list to a nested tuple - python

please how can i loop through a nested list get a nested list of tuples out of it for instance loop through pot to get rslt
pot = [[1,2,3,4],[5,6,7,8]]
I tried
b = []
for i in pot:
for items in i:
b = zip(pot[0][0:],pot[0][1:])
but didnt get the desired output Thanks
Desired Result =
rslt = [[(1,2),(3,4)],[(5,6),(7,8)]]

Based on the grouper recipe in the itertools documentation, you might try something like this (assuming your sublists are the length you have indicated):
>>> def grouper(iterable, n):
args = [iter(iterable)] * n # creates a list of n references to the same iterator object (which is exhausted after one iteration)
return zip(*args)
Now you can test it out:
>>> pot = [[1,2,3,4],[5,6,7,8]]
>>> rslt = []
>>> for sublist in pot:
rslt.append(grouper(sublist, 2))
>>> rslt
[[(1, 2), (3, 4)], [(5, 6), (7, 8)]]

you can also use a list comprehension:
[[(a, b), (c, d)] for a, b, c, d in l]

Related

How do you merge two lists and return them as a tuple in a new list?

rules: If one list is shorter than the other, the last element of the shorter list should be repeated as often as necessary. If one or both lists are empty, the empty list should be returned.
merge([0, 1, 2], [5, 6, 7])
should return [(0, 5), (1, 6), (2, 7)]
merge([2, 1, 0], [5, 6])
should return [(2, 5), (1, 6), (0, 6)]
merge([ ], [2, 3])
should return []
this is what I've written so far
def merge(a, b):
mergelist = []
for pair in zip(a, b):
for item in pair :
mergelist.append(item )
return mergelist
print(merge([0, 1, 2], [5, 6]))
Thanks for asking the question.
I tried to amend your code as it is always easier to understand our own code.
Please find modifications
def merge(a, b):
mergelist = []
if not a or not b:
return []
elif len(a) > len(b):
occ = len(a)-len(b)
b.extend([b[len(b)-1] for i in range(occ)])
elif len(a) < len(b):
occ = len(b)-len(a)
a.extend([a[len(a)-1] for i in range(occ)])
for pair in zip(a, b):
mergelist.append(pair)
return mergelist
print(merge(l,l1))
You need to manually append each tuple in the return list as you need to check if the length of the second list accordingly. This is one way of solving this
def merge(l1, l2):
new = []
for i in range(len(l1)):
if i > len(l2)-1:
s2 = l2[len(l2)-1] # use the last element of second list if there are no more elements
else:
s2 = l2[i]
new.append(l1[i], s2)
return new
"""
>>> merge([0,1,2],[5,6,7])
[(0, 5), (1, 6), (2, 7)]
>>> merge([2,1,0],[5,6])
[(2, 5), (1, 6), (0, 6)]
>>> merge([],[2,3])
[]
"""
This is actually somewhat tricky.
You would think something simple like this would work:
def merge(a, b):
# use iterator to keep iterations state after zip
a, b = iter(a), iter(b)
rtrn = list(zip(a, b))
try:
taila, tailb = rtrn[-1]
except IndexError: # one or both empty
return rtrn
# only one of these loops will run, draining the longer input list
rtrn.extend((ai, tailb) for ai in a)
rtrn.extend((taila, bi) for bi in b)
return rtrn
Here the trick is to use an iterator, not an iterable. An iterator keeps its state. So after the zip, both iterators should still point at the place where zip stopped.
However, this does not work if b is the shorter list. Because then zip will have removed one value from a and will discard it. You have to be careful to avoid this.
The easiest way is to just materialize two lists and deal with the length differences explicitely.
def merge(a, b):
# ensure that we have lists, not anything else like iterators, sets, etc
a, b = list(a), list(b)
rtrn = list(zip(a, b))
try:
taila, tailb = rtrn[-1]
except IndexError: # one or both empty
return rtrn
rtrnlen = len(rtrn)
# only one of these loops will run, draining the longer input list
# You could also use itertools.zip_longest for this
rtrn.extend((ai, tailb) for ai in a[rtrnlen:])
rtrn.extend((taila, bi) for bi in b[rtrnlen:])
return rtrn
I'd use zip_longest:
from itertools import zip_longest
def merge(a, b):
return list(a and b and zip_longest(a, b, fillvalue=min(a, b, key=len)[-1]))
Same thing, different style:
def merge(a, b):
if a and b:
short = min(a, b, key=len)
return list(zip_longest(a, b, fillvalue=short[-1]))
return []
from itertools import zip_longest
def merge(a,b):
if len(a) > len(b):
return list((zip_longest(a,b,fillvalue=b[-1])))
else:
return list((zip_longest(a,b,fillvalue=a[-1])))`
for example
a = [2,3,5]
b = [1,2]
merge(a,b)
[(2, 1), (3, 2), (5, 2)]
Link to documentation for zip_longest
https://docs.python.org/3/library/itertools.html#itertools.zip_longest

Most pythonic way to remove tuples from a list if first element is a duplicate

The code I have so far is pretty ugly:
orig = [(1,2),(1,3),(2,3),(3,3)]
previous_elem = []
unique_tuples = []
for tuple in orig:
if tuple[0] not in previous_elem:
unique_tuples += [tuple]
previous_elem += [tuple[0]]
assert unique_tuples == [(1,2),(2,3),(3,3)]
There must be a more pythonic solution.
If you don't care which tuple round you return for duplicates, you could always convert your list to a dictionary and back:
>>> orig = [(1,2),(1,3),(2,3),(3,3)]
>>> list(dict(orig).items())
[(1, 3), (2, 3), (3, 3)]
If you want to return the first tuple round, you could reverse your list twice and use an OrderedDict, like this:
>>> from collections import OrderedDict
>>> orig = [(1,2),(1,3),(2,3),(3,3)]
>>> new = list(OrderedDict(orig[::-1]).items())[::-1]
[(1, 2), (2, 3), (3, 3)]
These are not the most efficient solutions (if that's of great importance) , but they do make for nice idiomatic one-liners.
Some benchmarking
Note the difference in speed, and that should you not care which tuple round you return, the first option is much more efficient:
>>> import timeit
>>> setup = '''
orig = [(1,2),(1,3),(2,3),(3,3)]
'''
>>> print (min(timeit.Timer('(list(dict(orig).items()))', setup=setup).repeat(7, 1000)))
0.0015771419037069459
compared to
>>>setup = '''
orig = [(1,2),(1,3),(2,3),(3,3)]
from collections import OrderedDict
'''
>>> print (min(timeit.Timer('(list(OrderedDict(orig[::-1]).items())[::-1])',
setup=setup).repeat(7, 1000)))
0.024554947372323
The first option is nearly 15 times faster according to these speed tests.
That being said however, Saksham's answer is also O(n) and smashes these dictionary methods efficiency wise:
>>> setup = '''
orig = [(1,2),(1,3),(2,3),(3,3)]
newlist = []
seen = set()
def fun():
for (a, b) in orig:
if not a in seen:
newlist.append((a, b))
seen.add(a)
return newlist
'''
>>> print (min(timeit.Timer('fun()', setup=setup).repeat(7, 1000)))
0.0004833390384996095
If you want the first containing a specific key to always be the one that appears in the final list:
list(reversed(collections.OrderedDict( reversed([(1,2),(1,3),(2,3),(3,3)])).items()))
Which results in:
[(1, 2), (2, 3), (3, 3)]
If you do not wish to store in an additional data structure, the time complexity O(n^2), as pointed out in the comments:
orig = [(1,2),(1,3),(2,3),(3,3)]
newlist = []
for (a, b) in orig:
if not any(x == a for x, y in newlist):
newlist.append((a, b))
print newlist # prints [(1, 2), (2, 3), (3, 3)]
A little book-keeping can reduce it to linear time:
orig = [(1,2),(1,3),(2,3),(3,3)]
newlist = []
seen = set()
for (a, b) in orig:
if not a in seen:
newlist.append((a, b))
seen.add(a)
print newlist # prints [(1, 2), (2, 3), (3, 3)]

Comparing items within a list with each other

If I have a list
lst = [1, 2, 3, 4, 5]
and I want to show that two items exist one of which is larger than the other by 1, can I do this without specifying which items in the list?
ie. without having to do something like:
lst[1] - lst[0] == 1
a general code that works for any int items in the lst
You can pair the numbers if the one less than the number is in the list:
new = [(i, i - 1) for i in lst if i - 1 in lst]
This one: makes set of the list for faster member checks; then short circuiting checks if i + 1 exists in that set for each i in the list (I iterate over list instead of the newly created set because it should be slightly faster). As soon as it is proven that any i + 1 also is in the list, the function exits with True return value, False otherwise.
def has_n_and_n_plus_1(lst):
lset = set(lst)
return any(i + 1 in lset for i in lst)
Testing:
>>> has_n_and_n_plus_1([6,2,7,11,42])
True
>>> has_n_and_n_plus_1([6,2,9,11,42])
False
The all tricks in 1 basket brain-teaser one:
from operator import sub
from itertools import starmap, tee
a, b = tee(sorted(lst))
next(b, None)
exists = 1 in starmap(sub, zip(b, a))
What this code does is: sort the list in increasing order; then do the pairwise iteration of a, b = lst[i], lst[i + 1], then starmaps each b, a into the sub operator resulting in b - a; and then checks with in operator if that resulting iterator contains any 1.
You could zip the list with itself shifted by one.
>>> lst = [1,2,3,4,5]
>>> zip(lst, lst[1:])
[(1, 2), (2, 3), (3, 4), (4, 5)]
This assumes that the list is ordered. If it is not, then you could sort it first and then filter it to exclude non matches (perhaps including the indexes in the original list if that is important). So if it's a more complex list of integers this should work:
>>> lst = [99,12,13,44,15,16,45,200]
>>> lst.sort()
>>> [(x,y) for (x,y) in zip(lst, lst[1:]) if x + 1 == y]
[(12, 13), (15, 16), (44, 45)]
The following is the equivalent using functions. The use of izip from itertools ensure the list is only iterated over once when we are looking for matches with the filter function:
>>> from itertools import izip
>>> lst = [99,12,13,44,15,16,45,200]
>>> lst.sort()
>>> filter(lambda (x,y): x+1==y, izip(lst, lst[1:]))
[(12, 13), (15, 16), (44, 45)]
The same could be written using for comprehensions, but personally I prefer using functions.

How to get first element in a list of tuples?

I have a list like below where the first element is the id and the other is a string:
[(1, u'abc'), (2, u'def')]
I want to create a list of ids only from this list of tuples as below:
[1,2]
I'll use this list in __in so it needs to be a list of integer values.
>>> a = [(1, u'abc'), (2, u'def')]
>>> [i[0] for i in a]
[1, 2]
Use the zip function to decouple elements:
>>> inpt = [(1, u'abc'), (2, u'def')]
>>> unzipped = zip(*inpt)
>>> print unzipped
[(1, 2), (u'abc', u'def')]
>>> print list(unzipped[0])
[1, 2]
Edit (#BradSolomon):
The above works for Python 2.x, where zip returns a list.
In Python 3.x, zip returns an iterator and the following is equivalent to the above:
>>> print(list(list(zip(*inpt))[0]))
[1, 2]
do you mean something like this?
new_list = [ seq[0] for seq in yourlist ]
What you actually have is a list of tuple objects, not a list of sets (as your original question implied). If it is actually a list of sets, then there is no first element because sets have no order.
Here I've created a flat list because generally that seems more useful than creating a list of 1 element tuples. However, you can easily create a list of 1 element tuples by just replacing seq[0] with (seq[0],).
I was thinking that it might be useful to compare the runtimes of the different approaches so I made a benchmark (using simple_benchmark library)
I) Benchmark having tuples with 2 elements
As you may expect to select the first element from tuples by index 0 shows to be the fastest solution very close to the unpacking solution by expecting exactly 2 values
import operator
import random
from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()
#b.add_function()
def rakesh_by_index(l):
return [i[0] for i in l]
#b.add_function()
def wayneSan_zip(l):
return list(list(zip(*l))[0])
#b.add_function()
def bcattle_itemgetter(l):
return list(map(operator.itemgetter(0), l))
#b.add_function()
def ssoler_upacking(l):
return [idx for idx, val in l]
#b.add_function()
def kederrack_unpacking(l):
return [f for f, *_ in l]
#b.add_arguments('Number of tuples')
def argument_provider():
for exp in range(2, 21):
size = 2**exp
yield size, [(random.choice(range(100)), random.choice(range(100))) for _ in range(size)]
r = b.run()
r.plot()
II) Benchmark having tuples with 2 or more elements
import operator
import random
from simple_benchmark import BenchmarkBuilder
b = BenchmarkBuilder()
#b.add_function()
def kederrack_unpacking(l):
return [f for f, *_ in l]
#b.add_function()
def rakesh_by_index(l):
return [i[0] for i in l]
#b.add_function()
def wayneSan_zip(l):
return list(list(zip(*l))[0])
#b.add_function()
def bcattle_itemgetter(l):
return list(map(operator.itemgetter(0), l))
#b.add_arguments('Number of tuples')
def argument_provider():
for exp in range(2, 21):
size = 2**exp
yield size, [tuple(random.choice(range(100)) for _
in range(random.choice(range(2, 100)))) for _ in range(size)]
from pylab import rcParams
rcParams['figure.figsize'] = 12, 7
r = b.run()
r.plot()
This is what operator.itemgetter is for.
>>> a = [(1, u'abc'), (2, u'def')]
>>> import operator
>>> b = map(operator.itemgetter(0), a)
>>> b
[1, 2]
The itemgetter statement returns a function that returns the element at the index that you specify. It's exactly the same as writing
>>> b = map(lambda x: x[0], a)
But I find that itemgetter is a clearer and more explicit.
This is handy for making compact sort statements. For example,
>>> c = sorted(a, key=operator.itemgetter(0), reverse=True)
>>> c
[(2, u'def'), (1, u'abc')]
You can use "tuple unpacking":
>>> my_list = [(1, 'abc'), (2, 'def')]
>>> my_ids = [idx for idx, val in my_list]
>>> my_ids
[1, 2]
At iteration time each tuple is unpacked and its values are set to the variables idx and val.
>>> x = (1, 'abc')
>>> idx, val = x
>>> idx
1
>>> val
'abc'
if the tuples are unique then this can work
>>> a = [(1, u'abc'), (2, u'def')]
>>> a
[(1, u'abc'), (2, u'def')]
>>> dict(a).keys()
[1, 2]
>>> dict(a).values()
[u'abc', u'def']
>>>
From a performance point of view, in python3.X
[i[0] for i in a] and list(zip(*a))[0] are equivalent
they are faster than list(map(operator.itemgetter(0), a))
Code
import timeit
iterations = 100000
init_time = timeit.timeit('''a = [(i, u'abc') for i in range(1000)]''', number=iterations)/iterations
print(timeit.timeit('''a = [(i, u'abc') for i in range(1000)]\nb = [i[0] for i in a]''', number=iterations)/iterations - init_time)
print(timeit.timeit('''a = [(i, u'abc') for i in range(1000)]\nb = list(zip(*a))[0]''', number=iterations)/iterations - init_time)
output
3.491014136001468e-05
3.422205176000717e-05
when I ran (as suggested above):
>>> a = [(1, u'abc'), (2, u'def')]
>>> import operator
>>> b = map(operator.itemgetter(0), a)
>>> b
instead of returning:
[1, 2]
I received this as the return:
<map at 0xb387eb8>
I found I had to use list():
>>> b = list(map(operator.itemgetter(0), a))
to successfully return a list using this suggestion. That said, I'm happy with this solution, thanks. (tested/run using Spyder, iPython console, Python v3.6)
I'd prefer zipping this way:
>>> lst = [(1, u'abc'), (2, u'def')]
>>> new, _ = zip(*lst)
>>> new
(1, 2)
>>>
Or if you don't know how many extra values there are:
>>> new, *_ = zip(*lst)
>>> new
(1, 2)
>>>
you can unpack your tuples and get only the first element using a list comprehension:
l = [(1, u'abc'), (2, u'def')]
[f for f, *_ in l]
output:
[1, 2]
this will work no matter how many elements you have in a tuple:
l = [(1, u'abc'), (2, u'def', 2, 4, 5, 6, 7)]
[f for f, *_ in l]
output:
[1, 2]
I wondered why nobody suggested to use numpy, but now after checking i understand. It is maybe not the best for mixed type arrays.
This would be a solution in numpy:
>>> import numpy as np
>>> a = np.asarray([(1, u'abc'), (2, u'def')])
>>> a[:, 0].astype(int).tolist()
[1, 2]
To get an element of a list or tuple you can iterate through a list or tuple
a = [(1, u'abc'), (2, u'def')]
list1 = [a[i][0] for i in range(len(a))]
print(list1)
Those are tuples, not sets. You can do this:
l1 = [(1, u'abc'), (2, u'def')]
l2 = [(tup[0],) for tup in l1]
l2
>>> [(1,), (2,)]
another simple suggestion if you need to convert to a nested of the tuple, and all elements inside the list the answer will be:
s=[]
for i in range(len(a)):
s.append(a[i][0])
print(s)
Output:
[(1),(2)]
If you need to convert to a nested of the list, the answer will be:
a = [(1, u'abc'), (2, u'def')]
print([list(i[0]) for i in a])
output:
[[1], [2]]
Solution using list comprehension.
og_list = [(1, u'abc'), (2, u'def')]
list_of_keys = [key for key, _ in og_list]
output
[1,2]

Find an element in a list of tuples

I have a list 'a'
a= [(1,2),(1,4),(3,5),(5,7)]
I need to find all the tuples for a particular number. say for 1 it will be
result = [(1,2),(1,4)]
How do I do that?
If you just want the first number to match you can do it like this:
[item for item in a if item[0] == 1]
If you are just searching for tuples with 1 in them:
[item for item in a if 1 in item]
There is actually a clever way to do this that is useful for any list of tuples where the size of each tuple is 2: you can convert your list into a single dictionary.
For example,
test = [("hi", 1), ("there", 2)]
test = dict(test)
print test["hi"] # prints 1
Read up on List Comprehensions
[ (x,y) for x, y in a if x == 1 ]
Also read up up generator functions and the yield statement.
def filter_value( someList, value ):
for x, y in someList:
if x == value :
yield x,y
result= list( filter_value( a, 1 ) )
[tup for tup in a if tup[0] == 1]
for item in a:
if 1 in item:
print item
The filter function can also provide an interesting solution:
result = list(filter(lambda x: x.count(1) > 0, a))
which searches the tuples in the list a for any occurrences of 1. If the search is limited to the first element, the solution can be modified into:
result = list(filter(lambda x: x[0] == 1, a))
Or takewhile, ( addition to this, example of more values is shown ):
>>> a= [(1,2),(1,4),(3,5),(5,7),(0,2)]
>>> import itertools
>>> list(itertools.takewhile(lambda x: x[0]==1,a))
[(1, 2), (1, 4)]
>>>
if unsorted, like:
>>> a= [(1,2),(3,5),(1,4),(5,7)]
>>> import itertools
>>> list(itertools.takewhile(lambda x: x[0]==1,sorted(a,key=lambda x: x[0]==1)))
[(1, 2), (1, 4)]
>>>
Using filter function:
>>> def get_values(iterables, key_to_find):
return list(filter(lambda x:key_to_find in x, iterables))
>>> a = [(1,2),(1,4),(3,5),(5,7)]
>>> get_values(a, 1)
>>> [(1, 2), (1, 4)]
>>> [i for i in a if 1 in i]
[(1, 2), (1, 4)]
if you want to search tuple for any number which is present in tuple then you can use
a= [(1,2),(1,4),(3,5),(5,7)]
i=1
result=[]
for j in a:
if i in j:
result.append(j)
print(result)
You can also use if i==j[0] or i==j[index] if you want to search a number in particular index

Categories

Resources