0

I have a list of mixed tuples:

my_list = [("Adam",1985),("Karen",1990),("anna",1986)] 

How to unpack it separately into two lists or tuples, one with strings and one with integers?

Luca Cappelletti
  • 2,485
  • 20
  • 35
Karamzov
  • 343
  • 1
  • 4
  • 12
  • 2
    Possible duplicate of [What is the inverse function of zip in python?](https://stackoverflow.com/questions/13635032/what-is-the-inverse-function-of-zip-in-python) – user3483203 Jul 12 '18 at 05:28
  • I believe its half a duplicate: I believe the question should be expanded to include the possibility of tuples both `(str, int)` and `(int, str)`. – Luca Cappelletti Jul 12 '18 at 05:31

3 Answers3

2

If list schema is maintained as (str, int)

Using python's zip you can easily unpack list of tuples, without any particular regard to types:

my_list = [("Adam",1985),("Karen",1990),("anna",1986)] 
names, years = zip(*my_list)

The unpacked lists are:

('Adam', 'Karen', 'anna'), (1985, 1990, 1986)

Notes on the * operator

The * operator is called Unpacking Argument Lists, it passes the single elements of the list as argument. Suppose you have a list with 3 elements my_list = [1, 2, 3] ad a function with 3 parameters def func(a, b, c): you can use func(*my_list) to call func(my_list[0], my_list[1], ..., my_list[n]).

If list schema is shuffled

Solution using sorting, tuple by tuple

Using functools.cmp_to_key we can first sort the tuples and then execute the zip:

import functools
key=functools.cmp_to_key(lambda x1, x2: 1 if isinstance(x2, str) else -1)
names, years = zip(*[sorted(t, key=key) for t in l])

This way the list is first sorted as:

[['Adam', 1985], ['Karen', 1990], ['anna', 1986]]

And afterwards running the zip we obtain the same result:

('Adam', 'Karen', 'anna'), (1985, 1990, 1986)

Solution using groupby

from itertools import chain, groupby
names, years = [
    list(v) for _, v in groupby(
        sorted(chain.from_iterable(my_list), key=lambda x: str(x)),
        lambda x: isinstance(x, str))
]

Solutions speed comparison on example list

Running timeit on the example list we get:

Tuple sorting:

%timeit zip(*[sorted(t, key=key) for t in my_list])
>>> 5.2 µs ± 157 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Groupby

%timeit [list(v) for _, v in groupby(sorted(chain.from_iterable(my_list), key=lambda x: str(x)), lambda x: isinstance(x, str))]
>>> 6.83 µs ± 358 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Solutions speed comparison on large list

Running timeit on the a large list such as:

import random
n = 100000
my_list = []
for i in range(n):
    if random.choice([True, False]):
        my_list.append((i, "Karen"))
    else:
        my_list.append(("Karen", i))

Tuple sorting:

%timeit zip(*[sorted(t, key=key) for t in my_list])
>>> 166 ms ± 8.99 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Groupby

%timeit [list(v) for _, v in groupby(sorted(chain.from_iterable(my_list), key=lambda x: str(x)), lambda x: isinstance(x, str))]
>>> 149 ms ± 2.62 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Conclusions

Even though on small list the first method seems to be the faster one, on larger list the groupby method appears faster.

Luca Cappelletti
  • 2,485
  • 20
  • 35
  • 1
    what if `my_list = [(1, 'a'), ('b', 2)] ` ? – Chiheb Nexus Jul 12 '18 at 05:28
  • if i want to iterate over strings or integers,how to access each item. is there any indexing here – Karamzov Jul 12 '18 at 05:28
  • Sure, suppose you want `"Adam"`: you go `names[0]`. – Luca Cappelletti Jul 12 '18 at 05:29
  • @ChihebNexus That will require looping, one sec. – Luca Cappelletti Jul 12 '18 at 05:30
  • @LucaCappelletti One way to do it: `from itertools import chain, groupby; a = [list(v) for _, v in groupby(sorted(chain.from_iterable(my_list), key=lambda x: str(x)), lambda x: isinstance(x, str))]; print(a)` – Chiheb Nexus Jul 12 '18 at 05:50
  • `zip(*mylist)` whats this part * doing ? – Karamzov Jul 12 '18 at 06:38
  • It is called [Unpacking Argument Lists](https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists), it passes the single elements of the list as argument. Suppose you have a list with 3 elements `my_list = [1, 2, 3]` ad a function with 3 parameters `def func(a, b, c)`: you can go `func(*my_list)` to call `func(my_list[0], my_list[1], ..., my_list[n])`. – Luca Cappelletti Jul 12 '18 at 07:02
2

Using zip

>>> years, names = zip(*(sorted(t, key=lambda x: str(type(x))) for t in my_list))
>>> years
(1985, 1990, 1986)
>>> names
('Adam', 'Karen', 'anna')
>>> 

Or using dict

>>> d = dict(sorted(t, key=lambda x: str(type(x))) for t in my_list)
>>> years = tuple(d.keys())
>>> names = tuple(d.values())
>>> 
>>> years
(1985, 1990, 1986)
>>> names
('Adam', 'Karen', 'anna')
>>> 
Sunitha
  • 11,777
  • 2
  • 20
  • 23
  • @Karamzov `sorted(t, key=lambda x: str(type(x))) for t in my_list)` would return a list of tuples in ordered form, that is in a form where years will always come first and the names would come next. You can skip this step if your list is already in order – Sunitha Jul 12 '18 at 06:37
  • why : `key=lambda x` not `lamda x` .. ? – Karamzov Jul 12 '18 at 06:56
  • There is nothing called `lamda` in python. `lambda` is the correct terminology – Sunitha Jul 12 '18 at 06:58
  • : key=lambda x not lambda x .. ? – Karamzov Jul 12 '18 at 07:08
  • If you dont specify `key=` to sorted method, it will complain `TypeError: must use keyword argument for key function` as it expects only keyword arguments – Sunitha Jul 12 '18 at 07:15
1

you can convert it into dictionary and work as normal

dict([("Adam",1985),("Karen",1990),("anna",1986)])

{'Adam': 1985, 'Karen': 1990, 'anna': 1986}

aman5319
  • 662
  • 5
  • 16