0

I am trying to sort a list of tuples. They're in this format:

("First Last", 3, 0)

Or in other words:

(string, int, int)

I want to sort by the string value (first tuple element). I found how to sort a list of tuples by a certain element from this awesome answer: https://stackoverflow.com/a/3121985/8887398

This was my code:

# Yes, I do want to start from element 1 btw
myList[1:].sort(key=lambda tup: tup[0])

This worked great when I had only first names as the values of the strings in my tuples, such as:

("George", 8, 3)

Then I added last names, such as:

("George Manning", 8, 3)

It no longer sorted correctly, so I tried this:

myList[1:].sort(key=lambda tup: (tup[0].split(" ")[1]))

I was so confident this would work. It doesn't. I'm confused as I know my split method is correctly pulling the last name from debugging. What am I doing wrong? How can I sort my list by last name?

Here's an example. Yes they're fake names:

myList = [
    ("NAME", "SOME LABEL 1", "SOME LABEL 2"),
    ("Kevin Lee", 45, 4),
    ("John Bowes", 35, 2),
    ("George Smith", 8, 3),
    ("Gina Marnico", 40, 3),
    ("Alice Gordon", 48, 7),
    ("Lee Jackson", 49, 7),
    ("Adam Hao", 50, 4),
    ("Adrian Benco", 23, 2),
    ("Jessica Farner", 43, 20),
    ("Greg Hyde", 34, 20),
    ("Ryan Valins", 39, 7),
    ("Gary Funa", 49, 7),
    ("Sam Tuno", 15, 4),
    ("Katy Sendej", 30, 2),
    ("Jessica Randolf", 44, 8),
    ("Gina Gundo", 47, 30)
]

myList[1:].sort(key=lambda tup: (tup[0].split(" ")[1]))

I skip the first value because it's labeling information. I want that element to stay the same, and the rest of the list to be sorted by last name.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Birdman
  • 1,404
  • 5
  • 22
  • 49
  • It's possibly due to the fact that your last code line is putting the key into a tuple? Shouldn't it be: `myList[1:].sort(key=lambda tup: tup[0].split(" ")[1])` – Jab Dec 08 '18 at 02:58
  • @Jaba Nope. I tried without the redundant parenthesis. Good thought. But same results though. – Birdman Dec 08 '18 at 03:00
  • `myList[1:].sort(key=lambda tup: (tup[0].split(" ")[1]))` works for me on Python 3. What's a big example of your `myList`? – jcmack Dec 08 '18 at 03:01
  • Also do you want to be always skipping the first element of `myList`? – jcmack Dec 08 '18 at 03:03
  • Also is there a reason you're omitting the first value in `myList`? *(cough... py_case > camelCase == True)* And you're also only sorting by the last name with `..split(.)[1]` shouldn't that be 0? – Jab Dec 08 '18 at 03:03
  • @jcmack I've added an example to the question. Yes I want to skip first element. – Birdman Dec 08 '18 at 03:13
  • @Jaba Well the split should split at the space and put the results into a list. In this case ["First", "Last"]. So using [1] will choose the last name from that list. Also I'm just in habit of camel case. I'm really not sure if something different is best practice for Python or PP8 or something. – Birdman Dec 08 '18 at 03:13
  • It's a standard for python development, but python isn't too picky. Also, I wasn't sure if you wanted it to sort by first or last – Jab Dec 08 '18 at 03:21
  • This works for me: `[myList[0]] + sorted(myList[1:], key=lambda t: t[0].split(' ')[1])`. Not sure how married you are to doing the sort in place. – jcmack Dec 08 '18 at 03:22
  • `myList[1:].sort(key=lambda tup: tup[0])` creates and immediately discards a temporary object. Your actual list is not being sorted. `myList[1:]` would be a view to a numpy array, but not a python list. – Mad Physicist Dec 08 '18 at 03:32

4 Answers4

2

This sould work if you want to sort by the last name:

a = myList[1:]
a.sort(key=lambda tup: tup[0].split(" ")[1])
myList[1:] = a

Result:

