29

How can I modify this list so that all p's appear at the beginning, the q's at the end, and the values in between are sorted alphabetically?

l = ['f','g','p','a','p','c','b','q','z','n','d','t','q']

So I would like to have:

['p','p','a','b','c','d','f','g','n','t','z','q','q']
yatu
  • 86,083
  • 12
  • 84
  • 139

8 Answers8

62

You can use sorted with the following key:

sorted(l, key = lambda s: (s!='p', s=='q', s))
['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']

Explanation

To get a better idea of how this is working, the following list comprehension aims to replicate what is being returned from the lambda function defined in the key argument prior to making comparisons:

t = [(s!='p', s=='q', s) for s in pl]

print(t)
[(True, False, 'f'),
 (True, False, 'g'),
 (False, False, 'p'),
 (True, False, 'a'),
 (False, False, 'p'),
 (True, False, 'c'),
 (True, False, 'b'),
 (True, True, 'q'),
 (True, False, 'z'),
 (True, False, 'n'),
 (True, False, 'd'),
 (True, False, 't'),
 (True, True, 'q')]

This will then be the key to be used to sort the items in the list, as mentioned in the documentation:

The value of the key parameter should be a function that takes a single argument and returns a key to use for sorting purposes.

So taking into account that False = 0 and True = 1, when this list of tuples is sorted the result will be the following:

sorted(t)
[(False, False, 'p'),
 (False, False, 'p'),
 (True, False, 'a'),
 (True, False, 'b'),
 (True, False, 'c'),
 (True, False, 'd'),
 (True, False, 'f'),
 (True, False, 'g'),
 (True, False, 'n'),
 (True, False, 't'),
 (True, False, 'z'),
 (True, True, 'q'),
 (True, True, 'q')]
yatu
  • 86,083
  • 12
  • 84
  • 139
21

One idea is to use a priority dictionary with a custom function. This is naturally extendable should you wish to include additional criteria.

L = ['f','g','p','a','p','c','b','q','z','n','d','t','q']

def sort_func(x):
    priority = {'p': 0, 'q': 2}
    return priority.get(x, 1), x

res = sorted(L, key=sort_func)

print(res)

['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']
jpp
  • 159,742
  • 34
  • 281
  • 339
15

Use the key parameter in sorted:

l = ['f','g','p','a','p','c','b','q','z','n','d','t','q']

def key(c):
    if c == 'q':
        return (2, c)
    elif c == 'p':
        return (0, c)
    return (1, c)


result = sorted(l, key=key)
print(result)

Output

['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']
Dani Mesejo
  • 61,499
  • 6
  • 49
  • 76
8

Just define an appropriate key function:

>>> def _key(x):
...     if x == 'p':
...         return -1
...     elif x == 'q':
...         return float('inf')
...     else:
...         return ord(x)
...
>>> l = ['f','g','p','a','p','c','b','q','z','n','d','t','q']
>>> sorted(l, key=_key)
['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']

Note, every character is mapped to an integer >= 0, so we can just rely on ord, and since -1 will always be less than anything returned by ord, we can use that for p, and for q, we can use infinity, so it will be alway greater than something returned by ord.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
4

You can find all p and q elements, filter the original list, and then sort:

l = ['f','g','p','a','p','c','b','q','z','n','d','t','q']
_ps, _qs = [i for i in l if i == 'p'], [i for i in l if i == 'q']
new_l = _ps+sorted(filter(lambda x:x not in {'q', 'p'}, l))+_qs

Output:

['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']
Ajax1234
  • 69,937
  • 8
  • 61
  • 102
  • *If* you're going to do it this way, you can also just count the `p`s and `q`s: `['p']*l.count('p') + sorted(filter({'q', 'p'}.isdisjoint, l)) + ['q']*l.count('q')` which is the same time complexity. **Edit**: You can get rid of the `lambda` too. – pault Dec 27 '18 at 16:26
2

You could also store you front, middle and ends in a collections.defaultdict(), then just add all three lists at the end:

from collections import defaultdict

l = ["f", "g", "p", "a", "p", "c", "b", "q", "z", "n", "d", "t", "q"]

keys = {"p": "front", "q": "end"}

d = defaultdict(list)
for item in l:
    d[keys.get(item, "middle")].append(item)

print(d["front"] + sorted(d["middle"]) + d["end"])
# ['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']
RoadRunner
  • 25,803
  • 6
  • 42
  • 75
1

Solution to this question is:

  1. First find all p and q elements in list.
  2. Filter the original list.
  3. Then, finally sort the list.

list = ['f','g','p','a','p','c','b','q','z','n','d','t','q'];
noOfPs = [i for i in l if i == 'p']; 
noOfQs = [i for i in l if i == 'q'];
resultList= noOfPs + sorted(filter(lambda x:x not in {'q', 'p'}, l))+ noOfQs 
pushkin
  • 9,575
  • 15
  • 51
  • 95
Sagar P. Ghagare
  • 542
  • 2
  • 12
  • 25
0

You can use the following lambda function as the key in sorted():

l1 = sorted(l, key=lambda x: ((x == 'q') - (x == 'p'), x))

print(l1)
# ['p', 'p', 'a', 'b', 'c', 'd', 'f', 'g', 'n', 't', 'z', 'q', 'q']

The function generates the following comparison keys:

func = lambda x: ((x == 'q') - (x == 'p'), x)

for i in l1:
    print(func(i))

Output:

(-1, 'p')
(-1, 'p')
(0, 'a')
(0, 'b')
...
(0, 't')
(0, 'z')
(1, 'q')
(1, 'q')
Mykola Zotko
  • 15,583
  • 3
  • 71
  • 73