对列表中的元素子列表进行排序,其余部分保持不变

时间:2016-11-22 08:37:30

标签: python python-2.7 list sorting key

假设我有一个排序的字符串列表,如:

['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

现在我想基于B的尾随数值进行排序 - 所以我有:

['A', 'B' , 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

一种可能的算法是散列像regex = re.compile(ur'(B)(\d*))这样的正则表达式,查找第一个和最后一个B的索引,对列表进行切片,使用正则表达式的第二个组对切片进行排序,然后插入排序的切片。然而,这似乎太麻烦了。有没有办法编写一个关键函数,如果它与正则表达式不匹配,那么“将项目保留在原位” 对匹配的项目(子列表)进行排序?

注意:上面的只是一个例子;我不一定知道模式(或者我也可能想要对C进行排序,或者任何具有尾随数字的字符串)。理想情况下,我正在寻找一种解决一般问题的方法,即只对与给定标准匹配的子序列进行排序(或者失败,只有满足给定前缀的特定标准后跟一串数字的那些)。

15 个答案:

答案 0 :(得分:6)

在简单的情况下,您只想按字母顺序对尾随数字进行排序,并按字母顺序对非数字前缀进行排序,您需要一个键功能,将每个项目拆分为非数字和数字组件,如下所示:

'AB123' -> ['AB', 123]
'CD'    -> ['CD']
'456'   -> ['', 456]
  

注意: 在最后一种情况下,空字符串''在CPython 2.x中不是必需的,因为整数在字符串之前排序 - 但这是一个实现细节,而不是语言的保证,在Python 3.x中是必要的,因为字符串和整数根本无法进行比较。

您可以使用列表推导和re.split()

构建此类关键功能
import re

def trailing_digits(x):
   return [
       int(g) if g.isdigit() else g
       for g in re.split(r'(\d+)$', x)
   ]

这是在行动:

>>> s1 = ['11', '2', 'A', 'B', 'B1', 'B11', 'B2', 'B21', 'C', 'C11', 'C2']

>>> sorted(s1, key=trailing_digits)
['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11']

一旦你添加限制条件,只有具有特定前缀或前缀的字符串的尾随数字按数字排序,事情就会变得复杂一些。

以下函数构建并返回满足要求的键函数:

def prefixed_digits(*prefixes):
    disjunction = '|'.join('^' + re.escape(p) for p in prefixes)
    pattern = re.compile(r'(?<=%s)(\d+)$' % disjunction)
    def key(x):
        return [
            int(g) if g.isdigit() else g
            for g in re.split(pattern, x)
        ]
    return key

这里的主要区别是创建了预编译的正则表达式(包含从提供的前缀或前缀构造的lookbehind),并返回使用该正则表达式的键函数。

以下是一些使用示例:

>>> s2 = ['A', 'B', 'B11', 'B2', 'B21', 'C', 'C11', 'C2', 'D12', 'D2']

>>> sorted(s2, key=prefixed_digits('B'))
['A', 'B', 'B2', 'B11', 'B21', 'C', 'C11', 'C2', 'D12', 'D2']

>>> sorted(s2, key=prefixed_digits('B', 'C'))
['A', 'B', 'B2', 'B11', 'B21', 'C', 'C2', 'C11', 'D12', 'D2']

>>> sorted(s2, key=prefixed_digits('B', 'D'))
['A', 'B', 'B2', 'B11', 'B21', 'C', 'C11', 'C2', 'D2', 'D12']

如果没有参数调用,prefixed_digits()将返回一个与trailing_digits行为相同的键函数:

>>> sorted(s1, key=prefixed_digits())
['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11']

<强>注意事项:

  1. 由于Python的re模块中有关于后瞻语法的限制,多个前缀必须具有相同的长度。

  2. 在Python 2.x中,无论向prefixed_digits()提供哪些前缀,纯数字字符串都将按数字排序。在Python 3中,它们会导致异常(除非在没有参数的情况下调用,或者在key=prefixed_digits('')的特殊情况下调用 - 它将在数字上对纯数字字符串进行排序,并按字母顺序排列前缀字符串)。使用更复杂的正则表达式修复可能是可能的,但是我在大约20分钟后放弃了尝试。

答案 1 :(得分:5)

如果我理解正确,你的最终目标是对子序列进行排序, 而单独留下不属于子序列的项目。

在您的示例中,子序列被定义为以&#34; B&#34; 开头的项目。 您的示例列表恰好包含按字典顺序排列的项目, 这有点太方便了, 并且可能会分散寻找一般化解决方案的注意力。 让我们通过使用不同的示例列表来混合一点。 怎么样:

['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2']

在这里,不再订购物品(至少我试图将它们组织起来,以便它们不是),既不是以&#34; B&#34;开头的,也不是其他的。 但是,以&#34; B&#34;开头的项目仍然形成单个连续的子序列,占据单个范围1-6而不是分割范围,例如0-3和6-7。 这又可能会分散注意力,我将进一步解决这个问题。

如果我理解你的最终目标,你希望这个列表按照这样排序:

['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']

为了使这项工作,我们需要一个返回元组的键函数,例如:

  • 第一个值:
    • 如果该项目不以&#34; B&#34;开头,那么原始列表中的索引(或相同顺序的值)
    • 如果项目以&#34; B&#34;开头,那么最后一项不以&#34; B&#34;
    • 开头的索引
  • 第二个价值:
    • 如果该项目不以&#34; B&#34;开头,则省略此
    • 如果项目以&#34; B&#34;开头,则数字值

这可以像这样实现,并使用一些doctests

def order_sublist(items):
    """
    >>> order_sublist(['A', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'C1', 'C11', 'C2'])
    ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

    >>> order_sublist(['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2'])
    ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']

    """
    def key():
        ord1 = [0]

        def inner(item):
            if not item.startswith('B'):
                ord1[0] += 1
                return ord1[0],
            return ord1[0], int(item[1:] or 0)
        return inner

    return sorted(items, key=key())

在此实现中,项目按以下键排序:

[(1,), (1, 2), (1, 11), (1, 22), (1, 0), (1, 1), (1, 21), (2,), (3,), (4,), (5,)]

不以&#34; B&#34;开头的项目保持他们的顺序,这要归功于关键元组中的第一个值,以及以&#34; B&#34;开头的项目。得益于关键元组的第二个值进行排序。

这个实现包含一些值得解释的技巧:

  • key函数返回1或2个元素的元组,如前所述:非B项有一个值,B项有两个。

  • 元组的第一个值不完全是原始索引,但它足够好。第一个B项之前的值为1,所有B项使用相同的值,B之后的值每次都增加一个值。由于(1,) < (1, x) < (2,) x可以是任何内容,因此这些键会按照我们的要求进行排序。

现在进入&#34;真实&#34;技巧: - )

  • ord1 = [0]ord1[0] += 1有什么用?这是一种更改函数中非本地值的技术。如果我只使用ord1 = 0并且ord1 += 1不起作用,因为ord1是在函数外定义的原始值。如果没有global关键字,它既不可见也不可重新分配。 ord1函数内的原始inner值将遮蔽外部原始值。但是ord1是一个列表,它在inner中可见,并且其内容可以被修改。请注意,无法重新分配。如果您将ord1[0] += 1替换为ord1 = [ord1[0] + 1]会导致相同的值,则无法使用,因为左侧的ord1是局部变量,会影响{{ 1}}在外部范围内,修改其值。

  • ord1key功能的用途是什么?如果我们传递给inner的关键功能可以重复使用,我认为会很好。这个更简单的版本也适用:

    sorted

    重要的区别在于,如果您想要使用def order_sublist(items): ord1 = [0] def inner(item): if not item.startswith('B'): ord1[0] += 1 return ord1[0], return ord1[0], int(item[1:] or 0) return sorted(items, key=inner) 两次,则两个用户将共享相同的inner列表。这是可以接受的,因为整数值ord1在使用过程中不会溢出。在这种情况下,你不会使用该函数两次,即使你可能不会有整数溢出的风险,但作为一个原则问题,使函数很好像我在最初的提案中所做的那样,将其包裹起来并且可以重复使用。 ord1[0]函数的作用只是在其范围内初始化key,定义ord1 = [0]函数,并返回inner函数。这种方式inner实际上是私有的,这要归功于关闭。每次调用ord1时,它都会返回一个具有私有新key()值的函数。

  • 最后但同样重要的是,请注意doctestsord1评论不仅仅是文档,它是可执行测试。 """ ... """行是在Python shell中执行的代码,以下行是预期的输出。如果您将此程序放在名为>>>的文件中,则可以使用script.py运行测试。当所有测试通过时,您没有输出。当测试失败时,您会得到一个很好的报告。通过演示的示例,这是验证您的程序是否有效的好方法。您可以使用空白行分隔多个测试用例,以涵盖有趣的角落案例。在此示例中,有两个测试用例,包括原始排序输入和修改后的未排序输入。

然而,正如@zero-piraeus提出了一个有趣的评论:

  

我可以看到你的解决方案依赖于python -m doctest script.py从左到右扫描列表(这是合理的 - 我无法想象TimSort将会很快被替换或彻底改变 - 但是Python AFAIK无法保证,并且有些排序算法不能像那样工作)。

我试图自我批评,并怀疑从左到右的扫描是否合理。 但我认为是。 毕竟,排序真的是基于键, 不是实际值。 我认为Python很可能是这样的:

  1. 使用sorted()获取键值列表,从左到右访问值。
  2. [key(value) for value in input]包含原始项目的键列表
  3. 在压缩列表中应用任何排序算法,按照zip的第一个值比较项目,并交换项目
  4. 最后,使用zip
  5. 返回已排序的项目

    构建键值列表时, 可以在多个线程上工作, 让我们说两个,第一个线程填充前半部分,第二个线程并行填充后半部分。 那会弄乱return [t[1] for t in zipped]伎俩。 但我怀疑它是否做了这种优化, 因为它似乎有点矫枉过正。

    但要消除任何怀疑的阴影, 我们可以自己遵循这个替代实施策略, 虽然解决方案变得更加冗长:

    ord1[0] += 1

    请注意,感谢doctests, 我们有一种简单的方法来验证替代实现是否仍然像以前一样工作。

    如果您想要此示例列表,该怎么办?

    def order_sublist(items):
        """
        >>> order_sublist(['A', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'C1', 'C11', 'C2'])
        ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
    
        >>> order_sublist(['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2'])
        ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']
    
        """
        ord1 = 0
        zipped = []
        for item in items:
            if not item.startswith('B'):
                ord1 += 1
            zipped.append((ord1, item))
    
        def key(item):
            if not item[1].startswith('B'):
                return item[0],
            return item[0], int(item[1][1:] or 0)
    
        return [v for _, v in sorted(zipped, key=key)]
    

    要像这样排序:

    ['X', 'B', 'B1', 'B11', 'B2', 'B22', 'C', 'Q1', 'C11', 'C2', 'B21']
    

    即,以&#34; B&#34;开头的项目按数值排序, 即使他们没有形成连续的子序列

    使用神奇的按键功能无法实现。 但肯定有可能,还有更多的腿部工作。 你可以:

    1. 创建一个列表,其中包含以&#34; B&#34;
    2. 开头的项目的原始索引
    3. 创建一个列表,其中的项目以&#34; B&#34;开头。并以任何你喜欢的方式对其进行排序
    4. 在原始索引处回写已排序列表的内容
    5. 如果您需要最后一次实施方面的帮助,请与我们联系。

答案 2 :(得分:4)

大多数答案都集中在B,而我需要更一般的解决方案,如上所述。这是一个:

def _order_by_number(items):
    regex = re.compile(u'(.*?)(\d*)$') # pass a regex in for generality
    keys = {k: regex.match(k) for k in items}
    keys = {k: (v.groups()[0], int(v.groups()[1] or 0)) 
            for k, v in keys.iteritems()}
    items.sort(key=keys.__getitem__)

我仍在寻找一个神奇的钥匙,但这会留下东西

答案 3 :(得分:2)

您可以使用natsort模块:

>>> from natsort import natsorted
>>> 
>>> a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
>>> natsorted(a)
['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

答案 4 :(得分:1)

如果要排序的元素在列表中彼此相邻:

您可以在cmp - 函数中使用sorted()而不是key

s1=['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

def compare(a,b):
    if (a[0],b[0])==('B','B'): #change to whichever condition you'd like
        inta=int(a[1:] or 0)
        intb=int(b[1:] or 0)
        return cmp(inta,intb)  #change to whichever mode of comparison you'd like
    else:
        return 0               #if one of a, b doesn't fulfill the condition, do nothing

sorted(s1,cmp=compare)

这假设比较器具有传递性,对于更一般的情况则不然。这也比使用key慢得多,但优点是它可以考虑上下文(在很小程度上)。

如果要排序的元素在列表中彼此相邻:

您可以通过检查列表中的每个其他元素来概括比较类型排序算法,而不仅仅是邻居:

s1=['11', '2', 'A', 'B', 'B11', 'B21', 'B1', 'B2', 'C', 'C11', 'C2', 'B09','C8','B19']

def cond1(a):           #change this to whichever condition you'd like
    return a[0]=='B'

def comparison(a,b):    #change this to whichever type of comparison you'd like to make
    inta=int(a[1:] or 0)
    intb=int(b[1:] or 0)
    return cmp(inta,intb)

def n2CompareSort(alist,condition,comparison):
    for i in xrange(len(alist)):
        for j in xrange(i):
            if condition(alist[i]) and condition(alist[j]):
                if comparison(alist[i],alist[j])==-1:
                    alist[i], alist[j] = alist[j], alist[i]  #in-place swap

n2CompareSort(s1,cond1,comparison)

我认为这不是一个麻烦,而是制作一个单独的列表/元组,但它是“就地”并且留下不符合我们条件的元素不受影响。

答案 5 :(得分:0)

您可以使用以下按键功能。如果有数字,它将返回(letter, number)形式的元组,如果没有数字,则返回(letter,)形式的元组。这有效('A',) < ('A', 1)

import re

a = ['A', 'B' ,'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

regex = re.compile(r'(\d+)')

def order(e):
    num = regex.findall(e)
    if num:
        num = int(num[0])
        return e[0], num
    return e,

print(sorted(a, key=order))
>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

答案 6 :(得分:0)

如果我清楚地理解您的问题,那么您正尝试按两个属性对数组进行排序; 字母和尾随的&#39;

你可以做一些像

这样的事情
data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
data.sort(key=lambda elem: (elem[0], int(elem[1:]))

但是因为这会为没有数字跟踪它们的元素抛出异常,我们可以继续只做一个函数(我们不应该使用lambda 反正!)

def sortKey(elem):
     try:
             attribute = (elem[0], int(elem[1:]))
     except:
             attribute = (elem[0], 0)

     return attribute

使用此排序键功能,我们可以通过

对元素进行排序
data.sort(key=sortKey)

此外,您可以继续调整sortKey函数,以便在需要时优先使用某些字母。

答案 7 :(得分:0)

要准确回答您的描述,您可以这样做:

l = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2', 'D']


def custom_sort(data, c):
    s = next(i for i, x in enumerate(data) if x.startswith(c))
    e = next((i for i, x in enumerate(data) if not x.startswith(c) and i > s), -1)
    return data[:s] + sorted(data[s:e], key=lambda d: int(d[1:] or -1)) + data[e:]


print(custom_sort(l, "B"))

如果你是一个完整的类型,你可以简单地这样做(因为@Mike JS Choi回答但更简单):

output = sorted(l, key=lambda elem: (elem[0], int(elem[1:] or -1)))

答案 8 :(得分:0)

您可以使用ord()转换为例如B11&#39;数值:

cells = ['B11', 'C1', 'A', 'B1', 'B2', 'B21', 'B22', 'C11', 'C2', 'B']
conv_cells = []

## Transform expression in numerical value.
for x, cell in enumerate(cells):
    val = ord(cell[0]) * (ord(cell[0]) - 65) ## Add weight to ensure respect order.
    if len(cell) > 1:
        val += int(cell[1:])
    conv_cells.append((val, x)) ## List of tuple (num_val, index).

## Display result.
for x in sorted(conv_cells):
    print(str(cells[x[1]]) + ' - ' + str(x[0]))

答案 9 :(得分:0)

如果您希望使用不同子组的不同规则进行排序,则可以使用元组作为排序键。在这种情况下,项目将被逐层分组和排序:首先是第一个元组项目,然后是每个子组中的第二个元组项目,依此类推。这允许我们在不同的子组中具有不同的排序规则。唯一限制 - 每个组内的项目应具有可比性。例如,您不能在同一子组中包含intstr类型的键,但您可以将它们放在不同的子组中。

让我们尝试将它应用于任务。我们将为B元素准备元素类型(strint)的元组,并为所有其他元素准备带有(strstr)元组的元组。

def sorter(elem):
    letter, num = elem[0], elem[1:]
    if letter == 'B':
        return letter, int(num or 0)  # hack - if we've got `''` as num, replace it with `0`
    else:
        return letter, num

data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

sorted(data, key=sorter)
# returns
['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

<强>更新

如果您喜欢一行:

data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

sorted(data, key=lambda elem: (elem[0],  int(elem[1:] or 0) if elem[0]=='B' else elem[:1]
# result
['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

无论如何,这些关键功能非常简单,因此您可以根据实际需要采用它们。

答案 10 :(得分:0)

import numpy as np

def sort_with_prefix(list, prefix):
    alist = np.array(list)
    ix = np.where([l.startswith(prefix) for l in list])

    alist[ix] =  [prefix + str(n or '')
             for n in np.sort([int(l.split(prefix)[-1] or 0) 
                              for l in alist[ix]])]
    return alist.tolist()

例如:

l = ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

print(sort_with_prefix(l, 'B'))
>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

答案 11 :(得分:0)

只使用,并且前提是序列已经排序&#39;:

import re

s = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']

def subgroup_ordinate(element):
    # Split the sequence element values into groups and ordinal values.
    # use a simple regex and int() in this case
    m = re.search('(B)(.+)', element)  
    if m:
        subgroup = m.group(1)
        ordinate = int(m.group(2))
    else:
        subgroup = element
        ordinate = None
    return (subgroup, ordinate)

print sorted(s, key=subgroup_ordinate)

['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

subgroup_ordinate()函数执行两项操作:识别要排序的组,并确定组内的序号。此示例使用正则表达式,但该函数可能是任意复杂的。例如,我们可以将其更改为ur'(B|C)(.+)'并对B和C序列进行排序。

['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

仔细阅读赏金问题我注意到要求对某些价值观进行了分类,同时留下了其他值......#34;&#39;。将比较函数定义为对于不在子组中的元素返回0将使这些元素保留在序列中的位置。

s2 = ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'A', 'C', 'C1', 'C2', 'C11']

def compare((_a,a),(_b,b)):
    return 0 if a is None or b is None else cmp(a,b)

print sorted(s, compare, subgroup_ordinate)

['X', 'B', 'B1', 'B2', 'B11', 'B21', 'A', 'C', 'C1', 'C2', 'C11']

答案 12 :(得分:0)

def compound_sort(input_list, natural_sort_prefixes=()):
    padding = '{:0>%s}' % len(max(input_list, key=len))
    return sorted(
        input_list, 
        key = lambda li: \
            ''.join(
                [li for c in '_' if not li.startswith(natural_sort_prefixes)] or 
                [c for c in li if not c.isdigit()] + \
                [c for c in padding.format(li) if c.isdigit()]
            )
        )

此排序方法收到:

  • input_list:要排序的list
  • natural_sort_prefixesstringtuple string s。

natural_sort_prefixes定位的列表项目将自然地排序。与这些前缀不匹配的项目将按字典顺序进行排序。

此方法假定列表项的结构为一个或多个非数字字符,后跟一个或多个数字。

它应该比使用正则表达式的解决方案更高效,并且不依赖于外部库。

您可以像以下一样使用它:

print compound_sort(['A', 'B' , 'B11', 'B1', 'B2', 'C11', 'C2'], natural_sort_prefixes=("A","B"))

# ['A', 'B', 'B1', 'B2', 'B11', 'C11', 'C2']

答案 13 :(得分:0)

import re
from collections import OrderedDict

a = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
dict = OrderedDict()

def get_str(item):
  _str = list(map(str, re.findall(r"[A-Za-z]", item)))
  return _str

def get_digit(item):
  _digit = list(map(int, re.findall(r"\d+", item)))
  return _digit

for item in a:
  _str = get_str(item)
  dict[_str[0]] = sorted([get_digit(dig) for dig in a if _str[0] in dig])

nested_result = [[("{0}{1}".format(k,v[0]) if v else k) for v in dict[k]] for k in dict.keys()]
print (nested_result)
# >>> [['A'], ['B', 'B1', 'B2', 'B11', 'B21', 'B22'], ['C', 'C1', 'C2', 'C11']]

result = []
for k in dict.keys():
  for v in dict[k]:
    result.append("{0}{1}".format(k,v[0]) if v else k)
print (result)
# >>> ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']

答案 14 :(得分:0)

如果要在保留其他元素的同时对元素的任意子集进行排序,则在原始列表上设计视图会很有用。 一般来说,视图的概念就像是原始列表上的镜头,但修改它会操纵底层原始列表。 考虑一下这个助手类:

class SubList:
    def __init__(self, items, predicate):
        self.items = items
        self.indexes = [i for i in range(len(items)) if predicate(items[i])]

    @property
    def values(self):
        return [self.items[i] for i in self.indexes]

    def sort(self, key):
        for i, v in zip(self.indexes, sorted(self.values, key=key)):
            self.items[i] = v

构造函数将原始列表保存在self.items中,将原始索引保存在self.indexes中,由predicate确定。在您的示例中,predicate函数可以是:

def predicate(item):
    return item.startswith('B')

然后,values属性是原始列表上的镜头, 返回原始索引从原始列表中选取的值列表。

最后,sort函数使用self.values进行排序, 然后修改原始列表。

考虑使用doctests进行演示:

def demo(values):
    """
    >>> demo(['X', 'b3', 'a', 'b1', 'b2'])
    ['X', 'b1', 'a', 'b2', 'b3']

    """
    def predicate(item):
        return item.startswith('b')
    sub = SubList(values, predicate)

    def key(item):
        return int(item[1:])
    sub.sort(key)

    return values

请注意SubList仅用作操作输入values的工具。在sub.sort调用之后,values被修改,包含由predicate函数选择的排序元素,并根据key函数进行排序,并且所有其他元素都不会移动。

将此SubList帮助程序与适当的predicatekey函数一起使用, 您可以对列表中任意选择的元素进行排序。