[
    ('NAME', 'SOME LABEL 1', 'SOME LABEL 2'),
    ('Adrian Benco', 23, 2),
    ('John Bowes', 35, 2),
    ('Jessica Farner', 43, 20),
    ('Gary Funa', 49, 7),
    ('Alice Gordon', 48, 7),
    ('Gina Gundo', 47, 30),
    ('Adam Hao', 50, 4),
    ('Greg Hyde', 34, 20),
    ('Lee Jackson', 49, 7),
    ('Kevin Lee', 45, 4),
    ('Gina Marnico', 40, 3),
    ('Jessica Randolf', 44, 8),
    ('Katy Sendej', 30, 2),
    ('George Smith', 8, 3),
    ('Sam Tuno', 15, 4),
    ('Ryan Valins', 39, 7)
]

If you want to sort by last name and then first you can do this:

a.sort(key=lambda tup: list(reversed(tup[0].split(" "))))
  • 1
    Great answer. Sorting by last name then first name is a plus! It's going to take me a little bit to decipher the last line though. Are you able to explain? From what I can tell, we create a list from .split, then we reverse that list, to get a list of ["Last, "First]. Then we basically create a new list of all of the reversed lists. So if we have 15 elements we have a list of 15 lists. Then we sort that list? Then the sorted elements somehow correspond back to the original elements from list 'a'? – Birdman Dec 08 '18 at 04:37
  • That's almost right. `reversed(tup[0].split(" "))` creates an iterator object which you can't directly use as a key to sort, so I create a list from this iterator, thus the `list()` part, and the result is a list of `["Last, "First]`. – Edgar Ramírez Mondragón Dec 08 '18 at 06:05
0

Remove the labeling line and it works :

    myList.sort(key=lambda tup: (tup[0].split(" ")[1]))

Result :

    ('Adrian Benco', 23, 2) 
    ('John Bowes', 35, 2) 
    ('Jessica Farner', 43, 20) 
    ('Gary Funa', 49, 7) 
    ('Alice Gordon', 48, 7) 
    ('Gina Gundo', 47, 30) 
    ('Adam Hao', 50, 4)
    ('Greg Hyde', 34, 20) 
    ('Lee Jackson', 49, 7) 
    ('Kevin Lee', 45, 4)
    ('Gina Marnico', 40, 3)
    ('Jessica Randolf', 44, 8) 
    ('Katy Sendej', 30, 2) 
    ('George Smith', 8, 3) 
    ('Sam Tuno', 15, 4) 
    ('Ryan Valins', 39, 7)
avr_dude
  • 242
  • 2
  • 9
0

[myList[0]] + sorted(myList[1:], key=lambda t: t[0].split(' ')[1])

You can also chose to not sort in place and hold the label line constant.

jcmack
  • 239
  • 1
  • 11
0

The expression myList[1:] creates a separate list object, with a buffer that is distinct from that of myList. You are sorting that object in-place successfully, but the result is discarded without affecting the original myList.

You have a couple of options. The most straightforward is to just retain the sorted object and either re-insert it or just tack on the first element:

data = myList[1:]
data.sort(key=lambda x: x[0].split()[::-1])
myList[1:] = data

Or

...
myList = [myList[0]] + data

Or

...
myList = myList[:1] + data

Using sorted, you can make the code a tad more concise, since it has a return value:

myList[1:] = sorted(myList[1:], key=lambda x: x[0].split()[::-1]))

Or

myList = [myList[0]] + sorted(myList[1:], key=lambda x: x[0].split()[::-1]))

Or

myList = myList[:1] + sorted(myList[1:], key=lambda x: x[0].split()[::-1]))

You could even use the wrap-sort-unwrap pattern here. The wrapper would be a flag indicating whether or not an element is a header, allowing you to sort the whole list at once, keeping the header where it is. I do not recommend this approach here because it is overkill and less legible than the alternatives. However, you may find the pattern to be useful elsewhere:

myList = [x[1] for x in sorted(enumerate(myList), key=lambda x: (bool(x[0]), x[1][0].split()[::-1]))]

All of these problems would go away if you changed the design of the program to keep homogeneous data in your list. Let's say you get your list from a CSV file. You can always do the following:

myHeader, *myList = myList
myList.sort(...)

The first line is an easy syntactic sugar for stripping off the first element and repackaging the rest. It's essentially equivalent to

myHeader, myList = myList[0], myList[1:]

In all cases, I would recommend using .split()[::-1] or at least .split()[-1] in the key rather than .split(' ')[1]. The first option will allow you to sort by first name if the last name matches. It relies on the lexicographixal comparison of sequences. The second option will use the last element of the name as the sort key, making it robust against middle names and single names.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264