2

Could someone please explain these lambdas? I am fairly confused by the following code and would appreciate some help understanding the code from someone who knows about this and can break the code down into understandable components.

convert = lambda text: int(text) if text.isdigit() else text
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key = alphanum_key)

(source: https://arcpy.wordpress.com/2012/05/11/sorting-alphanumeric-strings-in-python/)

I understand the first lambda.

For the second lambda alphanum_key I am confused. It seems that a key is passed in to the lambda and used in the split() function in the re module, but I do not see a key passed into the alphanum_key lambda when this lambda is called in the sorted() function.

I wrote a small program to see if I could create normal def functions out of the lambdas that are apparently using bad form by returning values and being called like functions. Here is my code:

import re

def convert2(text):
    if text.isdigit():
        return int(text)
    else:
        return text

def alphanum_key2(key):
    a_list = []
    for i in re.split('([0-9]+)', key):
        a_list.append(convert2(i))

    return a_list


if __name__ == "__main__":
    things = ["10bags", "500miles", "10000maniacs", "2000lightYearsFromHome"]
    x = sorted(things, key= alphanum_key2)
    print(x)
    #This prints
    #['10bags', '500miles', '2000lightYearsFromHome', '10000maniacs']

    convert = lambda text: int(text) if text.isdigit() else text
    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
    print(sorted(things, key = alphanum_key))
    #This prints
    #['10bags', '500miles', '2000lightYearsFromHome', '10000maniacs']

This leads to a second question... Why is the normal def functions that mimic the lambda functions returning different and incorrect results. Please note the results returned in the comments of the code if you do not feel like running this program on your machine.

user3808269
  • 1,321
  • 3
  • 21
  • 40
  • 10
    I suggest writing both functions using the `def` syntax and then see if you can figure them out. – timgeb Jan 04 '19 at 20:50
  • 1
    This might help: https://wiki.python.org/moin/HowTo/Sorting#Key_Functions – iz_ Jan 04 '19 at 20:51
  • 3
    You've basically got the understanding from your description, your only issue is that you don't understand how the lambda gets the `key` value. Sorted will take your list `l` and pass each element of the list into the key function, in this case, `alphanum_key`. You don't give it an argument, because `sorted` passes the arguments itself, you just tell it which function to use. – alkasm Jan 04 '19 at 20:53
  • 5
    Also, tangentially to what you're asking: [don't do what they did](https://docs.quantifiedcode.com/python-anti-patterns/correctness/assigning_a_lambda_to_a_variable.html). You're likely confused because the author is doing something naughty. The [original source](https://stackoverflow.com/a/2669120/3579910) is almost 10 years old, and goes [against PEP-8](https://www.python.org/dev/peps/pep-0008/#programming-recommendations). – TemporalWolf Jan 04 '19 at 20:56
  • @timgeb Thank-you man for the helpful advice. I have done this and posted the code in my initial question. However, the def versions do not return the correct results! What am I doing wrong? – user3808269 Jan 04 '19 at 21:50
  • @timgeb assigning a lambda is a really big code smell, you should *always* prefer to use `def`. You can make it a one-liner by putting the body right after the colon: `def convert(text): return int(text) if text.isdigit() else text`. – Mark Ransom Jan 04 '19 at 21:53
  • 1
    The `def` version of `alphanum_key2` is not the same because it doesn't return a list. – Mark Ransom Jan 04 '19 at 21:55
  • @MarkRansom Thank-you for the helpful feedback. Do you know of a way to change the function to return a list and get the same results as the lambda? I tried returning a list by just putting brackets around the `convert2(i)` call but that did not work. – user3808269 Jan 04 '19 at 22:04
  • @MarkRansom Never mind sir. I figured it out. Wow. :) – user3808269 Jan 04 '19 at 22:08

1 Answers1

1

You say you understand the first function convert so I won't go into that; just be aware that it returns either a string or an integer.

The straightforward conversion of the second from a lambda is:

def alphanum_key(key):
    return [convert(c) for c in re.split('([0-9]+)', key)]

Let's break that down.

[... for ...]

That's a list comprehension. It will create a list containing an element for each iteration of the for.

re.split('([0-9]+)', key)

This uses a regular expression consisting of all the digits ([0-9]) repeated one or more times (+). By putting parentheses around this expression those matches will be included in the output from split.

>>> re.split('([0-9]+)', "10bags")
['', '10', 'bags']

There's an empty string at the beginning since split splits apart the string at the matches and returns the parts both before and after the match.

The final output of alphanum_key will be ['', 10, 'bags'] since '10' will be converted to the integer 10. This is important because you want to compare numbers:

>>> '10000' < '500'
True
>>> 10000 < 500
False
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • After the re.split() method breaks apart the numbers and the convert2() function casts the numbers to integers how are these pieces put back together and sorted in the correct order? – user3808269 Jan 04 '19 at 23:37
  • Also, I noticed this does not sort correctly if I put the numbers at the end like "something2". Technically "something2" should come before the rest of the items in the list as the number 2 is smaller than all the other numbers. – user3808269 Jan 04 '19 at 23:44
  • @user3870315 the pieces *aren't* put back together. When you compare two lists, it compares them item by item until one item is unequal to the other. If they're both the same until one runs out, the shorter one is less than the longer one. I make no claims about whether the code you found is correct, you asked for an explanation and I gave you one. – Mark Ransom Jan 05 '19 at 00:07