如何在 Python 中进行循环(子)排序?
How to do a circular (sub)sort of sets in Python?
考虑以下最小化示例:
代码:
a = [(1,'A'), (2,'A'), (3,'A'), (4,'A'), (5,'A')]
b = [(1,'B'), (2,'B'), (3,'B')]
c = []
d = [(1,'D'), (2,'D'), (3,'D'), (4,'D')]
print(sorted(a+b+c+d))
结果:
[(1, 'A'), (1, 'B'), (1, 'D'), (2, 'A'), (2, 'B'), (2, 'D'), (3, 'A'), (3, 'B'), (3, 'D'), (4, 'A'), (4, 'D'), (5, 'A')]
Python 按每个集合的第一项然后是第二项对集合列表进行排序。没关系。
现在,我需要第二个排序顺序在字符串中是“循环的”(不确定这是否是正确的术语)。
此外,我想指定新排序列表中的最后一个字符串。例如,如果我指定 'B'
,则有序列表应从 'C'
开始。如果 'C'
不存在,它应该从 'D'
开始,等等。但是,指定的字符也可能不在列表中,例如如果 'C'
不存在,新的排序列表仍然应该从 'D'
.
开始
编辑:
抱歉,我没有添加所需的集合列表输出顺序以使其清楚。
假设我会指定 mySpecialSort(myList,'B')
。
然后应该首先是包含 1
作为最高优先级排序顺序的所有集合,然后是“循环”字符串(这里从 'D'
开始,因为列表中没有 C
).
所需的排序顺序:
[(1, 'D'), (1, 'A'), (1, 'B'), (2, 'D'), (2, 'A'), (2, 'B'), (3, 'D'), (3, 'A'), (3, 'B'), (4, 'D'), (4, 'A'), (5, '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', 'D', 'E', 'G', 'J', 'K', 'T', 'Z']
Circular sort with D as last: ['E', 'G', 'J', 'K', 'T', 'Z', 'A', 'D']
Normal sort: ['A', 'D', 'E', 'G', 'J', 'K', 'T', 'Z']
Circular sort with X as last: ['Z', 'A', 'D', 'E', 'G', 'J', 'K', 'T'] # X actually not in the list
但是,现在我被困在如何获得这种“循环”排序(在集合列表的第二项上)和“正常”排序(在集合列表的第一项上)。
或者,我可能会想到一种“强力”方法来找到最高索引(此处:4
)和所有现有字符串(此处:A
-Z
) 并检查两个嵌套 for 循环中每个组合的存在。
我是在正确的轨道上,还是会做一些非常复杂和低效的事情,或者我是否缺少一些聪明的 Python 功能?
编辑2:
进一步搜索后,我猜 lambda
和 cmp(x,y)
可以完成这项工作(参见 example),但它似乎不存在于 [=85] =]3 了。所以,可能是 operator.itemgetter()
或 operator.methodcaller()
的东西,我仍然不知道如何使用,因为我缺少很好的例子...
您可以使用字典将字母映射到正确的位置:
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', 'G', 'J', 'K', 'T', 'Z', 'A', 'A', '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...]
使用自定义排序键功能:
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'), (2, 'D'), (3, 'D'), (4, 'D'), (1, 'A'), (2, 'A'), (3, 'A'), (4, 'A'), (5, 'A'), (1, 'B'), (2, 'B'), (3, 'B')]
这只是将 26 添加到小于或等于指定的最后一个字母的任何字母的索引。
哎呀,这非常耗时,但我想我现在有解决方案了。至少结果似乎具有所需的顺序。
模块 functools
提供 cmp_to_key
来替换显然在 Python3 中删除的 cmp()
。至少那是我发现的 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'), (2,'Ag'), (3,'Ag'), (4,'Ag'), (5,'Ag')]
au = [(1,'Au'), (2,'Au')]
ba = []
ca = [(1,'Ca'), (2,'Ca'), (3,'Ca')]
fe = [(1,'Fe'), (2,'Fe')]
ti = [(1,'Ti'), (2,'Ti'), (3,'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, 'Fe'), (2, 'Fe'), (1, 'Ti'), (2, 'Ti'), (3, 'Ti'), (1, 'Ag'), (2, 'Ag'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag'), (1, 'Au'), (2, 'Au'), (1, 'Ca'), (2, 'Ca'), (3, 'Ca')]
Circular sort with Ag as last: [(1, 'Au'), (1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (2, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (3, 'Ca'), (3, 'Ti'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Au as last: [(1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (1, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (3, 'Ca'), (3, 'Ti'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Ba as last: [(1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (1, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (3, 'Ca'), (3, 'Ti'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Ca as last: [(1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (1, 'Au'), (1, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (2, 'Ca'), (3, 'Ti'), (3, 'Ag'), (3, 'Ca'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Fe as last: [(1, 'Ti'), (1, 'Ag'), (1, 'Au'), (1, 'Ca'), (1, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (2, 'Ca'), (2, 'Fe'), (3, 'Ti'), (3, 'Ag'), (3, 'Ca'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Ti as last: [(1, 'Ag'), (1, 'Au'), (1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (2, 'Ag'), (2, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (3, 'Ag'), (3, 'Ca'), (3, 'Ti'), (4, 'Ag'), (5, 'Ag')]
我在示例数据中看到一个模式:
a = [(1,'A'), (2,'A'), (3,'A'), (4,'A'), (5,'A')]
b = [(1,'B'), (2,'B'), (3,'B')]
c = []
d = [(1,'D'), (2,'D'), (3,'D'), (4,'D')]
可能这个模式误导了我,而真实数据没有相同的模式。
这种情况,请忽略我的回答。
否则,考虑到 OP 对我评论的回答:
starting point is several separate lists
我提出这个解决方案:
- 使用源列表构建嵌套列表;
- 根据起点旋转列表
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]
然后,比如旋转3次开始D
:
tmp = [a, b, c, d]
tmp = rotate(tmp, 3)
tmp = transpose(tmp)
tmp = flatten(tmp)
tmp
#=> [(1, 'D'), (1, 'A'), (1, 'B'), (2, 'D'), (2, 'A'), (2, 'B'), (3, 'D'), (3, 'A'), (3, 'B'), (4, 'D'), (4, 'A'), (5, 'A')]
考虑以下最小化示例:
代码:
a = [(1,'A'), (2,'A'), (3,'A'), (4,'A'), (5,'A')]
b = [(1,'B'), (2,'B'), (3,'B')]
c = []
d = [(1,'D'), (2,'D'), (3,'D'), (4,'D')]
print(sorted(a+b+c+d))
结果:
[(1, 'A'), (1, 'B'), (1, 'D'), (2, 'A'), (2, 'B'), (2, 'D'), (3, 'A'), (3, 'B'), (3, 'D'), (4, 'A'), (4, 'D'), (5, 'A')]
Python 按每个集合的第一项然后是第二项对集合列表进行排序。没关系。
现在,我需要第二个排序顺序在字符串中是“循环的”(不确定这是否是正确的术语)。
此外,我想指定新排序列表中的最后一个字符串。例如,如果我指定 'B'
,则有序列表应从 'C'
开始。如果 'C'
不存在,它应该从 'D'
开始,等等。但是,指定的字符也可能不在列表中,例如如果 'C'
不存在,新的排序列表仍然应该从 'D'
.
编辑:
抱歉,我没有添加所需的集合列表输出顺序以使其清楚。
假设我会指定 mySpecialSort(myList,'B')
。
然后应该首先是包含 1
作为最高优先级排序顺序的所有集合,然后是“循环”字符串(这里从 'D'
开始,因为列表中没有 C
).
所需的排序顺序:
[(1, 'D'), (1, 'A'), (1, 'B'), (2, 'D'), (2, 'A'), (2, 'B'), (3, 'D'), (3, 'A'), (3, 'B'), (4, 'D'), (4, 'A'), (5, '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', 'D', 'E', 'G', 'J', 'K', 'T', 'Z']
Circular sort with D as last: ['E', 'G', 'J', 'K', 'T', 'Z', 'A', 'D']
Normal sort: ['A', 'D', 'E', 'G', 'J', 'K', 'T', 'Z']
Circular sort with X as last: ['Z', 'A', 'D', 'E', 'G', 'J', 'K', 'T'] # X actually not in the list
但是,现在我被困在如何获得这种“循环”排序(在集合列表的第二项上)和“正常”排序(在集合列表的第一项上)。
或者,我可能会想到一种“强力”方法来找到最高索引(此处:4
)和所有现有字符串(此处:A
-Z
) 并检查两个嵌套 for 循环中每个组合的存在。
我是在正确的轨道上,还是会做一些非常复杂和低效的事情,或者我是否缺少一些聪明的 Python 功能?
编辑2:
进一步搜索后,我猜 lambda
和 cmp(x,y)
可以完成这项工作(参见 example),但它似乎不存在于 [=85] =]3 了。所以,可能是 operator.itemgetter()
或 operator.methodcaller()
的东西,我仍然不知道如何使用,因为我缺少很好的例子...
您可以使用字典将字母映射到正确的位置:
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', 'G', 'J', 'K', 'T', 'Z', 'A', 'A', '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...]
使用自定义排序键功能:
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'), (2, 'D'), (3, 'D'), (4, 'D'), (1, 'A'), (2, 'A'), (3, 'A'), (4, 'A'), (5, 'A'), (1, 'B'), (2, 'B'), (3, 'B')]
这只是将 26 添加到小于或等于指定的最后一个字母的任何字母的索引。
哎呀,这非常耗时,但我想我现在有解决方案了。至少结果似乎具有所需的顺序。
模块 functools
提供 cmp_to_key
来替换显然在 Python3 中删除的 cmp()
。至少那是我发现的 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'), (2,'Ag'), (3,'Ag'), (4,'Ag'), (5,'Ag')]
au = [(1,'Au'), (2,'Au')]
ba = []
ca = [(1,'Ca'), (2,'Ca'), (3,'Ca')]
fe = [(1,'Fe'), (2,'Fe')]
ti = [(1,'Ti'), (2,'Ti'), (3,'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, 'Fe'), (2, 'Fe'), (1, 'Ti'), (2, 'Ti'), (3, 'Ti'), (1, 'Ag'), (2, 'Ag'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag'), (1, 'Au'), (2, 'Au'), (1, 'Ca'), (2, 'Ca'), (3, 'Ca')]
Circular sort with Ag as last: [(1, 'Au'), (1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (2, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (3, 'Ca'), (3, 'Ti'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Au as last: [(1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (1, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (3, 'Ca'), (3, 'Ti'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Ba as last: [(1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (1, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (3, 'Ca'), (3, 'Ti'), (3, 'Ag'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Ca as last: [(1, 'Fe'), (1, 'Ti'), (1, 'Ag'), (1, 'Au'), (1, 'Ca'), (2, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (2, 'Ca'), (3, 'Ti'), (3, 'Ag'), (3, 'Ca'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Fe as last: [(1, 'Ti'), (1, 'Ag'), (1, 'Au'), (1, 'Ca'), (1, 'Fe'), (2, 'Ti'), (2, 'Ag'), (2, 'Au'), (2, 'Ca'), (2, 'Fe'), (3, 'Ti'), (3, 'Ag'), (3, 'Ca'), (4, 'Ag'), (5, 'Ag')]
Circular sort with Ti as last: [(1, 'Ag'), (1, 'Au'), (1, 'Ca'), (1, 'Fe'), (1, 'Ti'), (2, 'Ag'), (2, 'Au'), (2, 'Ca'), (2, 'Fe'), (2, 'Ti'), (3, 'Ag'), (3, 'Ca'), (3, 'Ti'), (4, 'Ag'), (5, 'Ag')]
我在示例数据中看到一个模式:
a = [(1,'A'), (2,'A'), (3,'A'), (4,'A'), (5,'A')]
b = [(1,'B'), (2,'B'), (3,'B')]
c = []
d = [(1,'D'), (2,'D'), (3,'D'), (4,'D')]
可能这个模式误导了我,而真实数据没有相同的模式。
这种情况,请忽略我的回答。
否则,考虑到 OP 对我评论的回答:
starting point is several separate lists
我提出这个解决方案:
- 使用源列表构建嵌套列表;
- 根据起点旋转列表
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]
然后,比如旋转3次开始D
:
tmp = [a, b, c, d]
tmp = rotate(tmp, 3)
tmp = transpose(tmp)
tmp = flatten(tmp)
tmp
#=> [(1, 'D'), (1, 'A'), (1, 'B'), (2, 'D'), (2, 'A'), (2, 'B'), (3, 'D'), (3, 'A'), (3, 'B'), (4, 'D'), (4, 'A'), (5, 'A')]