Dragon-Ash
Dragon-Ash

Reputation: 71

Reorder a list with one list comprehension and two conditions

As it says on the tin. Let's suppose I have a list of strings: fruits = ['blueberries', 'apricot', 'apple', 'avocado', 'banana', 'apricot','blackberries']

Let's suppose I wanted to reorder this, with fruits starting with 'a' first, then all the 'b' fruits - not in alphabetical order, but in the order they appear in the list. So apricot would the first item in the reordered list. Blueberries would come before banana, etc.

This is trivial with two list comprehensions, even just:

fruits = ['blueberries', 'apricot', 'apple', 'avocado', 'banana', 'apricot','blackberries']
    
a_fruit = [x for x in fruits if x[0] == 'a']
b_fruit = [x for x in fruits if x[0] == 'b']
    
print(a_fruit + b_fruit)

['apricot', 'apple', 'avocado', 'apricot', 'blueberries', 'banana', 'blackberries']

Is there a way to do this with -one- list comprehension? I don't think if-else works since that only goes through the list once. I assume it would have to be some sort of nested list comprehension that goes through the list twice, first adding the 'a' items then the 'b' items, but I haven't been able to figure it out, and thought I'd double-check that what I'm trying to do isn't impossible.

Even if it is possible - would it be preferable? Seems like the code would get rather hard to read...

EDIT: I should clarify that I'm looking for a general solution that could be used with any two conditions - so I'd like to avoid 'sort' since a general condition might not use strings etc.

Upvotes: 0

Views: 97

Answers (2)

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95908

I would say the most elegant way of doing this would be to just sort the list based on the first character:

>>> sorted(fruits, key=lambda fruit: fruit[0])
['apricot', 'apple', 'avocado', 'apricot', 'blueberries', 'banana', 'blackberries']

Alternatively, you can nest the comprehension like this:

>>> [f for c in 'ab' for f in fruits if f[0] == c]
['apricot', 'apple', 'avocado', 'apricot', 'blueberries', 'banana', 'blackberries']

Upvotes: 4

ShadowRanger
ShadowRanger

Reputation: 155363

The best solution here isn't a listcomp, it's sorting. Just do:

sorted_fruit = sorted(fruits, key=lambda x: x[0])

and it'll put the as before the bs while otherwise maintaining the existing order.

If you really insist on a listcomp, you could do a nested loop structure a la:

sorted_fruit = [x for prefix in 'ab' for x in fruits if x[0] == prefix]

That just loops over fruits once for each prefix, so it's doing all the work of the two listcomps in a single listcomp; it's a slight improvement over concatenating the results of two listcomps in terms of performance (since it builds the final list directly, not two temporary lists that are thrown away to build a third list), but it's uglier to my mind.

It's theoretically faster than sorting (it's two O(n) operations, rather than one O(n log n) operation) but it may be slower in practice (given sorted is a built-in implemented in C, and the sort operation is pretty simple), unless the inputs get really huge.

Upvotes: 6

Related Questions