Friday, December 22, 2006

Functional Python Sorting Using Short Circuited Lambda

Given a python list there are two inbuilt ways that the list can be sorted. One is using the lists sort() method. This will sort the list in-place. The second is the sorted() function introduced in Python 2.4. The sorted function returns a sorted list, but leaves the original list untouched. It is intended to be used functionally or in list comprehensions. So: sorted()
>>> a=[40,-1,3,100,-24,0.3,-99]
>>> from math import pi, sqrt
>>> [sqrt(val*pi) for val in sorted(a) if val>=0]
[0.97081295627784958, 3.0699801238394655, 11.209982432795858, 17.724538509055161]
notice list 'a' remains unchanged
>>> a
[40, -1, 3, 100, -24, 0.29999999999999999, -99]
sort() The sort method sorts the list in place.
>>> a.sort()
>>> a
[-99, -24, -1, 0.29999999999999999, 3, 40, 100]
cmp Both of these functions take a 'cmp' argument that passes in a comparison function. The function will be passed two of the objects for comparison. By definition it should return a value less than 0 if the first value is less than the second, a value greater than 0 if the first value is more than the second, and 0 if the values are the same. A slow but illustrative example is the following:
def our_compare(x, y):
 if x>y:
   return 1
 elif x<y:
   return -1
 else:
   return 0

and then perform our sort as

a.sort(our_compare)
or
sorted(a,our_compare)

It would be great if we could wrap this comparison in a lambda expression. But lambdas, being a functional paradigm, only allow expression evaluation. One way that is commonly referred to is to do something like the following

a.sort( lambda x,y: x-y )

The disadvantages of this method is firstly it isn't very flexible, and secondly if the result of the subtraction is outside the range allowed by the variable type, then the sort can not be relied upon. The technique that can help us here is short circuited boolean expressions.

Short Circuited Booleans

For those of you not familiar with expression short circuiting, it can help you construct conditional blocks in a single expression. It can effectively give lambdas an if/then/else construct. It is not just limited to lambdas though. It can be used in regular code as well, of course.

Have a look at the boolean 'or' table below

A B A or B
False False False
False True True
True False True
True True True

If we imagine we are the computer for a second and we are evaluating the expression 'A or B'. Notice that from the table, if A is true then 'A or B' is always true. That is, if we evaluate 'A' to be true, then there is no need to evaluate 'B' at all, because the outcome is always True. This is the essence of short circuited boolean evaluation. The step where the computer can definitely know the result is the step where it stops evaluation and returns the result.

If the value of A is False, then B needs to be evaluated and that outcome is the final evaluation. You can see that 'or' acts as if it were an 'if not' in a conditional structure. It also acts like an 'else'. More on that in a second.

Lets look at an 'and' and see how that behaves in a short circuited language.

A B A and B
False False False
False True False
True False False
True True True

Imagining we are the computer again, if we evaluate 'A' and find it to be false, then we can skip the evaluation of 'B' because the answer will always be false. However if we evaluate 'A' and it turns out to be true then we must evaluate 'B', and the final result is then whatever 'B' is evaluated to. In this way an 'and' behaves like an 'if' statement.

By using these two operators, complex if/elif/else conditional blocks can be expressed as a single expression. Evaluating the expression is equivalent to running the equivalent conditional code. In summary we have the following equivalencies. Don't memorise this table. You should be able to 'think short circuited' and mimic the computers evaluation procedure when writing your code. But here's a nice table for you to get a grip on it.

Python Conditional Equivalent Expression
if A: B A and B
if not A: B A or B
if A: B else: C (A and B) or C
if A: B elif C: D else: E (A and B) or (C and D) or E
The disadvantage of these short circuit expressions is readability, which can degrade significantly with complex nesting, so comment liberally. They are also purely functional, which means A, B, C and D cannot be statements, only expressions. Also beware when the expression after the 'and' statement could be interperated as the boolean false. For example, in "(A and B) or C", if B is False or 0 or "" or None or [] or () then C will be evaluated and returned regardless of the truth of A, because the whole of (A and B) is evaluated to be false. As a brief side note this problem can be accounted for by making the returns be a list of one element like [(A and [B]) or [C])[0]. Even with these all these limitations, they help us in many instances to construct more effective lambdas. So our_compare can be rewritten as a single expression: "(x>y) or (x<y and -1)". Notice there is no need to say (x>y or 1) because x>y already evaluates to True(1) if it is correct. Also there is no need for a trailing "or 0" because the result will be false if neither of the earlier conditionals are true. This leads us to write our sort as:
a.sort( lambda x,y: x>y or (x<y and -1) )
give this a go and you will see it works. Applications So how can we apply this to save ourselves time and bugs? One application is for code that requires multiple sorts on lists of objects. If we have a list of objects that always need to be sorted the same way, then we can override the objects _cmp_ method in its class definition and place our cmp function in there. However sometimes we may have a list of objects that we want to sort in different ways, on different values, at different points in the code. This comes up quite often when dealing with groups of game objects. For instance:
# a list of the game object from top of screen to bottom ERRATA: see comments for correction
toptobottom=objlist[:].sort(lambda a,b: a.y>b.y or (a.y<b.y and -1))

