簡體   English   中英

如何在 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'開始。

編輯:

抱歉,我沒有在集合列表中添加所需的 output 順序以使其清楚。 假設我會指定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:

經過進一步的搜索,我猜lambdacmp(x,y)會完成這項工作(參見示例),但它似乎不再存在於 Python3 中。 所以,那么可能是operator.itemgetter()operator.methodcaller()的東西,我仍然不知道如何使用,因為我錯過了很好的例子......

您可以使用 dict 到 map 一個字母到其正確的 position:

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...]

唷,這非常耗時,但我想我現在有一個解決方案。 至少結果似乎具有所需的順序。 模塊functools提供cmp_to_key來替換cmp() ,后者顯然已在 Python3 中刪除。 至少這是我在這里找到的。

如果有“本機”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')]

使用自定義排序鍵 function:

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 添加到小於或等於指定的最后一個字母的任何字母的索引。

我在示例數據中看到了一個模式:

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 對我的評論的回答:

起點是幾個單獨的列表

我提出這個解決方案:

  • 使用源列表構建嵌套列表;
  • 根據起點將列表旋轉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')]

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM