排序列表时嵌套的lambda语句

时间:2018-04-05 12:07:19

标签: python python-3.x list sorting lambda

我希望先按照数字排序以下列表,然后按文字排序。

lst = ['b-3', 'a-2', 'c-4', 'd-2']

# result:
# ['a-2', 'd-2', 'b-3', 'c-4']

尝试1

res = sorted(lst, key=lambda x: (int(x.split('-')[1]), x.split('-')[0]))

我对此不满意,因为它需要将字符串拆分两次,以提取相关组件。

尝试2

我提出了以下解决方案。但我希望通过Pythonic lambda语句提供更简洁的解决方案。

def sorter_func(x):
    text, num = x.split('-')
    return int(num), text

res = sorted(lst, key=sorter_func)

我查看Understanding nested lambda function behaviour in python但无法直接调整此解决方案。有没有更简洁的方法来重写上面的代码?

8 个答案:

答案 0 :(得分:17)

有两点需要注意:

  • 单行答案不一定更好。使用命名函数可能会使您的代码更易于阅读。
  • 您可能正在寻找嵌套的lambda语句,因为函数组合不是标准库的一部分(请参阅注释#1)。您可以轻松完成的工作是让一个lambda函数返回 另一个 lambda函数的结果。

因此,正确答案可以在Lambda inside lambda中找到。

针对您的具体问题,您可以使用:

res = sorted(lst, key=lambda x: (lambda y: (int(y[1]), y[0]))(x.split('-')))

请记住,lambda只是一个功能。您可以在定义后立即调用它,即使是在同一行上。

注意#1 :第三方toolz库允许合成:

from toolz import compose

res = sorted(lst, key=compose(lambda x: (int(x[1]), x[0]), lambda x: x.split('-')))

注意#2 :正如@chepner指出的那样,此解决方案的缺陷(重复函数调用)是考虑PEP-572的原因之一。

答案 1 :(得分:6)

我们可以将split('-')返回的列表包装在另一个列表中,然后我们可以使用循环来处理它:

# Using list-comprehension
>>> sorted(lst, key=lambda x: [(int(num), text) for text, num in [x.split('-')]])
['a-2', 'd-2', 'b-3', 'c-4']
# Using next()
>>> sorted(lst, key=lambda x: next((int(num), text) for text, num in [x.split('-')]))
['a-2', 'd-2', 'b-3', 'c-4']

答案 2 :(得分:2)

PeriodicWorkRequest

答案 3 :(得分:2)

在几乎所有情况下,我都会尝试第二次尝试。它是可读且简洁的(我希望每次使用三行而不是一行!)-尽管函数名可能更具描述性。但是,如果您将其用作本地函数,那就没什么大不了了。

您还必须记住,Python使用key函数,而不是cmp(比较)函数。因此,要对长度为n的可迭代数组进行排序,key函数将被精确调用n次,但是排序通常会进行O(n * log(n))个比较。因此,只要您的键函数的算法复杂度为O(1),键函数的调用开销就无关紧要了。那是因为:

O(n*log(n)) + O(n)   ==  O(n*log(n))

有一个例外,这是Python sort的最佳情况:在最佳情况下,sort仅进行O(n)比较,但这仅在可迭代对象已经排序(或几乎已排序)时发生排序)。如果Python具有比较功能(在Python 2中确实有一个比较功能),则该功能的常数会更为重要,因为它将被调用O(n * log(n))次(每次比较均调用一次)。

因此,不要为简洁或提高速度而烦恼(除非您可以在不引入太大常数因素的情况下减少big-O,否则您应该这样做!),首先要考虑的是可读性。因此,您实际上应该执行任何嵌套的lambda或其他任何奇特的构造(可能作为练习除外)。

长话短说,只需使用您的#2:

def sorter_func(x):
    text, num = x.split('-')
    return int(num), text

res = sorted(lst, key=sorter_func)

顺便说一句,它也是所有提议的方法中最快的(尽管差别不大):

enter image description here

摘要:它可读且快速

用于重现基准的代码。它需要安装simple_benchmark才能运行(免责声明:这是我自己的库),但是可能有等效的框架可以执行这种任务,但我对此很熟悉:

# My specs: Windows 10, Python 3.6.6 (conda)

import toolz
import iteration_utilities as it