# a list of the same objects from left of screen to right
lefttoright=sorted(objlist, lambda a,b: a.x>b.x or (a.x<b.x and -1))

# draw all the game objects in z order
[obj.Draw(renderer) for obj in sorted( objlist, lambda a,b: a.zindex>b.zindex
  or (a.zindex<b.zindex and -1) ) ]
This is just scratching the surface of what can be done. All kinds of conditional structures can be reduced by short circuiting, and when combined with methods like sort() and even more so with list comprehensions, extremely complex operations can be performed with much less code (and bugs) than an ordinary procedural approach. Try bringing some of these functional techniques into your code. Try and think of ways that you can use it to reduce your burden. It doesn't take long to grasp the idea, but it does take practice to be efficient at it and also to recognise some of the more esoteric places it can be used. Give it a go, and merry christmas!
 

22 comments:

Anonymous said...

Hello -- arriving from reddit.com I'd like to make you aware of two things:

1) The three-result (-1, 0, +1) comparison of two values can be achieved using the cmp function, e.g. cmp(x,y) returns 1 if x >y , 0 if x==y etc. This was often used to get customised sorting using list.sort, so you might do something like:

l.sort(lambda x,y: cmp(x.attr,y.attr)) to sort by a different attribute. or perhaps: l.sort(lambda x,y: cmp(x.attr,y.attr) or cmp(x.attr2,y.attr2)) to sort by attr then attr2.



2) However, these days this is rarely necessary because of the new key keyword argument to sort which lets you use the "Schwartzian transformation" aka "Decorate-Sort-Undecorate" pattern. Using that you tell sort to use a different key for comparing two items (which ends up being faster than a custom comparison function as its complexity is proportoinal to the length of the list rather than number of comparisons necessary). e.g.:

l.sort(key = lambda x: x.y)
to sort the list by each member's y attribute.

Having written the above, it is not actually the fastest way as that forces python's fast C sorting code (aka "timsort") to evaluate your little python lambda. Even faster is this:

l.sort(key = operator.attrgetter('y'))

which stays entirely in C, though IMO looks slighlty awkward. the operator.attrgetter function is a factory that returns a callable that retrieves that given attribute from the object given to the callable. There's a corresponding operator.itemgetter to do l[x] indexing.

(Finally, a little typo in your post: sort does not return the new sorted list, thus newl = l[:].sort(...) assigns None to newl.)

Paddy3118 said...

Python 2.5 has an if expression so you can write:
lambda x,y: 1 if (x>y) else ( -1 if (x<y) else 0 )

- Paddy.

Anonymous said...

http://www.purchaselevitranorx.com/#6retrograde-orbit.blogspot.com - buy viagra [url=http://www.purchaselevitranorx.com/#4retrograde-orbit.blogspot.com]levitra 20mg[/url] levitra
levitra

Anonymous said...

http://www.bloglog.com/blog/xmyshang6/126030/the-technology-in-question-m-a-r-c-b-y-m-a-r-c-j-a-c-o-b-s-is-wi-fi
http://www.toma.jp/blog/jiumengshici/?entry_id=869830
http://www.webshare.cc/blog/b/blog_view.php?mid=547194&id=147&show_bbslink=
http://www.acdating.net/index.php?do=/blog/77942/always-be-aware-of-where-you-search-for-your-spyware-virus-protetion/
http://www.moselbook.de/index.php?p=blogs/viewstory/147039
http://www.altasbaixarias.baumgarten.cnt.br/jcow/blogs/viewstory/87668
http://nen360.nenonline.org/blog/mobile-phones-double-digital-cameras-are-not-uncommon-any-more
http://hollar.se/index.php?do=/blog/2357/the-on-tory-burch-riding-boots-foot-navigation-is-a-great-additional-featur/
http://d.hatena.ne.jp/jiumengshici/20130129/1359425718
http://archive.remdublin.com/blog/huangshumei/2013/01/29/home-phones-are-easily-available-internet
http://xmyishang.exblog.jp/17225972
http://www.soberhood.org/node/125735
http://socialcirclessite.com/blogs/viewstory/1229595
http://oriflameblog.cz/forum/topic/the-pay-as-you-go-is-the-most-convenient-and-straightforward-modes-which-comes?replies=1#post-38926
http://mesmerise.us/jcow/index.php?p=blogs/viewstory/113

Anonymous said...

Hello. And Bye. Thank you very much.

Anonymous said...

Hello. And Bye. Thank you very much.

Anonymous said...

Hello. And Bye. Thank you very much.

Anonymous said...

Hello. And Bye. Thank you very much.

