问题描述
考虑以下最小化示例:
代码:
a = [(1,'A'),(2,(3,(4,(5,'A')]
b = [(1,'B'),'B')]
c = []
d = [(1,'D'),'D')]
print(sorted(a+b+c+d))
结果:
[(1,(1,'A')]
Python 按每个集合的第一项然后按第二项对集合列表进行排序。没关系。
现在,我需要字符串中的第二个排序顺序是“圆形”(不确定这是否是正确的术语)。
此外,我想在新的有序列表中指定最后一个字符串。例如,如果我指定 'B'
,则有序列表应从 'C'
开始。如果 'C'
不存在,它应该从 'D'
等开始。但是,也可能发生指定字符可能不在列表中的情况,例如如果 'C'
不存在,则新的排序列表仍应从 'D'
开始。
编辑:
抱歉,我没有添加集合列表的所需输出顺序以使其清楚。
假设我会指定 mySpecialSort(myList,'B')
。
然后应该首先是所有包含 1
的集合作为最高优先级排序顺序,然后是“循环”字符串(这里从 'D'
开始,因为列表中没有 C
).
所需的排序顺序:
[(1,'A')]
或缩短的可读形式:
1D,1A,1B,2D,2A,2B,3D,3A,3B,4D,4A,5A
我为单个字符列表(此处有重复项)上的“循环”排序提出的(但到目前为止仅)的(麻烦的)解决方案是以下:
代码:
myList = ['A','D','E','G','Z','A','J','K','T']
def myCircularSort(myList,myLast):
myListTmp = sorted(list(set(myList + [myLast]))) # add myLast,remove duplicates and sort
idx = myListTmp.index(myLast) # get index of myLast
myStart = myListTmp[(idx+1)%len(myListTmp)] # get the start list item
myListSorted = sorted(list(set(myList))) # sorted original list
print("normal sort: {}".format(myListSorted))
idx_start = myListSorted.index(myStart) # find start item and get its index
Mynewsort = myListSorted[idx_start:] + myListSorted[0:idx_start] # split list and put in new order
print("Circular sort with {} as last: {}\n".format(myLast,Mynewsort))
myCircularSort(myList,'D')
myCircularSort(myList,'X')
结果:
normal sort: ['A','T','Z']
Circular sort with D as last: ['E','D']
normal sort: ['A','Z']
Circular sort with X as last: ['Z','T'] # X actually not in the list
但是,现在我被困在如何将这种“循环”排序(在集合列表的第二项上)与“正常”排序(在集合列表的第一项上)结合起来。>
或者,我可能会想到一种“蛮力”方法来查找最高索引(此处:4
)和所有现有字符串(此处:A
-Z
)和检查两个嵌套 for 循环中每个组合是否存在。
我是在正确的轨道上还是会做一些非常复杂和低效的事情,或者我是否缺少一些智能的 Python 功能?
编辑 2:
经过一些进一步的搜索,我猜 lambda
和 cmp(x,y)
会完成这项工作(参见 example),但它似乎不再存在于 python3 中。所以,那么可能是带有 operator.itemgetter()
或 operator.methodcaller()
的东西,我仍然不知道如何使用,因为我错过了很好的例子......
解决方法
您可以使用 dict 将字母映射到其正确位置:
from string import ascii_uppercase as ABC
start = ABC.index('D') + 1
sorter = {
ABC[(n + start) % len(ABC)]: n
for n in range(len(ABC))
}
myList = ['A','D','E','G','Z','A','J','K','T']
print(sorted(myList,key=sorter.get))
# ['E','T','D']
要处理任意关键字,请将它们提取到 keys
列表中,根据需要重新排列并使用 keys.index(word)
作为排序键:
myList = [
(1,'ARTHUR'),(2,'CHARLIE'),(3,'GEORGE'),(4,'HARRY'),(5,'JACK'),(6,'LEO'),(7,'MUHAMMAD'),(8,'NOAH'),(9,'OLIVER'),]
def circ_sorted(lst,start):
keys = sorted(e[1] for e in lst)
less = sum(1 for k in keys if k <= start)
keys = keys[less:] + keys[:less]
return sorted(lst,key=lambda e: (keys.index(e[1]),e[0]))
print(circ_sorted(myList,'LEO')) ## [MUHAMMAD,NOAH...]
print(circ_sorted(myList,'IAN')) ## [JACK,LEO...]
,
呼,这很费时间,但我想我现在有一个解决方案。至少结果似乎具有所需的顺序。
模块 functools
提供了 cmp_to_key
来替换 cmp()
,它显然在 Python3 中被删除了。至少这是我发现的here。
如果有“原生”Python3 解决方案,我很乐意了解它。欢迎提出意见、改进和简化。
因此,以下代码首先按数字(此处为 1 到 5)对列表的集合进行排序,然后以循环方式(此处为:Ag、Au、Ca、Fe、Ti)按字符串排序,使得最后一个字符串将由 myRef
决定。
代码:
### special numerical and circular alphanumerical sort on a list of sets
from functools import cmp_to_key
# different lists of sets
ag = [(1,'Ag'),'Ag')]
au = [(1,'Au'),'Au')]
ba = []
ca = [(1,'Ca'),'Ca')]
fe = [(1,'Fe'),'Fe')]
ti = [(1,'Ti'),'Ti')]
myList = fe + ti + ag + au + ca + ba # merge all lists
def mySpecialCircularSort(myList,myRef):
myList = list(set(myList)) # remove duplicates
myListNew = sorted(myList,key=cmp_to_key(lambda a,b:
-1 if a[0]<b[0] else 1 if a[0]>b[0] else
-1 if b[1]==myRef else
1 if a[1]==myRef else
-1 if a[1]>myRef and b[1]<myRef else
1 if a[1]<myRef and b[1]>myRef else
-1 if a[1]<b[1] else
1 if a[1]>b[1] else 0))
print("Circular sort with {} as last: {}".format(myRef,myListNew))
print("Unsorted as is: {}\n".format(myList))
mySpecialCircularSort(myList,'Ag')
mySpecialCircularSort(myList,'Au')
mySpecialCircularSort(myList,'Ba') # since Ba-List was empty,the result will be same as 'Au'
mySpecialCircularSort(myList,'Ca')
mySpecialCircularSort(myList,'Fe')
mySpecialCircularSort(myList,'Ti')
结果:
Unsorted as is: [(1,(1,'Ca')]
Circular sort with Ag as last: [(1,'Ag')]
Circular sort with Au as last: [(1,'Ag')]
Circular sort with Ba as last: [(1,'Ag')]
Circular sort with Ca as last: [(1,'Ag')]
Circular sort with Fe as last: [(1,'Ag')]
Circular sort with Ti as last: [(1,'Ag')]
,
具有自定义排序键功能:
from string import ascii_uppercase
order = {c: i for i,c in enumerate(ascii_uppercase)}
def circular_sort(lst,last):
return sorted(lst,key=lambda x: (x[0],order[x[1]] + 26*(x[1]<=last)))
>>> circular_sort(a+b+c+d,'B')
[(1,'D'),'A'),'B'),'B')]
这只是将 26 添加到任何小于或等于指定的最后一个字母的字母索引。
,我在示例数据中看到了一个模式:
a = [(1,'A')]
b = [(1,'B')]
c = []
d = [(1,'D')]
也许这个模式误导了我,而真实数据没有相同的模式。
在这种情况下,请忽略我的回答。
否则,鉴于 OP 对我的评论的回答:
起点是几个单独的列表
我提出这个解决方案:
- 用源列表构建一个嵌套列表;
- 根据起点将列表旋转
n
次; - 转置;
- 压平;
这是一个实现示例,定义了一些帮助程序:
from itertools import zip_longest
def rotate(l,n):
return l[n:] + l[:n]
def transpose(l):
return [list(filter(None,i)) for i in zip_longest(*tmp)]
def flatten(l):
return [item for sublist in l for item in sublist]
然后,例如旋转三圈以从 D
开始:
tmp = [a,b,c,d]
tmp = rotate(tmp,3)
tmp = transpose(tmp)
tmp = flatten(tmp)
tmp
#=> [(1,'A')]