Python, Perl: One-Liner Loop Functional Style
Today we show a example of a loop done as a one-liner of Functional Programing style.
Problem
Suppose you have a list, and its elements are full paths to image files:
/Users/t/t4/oh/DSCN2059m-s.jpg /Users/t/t4/oh/DSCN2062m-s.jpg /Users/t/t4/oh/DSCN2097m-s.jpg /Users/t/t4/oh/DSCN2099m-s.jpg /Users/t/Icons_dir/icon_sum.gif
Note some of these has a “-s” suffix. Your job, is to generate a new list, such that, if a image file has “-s”, check on disk if there is a version without the “-s”. If so, replace the list element with that. In summary, you want to replace the “-s” ones but only if the file exist on disk. Here's a solution:
Python Solution
# -*- coding: utf-8 -*- # python 2 import re, os.path imgPaths=[ u'/Users/joe/lanci/t4/oh/DSCN2059m-s.jpg', u'/Users/joe/lanci/t4/oh/DSCN2062m-s.jpg', u'/Users/joe/lanci/t4/oh/DSCN2097m-s.jpg', u'/Users/joe/lanci/t4/oh/DSCN2099m-s.jpg', u'/Users/t/web/Icons_dir/icon_sum.gif'] # change the image path to the full sized image, if it exists # that is, if image ends in -s.jpg, find one without the '-s'. imgPaths2=[] for myPath in imgPaths: p=myPath (dirName, fileName) = os.path.split(myPath) (fileBaseName, fileExtension)=os.path.splitext(fileName) if(fileBaseName[-2:] == '-s'): p2=os.path.join(dirName,fileBaseName[0:-2]) + fileExtension if os.path.exists(p2): p=p2 imgPaths2.append(p) print imgPaths2
Functional Style
But how do you do it in a functional programing style? Namely, something of the from imgPath2=f(imgPath)
, where the f is some function. Normally, the f would be a pure function construct made up on the spot, that is, lambda. But Python's lambda is limited to only a expression as its body. Nevertheless, one can achieve a functional style with some workaround, using map twice. Here's the code:
imgPaths3 = map( lambda x: os.path.exists(x[1]) and x[1] or x[0], \ map(lambda x: (x, re.sub( r"^(.+?)-s(\.[^.]+)$",r"\1\2", x)), imgPaths))
The first map:
newList = map(lambda x: (x, re.sub( r"^(.+?)-s(\.[^.]+)$",r"\1\2", x)), imgPaths)
generate a list of pairs (x, y)
, where x is the same element in imgPaths, and y is one without the -s. Then, a second map:
map( lambda x: os.path.exists(x[1]) and x[1] or x[0], newList, imgPaths))
checks if the path y exists, if so, use that, else, use the x part. The function body is essentially of a Conditional Expression, of the from (test, trueResult, falseResult)
. This form of a test that returns a expression is very important in functional programing. Because, in functional programing a common pattern is to sequence functions and passing values. One cannot stop in the middle and use a block structure. Here's how this form's syntax in several languages:
(test_expr ? true_expr : false_expr)
, C, PerlIf[test_expr, true_expr, false_expr]
, Mathematica(if test_expr true_expr false_expr)
, LISP
Boolean Operators as a Hack for Branching
In Python, there is no such form for this, but a semantically equivalent workaround is to sequence boolean expressions as used in our example. Namely:
testExpr and trueExpr or falseExpr (Python)
This works because it exploits a particular behavior of how Python treats boolean sequences. In Python,
x and y
returns y if x is true. And, x or y
returns x if x is true. This behavior is compatible with the mathematical sense of booleans. e.g. mathematically x and y
should be true if both are true, yet in Python if x is true then y is returned, and if y is true then this is compatible with the math sense, but if y is false then the whole result is also false, so it also satisfies the math sense. Similar is the case of x or y
. The point here is that one of the element is returned, instead of a real True or False value. Therefore, in a twisted way this can be used as a conditional expression.
result1= True and 4 or 5 # returns 4 result2= False and 4 or 5 # returns 5 print result1 print result2
Such language behavior is a result of the sloppiness of language design, particular seen in unix shells (For example, the operators
&&
and
||
).
The problem with this design is that code relying on the returned element of a boolean sequence does not clearly indicate the programer's intention.
It works by a peculiar side-effect, instead of expressed program logic.
Perl Solution
Here's the Perl code of the same loop block:
# -*- coding: utf-8 -*- # perl use File::Basename; @imgPaths=( '/Users/joe/lanci/t4/oh/DSCN2059m-s.jpg', '/Users/joe/lanci/t4/oh/DSCN2062m-s.jpg', '/Users/joe/lanci/t4/oh/DSCN2097m-s.jpg', '/Users/joe/lanci/t4/oh/DSCN2099m-s.jpg', '/Users/t/web/Icons_dir/icon_sum.gif'); # change the image path to the full sized image, if it exists # that is, if image ends in -s.jpg, find one without the '-s'. @imgPaths2=(); for $myPath (@imgPaths){ $p=$myPath; ($fileBaseName, $dirName, $fileExtension) = fileparse($myPath, ('\.[^.]+$') ); if (substr($fileBaseName,-2,2) eq '-s') { $p2= $dirName . '/' . substr($fileBaseName,0,length($fileBaseName)-2) . $fileExtension; print $p2, "\n"; if (-e $p2) { $p=$p2} } push(@imgPaths2, $p); } use Data::Dumper; print Dumper(\@imgPaths2)
In Perl, this can be written in a functional style one-liner:
@imgPaths3= map{ $y = $_; $y =~ s/^(.+?)-s(\.[^.]+)$/$1$2/; -e $y ? $y : $_; } @imgPaths;