Anonymous said...

Hello. And Bye. Thank you very much.

Anonymous said...

Hi, provigil price - provigil online http://www.provigilpills4sale.com/, [url=http://www.provigilpills4sale.com/]buy provigil [/url]

Anonymous said...

Li, neurontin without prescription - order gabapentin http://www.neurontinonlineprice.net/, [url=http://www.neurontinonlineprice.net/]buy gabapentin[/url]

Anonymous said...

Hi, order valium - cheap valium online no prescription http://www.9oul.com/, [url=http://www.9oul.com/]buy valium[/url]

Anonymous said...

4, Order Lamisil - lamisil without prescription http://www.lamisilfast24.net/, [url=http://www.lamisilfast24.net/]Order Lamisil [/url]

Anonymous said...

4, Order Accutane Online - accutane pills http://www.benefitsofisotretinoin.net/, [url=http://www.benefitsofisotretinoin.net/]Cheap Accutane[/url]

Anonymous said...

2, Imitrex Cheap - Sumatriptan Price - buy sumatriptan http://www.sheddesignhub.com/ .

Anonymous said...

2, Order Imitrex - Imitrex Pills - cheap sumatriptan http://www.rcenter.net/ .

Anonymous said...

5, [url=http://www.nexiumpricewatch.net/] Buy Nexium [/url] - Nexium Online - nexium without rx http://www.nexiumpricewatch.net/ .

Anonymous said...

12, Zyban Pharmacy - Buy Zyban - zyban without prescription http://www.installandenjoy.com/

Anonymous said...

geotorelxzp low rate loans
debt consolidation companies

Anonymous said...

Do you mind if I quote a couple of your posts as long as
I provide credit and sources back to your website? My blog is
in the exact same niche as yours and my visitors would really benefit from
a lot of the information you provide here. Please let
me know if this alright with you. Many thanks!

Feel free to surf to my website ... wholesale oakley sunglasses

Anonymous said...

If you approach this from the viewpoint that the man may have a low sperm count, you may miss the fact that the woman may be low in folic acid. Remember, eating fruits and veggies is an excellent way to get a lot of needed vitamins and minerals but not if they are loaded with pesticides.
http://pregnancyhelper.in

Anonymous said...


Health insurance quotes for your whole family

Every week families in the united states search for reasonably priced term life insurance quotes.Many individuals are surprised upon completion of their application in how easy the process is to shop and buy life insurance online.Each one of our clients that is searching for a term life policy has very different monetary needs.Therefore, we make sure that we compare 100 of the top rated carriers.We do this to ensure that our clients get the best deal in the marketplace.We need to make sure you find a policy that will last a lifetime.Our consulting firm works meticulously to ensure that we locate the best term life insurance contract for your family, said vince bagni, of paramount life insurance.Get insurance quotes today
Our firm makes shopping online for life insurance very easy.It doesn't matter if you are looking for an reasonable way to cover your expenses with mortgage affordable life insurance, or you have a very complex need involving premium financing for your insurance, we will give you specific and unwavering consultation that will give you the proper end result.Paramount offers life insurance products that are designed to your direct requirements and goals, bagni said.
Principal life insurance offers insurance products and services through its online website.Advisors at our firm are independent consultants that have years of experience in the financial services industry.They are well versed in term, whole, and universal life insurance.Furthermore, our advisors have a great deal of experience in looking at your total financial picture.Ask your paramount advisor for our special annuity or life insurance beneficiary review today.
Many of our consumers ask us, should i buy whole [URL=http://www.nikeshoxsale99.com]cheap mens nike shox[/URL] life, or term insurance?That is one of the best questions a client can ask.Everyone takes a different approach to their life insurance.Some look at life insurance as protection, while others see it as a way to use the internal revenue code to build cash value tax free.It really will come down to our complete analysis of your situation.You insurance needs can change on an annual basis.Make sure you complete your policy review this year.
Owing to the growing number of immigrants coming into the state of california, the number of uninsured individuals is steeply rising.It was due to this factor that the california health insurance act was passed in 2003 to provide the largest possible number of workers and their families [URL=http://www.nikeshoxs2013.com]Cheap Nike Shox nz[/URL] with affordable health insurance coverage.
There are health insurance policies galore in caliFornia and most of them are regulated by the caliFornia department of insurance and you have to select the one from many different kinds, depending upon your needs, budget and health care requirements.Some of the policies are:Indemnity policies(Traditional fee-For-Service insurance), Preferred Provider Organizations(Ppos), Health Maintenance Organizations(Hmos or managed care), Self-Insured health plans(Single employer self-Insured plans)And multiple employer welfare arrangements(Mewas).There are also special policies like:Major risk medical insurance program(Mrmip), Healthy Families Program(Hfp), Access For Infants and Mothers Program (Aim), Pacific Health Advantage(Pacadvantage), and other Supplemental Health Insurance Policies.