def approach_jpp_1(lst):
    return sorted(lst, key=lambda x: (int(x.split('-')[1]), x.split('-')[0]))

def approach_jpp_2(lst):
    def sorter_func(x):
        text, num = x.split('-')
        return int(num), text
    return sorted(lst, key=sorter_func)

def jpp_nested_lambda(lst):
    return sorted(lst, key=lambda x: (lambda y: (int(y[1]), y[0]))(x.split('-')))

def toolz_compose(lst):
    return sorted(lst, key=toolz.compose(lambda x: (int(x[1]), x[0]), lambda x: x.split('-')))

def AshwiniChaudhary_list_comprehension(lst):
    return sorted(lst, key=lambda x: [(int(num), text) for text, num in [x.split('-')]])

def AshwiniChaudhary_next(lst):
    return sorted(lst, key=lambda x: next((int(num), text) for text, num in [x.split('-')]))

def PaulCornelius(lst):
    return sorted(lst, key=lambda x: tuple(f(a) for f, a in zip((int, str), reversed(x.split('-')))))

def JeanFrançoisFabre(lst):
    return sorted(lst, key=lambda s : [x if i else int(x) for i,x in enumerate(reversed(s.split("-")))])

def iteration_utilities_chained(lst):
    return sorted(lst, key=it.chained(lambda x: x.split('-'), lambda x: (int(x[1]), x[0])))

from simple_benchmark import benchmark
import random
import string

funcs = [
    approach_jpp_1, approach_jpp_2, jpp_nested_lambda, toolz_compose, AshwiniChaudhary_list_comprehension,
    AshwiniChaudhary_next, PaulCornelius, JeanFrançoisFabre, iteration_utilities_chained
]

arguments = {2**i: ['-'.join([random.choice(string.ascii_lowercase),
                              str(random.randint(0, 2**(i-1)))]) 
                    for _ in range(2**i)] 
             for i in range(3, 15)}

b = benchmark(funcs, arguments, 'list size')

%matplotlib notebook
b.plot_difference_percentage(relative_to=approach_jpp_2)

我冒昧地包括了我自己的一个库iteration_utilities.chained的函数组合方法:

from iteration_utilities import chained
sorted(lst, key=chained(lambda x: x.split('-'), lambda x: (int(x[1]), x[0])))

速度相当快(第2位或第3位),但比使用您自己的功能还慢。


请注意,如果您使用的算法具有key(或更好)的算法复杂性,例如O(n)min,则max的开销会更大。那么按键功能的恒定因素会更显着!

答案 4 :(得分:1)

只有当项目的索引为0时(逆转拆分列表时),才可以转换为整数。创建的唯一对象(除split的结果外)是用于比较的2元素列表。其余的只是迭代器。

sorted(lst,key = lambda s : [x if i else int(x) for i,x in enumerate(reversed(s.split("-")))])

顺便说一下,-令牌在涉及数字时并不是特别好,因为它使负数的使用变得复杂(但可以用s.split("-",1)来解决

答案 5 :(得分:0)

lst = ['b-3', 'a-2', 'c-4', 'd-2']
def xform(l):
    return list(map(lambda x: x[1] + '-' + x[0], list(map(lambda x: x.split('-'), lst))))
lst = sorted(xform(lst))
print(xform(lst))

看到它here 我认为@jpp有一个更好的解决方案,但是一个有趣的小脑力激荡器: - )

答案 6 :(得分:0)

一般情况下,使用FOP(功能导向编程),您可以将它全部放在一个衬里中并将lambda嵌套在一行内,但这通常是不好的礼仪,因为在2嵌套功能之后它全部变为很不可思议。

解决此类问题的最佳方法是将其分为几个阶段:

1:将字符串拆分为tuple

lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( lambda str_x: tuple( str_x.split('-') ) , lst)   

2:按照您的意愿排序元素:

lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( lambda str_x: tuple( str_x.split('-') ) , lst)  
res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) ) 

由于我们将字符串拆分为元组,因此它将返回一个地图对象,该对象将表示为元组列表。所以现在第3步是可选的:

3:代表您查询的数据:

lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( lambda str_x: tuple( str_x.split('-') ) , lst)  
res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) ) 
res = map( '-'.join, res )  

现在请记住,lambda nesting可以生成更多的单行解决方案,并且您可以实际嵌入非离散嵌套类型的lambda,如下所示:

a = ['b-3', 'a-2', 'c-4', 'd-2']
resa = map( lambda x: x.split('-'), a)
resa = map( lambda x: ( int(x[1]),x[0]) , a) 
# resa can be written as this, but you must be sure about type you are passing to lambda 
resa = map( lambda x: tuple( map( lambda y: int(y) is y.isdigit() else y , x.split('-') ) , a)  

但正如您可以看到list a的内容除了由'-'分隔的2个字符串类型之外的任何内容之外, lambda 函数都会引发错误,您将会我很难搞清楚到底发生了什么。

所以最后,我想向您展示第三步程序的几种编写方式:

1:

lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( '-'.join,\
             sorted(\ 
                  map( lambda str_x: tuple( str_x.split('-') ) , lst),\
                       key=lambda x: ( int(x[1]), x[0] )\
              )\
         )

2:

lst = ['b-3', 'a-2', 'c-4', 'd-2']
res = map( '-'.join,\
        sorted( map( lambda str_x: tuple( str_x.split('-') ) , lst),\
                key=lambda x: tuple( reversed( tuple(\
                            map( lambda y: int(y) if y.isdigit() else y ,x  )\
                        )))\
            )\
    )  # map isn't reversible

3:

res = sorted( lst,\
             key=lambda x:\
                tuple(reversed(\
                    tuple( \
                        map( lambda y: int(y) if y.isdigit() else y , x.split('-') )\
                    )\
                ))\
            )

所以你可以看到这一切如何变得非常复杂和难以理解。在阅读我自己或别人的代码时,我常常喜欢看到这个版本:

res = map( lambda str_x: tuple( str_x.split('-') ) , lst) # splitting string 
res = sorted( res, key=lambda x: ( int(x[1]), x[0] ) ) # sorting for each element of splitted string
res = map( '-'.join, res ) # rejoining string  

这一切都来自我。玩得开心。我已在py 3.6中测试了所有代码。

PS。一般来说,您有两种方法可以接近lambda functions

mult = lambda x: x*2  
mu_add= lambda x: mult(x)+x #calling lambda from lambda

这种方式对于典型的FOP非常有用,在这种情况下,您拥有常量数据,并且需要操作该数据的每个元素。但是,如果您需要在 list,tuple,string,dict 中解决lambda,则这些操作并非常有用,因为如果存在任何container/wrapper类型,容器内元素的数据类型变得有问题。因此,我们需要提升抽象级别并确定如何根据其类型操作数据。

mult_i = lambda x: x*2 if isinstance(x,int) else 2 # some ternary operator to make our life easier by putting if statement in lambda 

现在您可以使用其他类型的lambda函数:

int_str = lambda x: ( lambda y: str(y) )(x)*x # a bit of complex, right?  
# let me break it down. 
#all this could be written as: 
str_i = lambda x: str(x) 
int_str = lambda x: str_i(x)*x 
## we can separate another function inside function with ()
##because they can exclude interpreter to look at it first, then do the multiplication  
# ( lambda x: str(x)) with this we've separated it as new definition of function  
# ( lambda x: str(x) )(i) we called it and passed it i as argument.  

有些人把这种语法称为嵌套的lambdas,我称之为indiscreet,因为你可以看到所有。

你可以使用递归的lambda赋值:

def rec_lambda( data, *arg_lambda ):  
    # filtering all parts of lambda functions parsed as arguments 
    arg_lambda = [ x for x in arg_lambda if type(x).__name__ == 'function' ]  

    # implementing first function in line
    data = arg_lambda[0](data)  

    if arg_lambda[1:]: # if there are still elements in arg_lambda 
        return rec_lambda( data, *arg_lambda[1:] ) #call rec_lambda
    else: # if arg_lambda is empty or []
        return data # returns data  

#where you can use it like this  
a = rec_lambda( 'a', lambda x: x*2, str.upper, lambda x: (x,x), '-'.join) 
>>> 'AA-AA' 

答案 7 :(得分:-3)

我认为*如果你确定格式是一致的" [0]字母[1]破折号"超出[2:]的以下索引将始终为数字,然后您可以使用切片替换split,或者您可以使用str.index(' - ')

sorted(lst, key=lambda x:(int(x[2:]),x[0]))

# str.index('-') 
sorted(lst, key=lambda x:(int(x[x.index('-')+1 :]),x[0]))