Python 对列表中元素的子列表进行排序,保留其余元素

Python 对列表中元素的子列表进行排序,保留其余元素,python,python-2.7,list,sorting,key,Python,Python 2.7,List,Sorting,Key,假设我有一个已排序的字符串列表,如中所示: ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] 现在我想根据Bs的尾随数值进行排序-因此我有: ['A', 'B' , 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2'] 一种可能的算法是对正则表达式进行散列,如regex=re.compile(ur'(B)(\d*),找到第一个和最后一个B,对列表

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

['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
现在我想根据
B
s的尾随数值进行排序-因此我有:

['A', 'B' , 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
一种可能的算法是对正则表达式进行散列,如
regex=re.compile(ur'(B)(\d*)
,找到第一个和最后一个
B
,对列表进行切片,使用正则表达式的第二组对切片进行排序,然后插入排序后的切片。然而,这似乎太麻烦了。有没有一种方法可以编写一个键函数,如果它与正则表达式不匹配,并且只有 对匹配的项(子列表)进行排序


注意:以上只是一个例子;我不一定知道模式(或者我可能还想对C或任何有尾随数字的字符串进行排序)。理想情况下,我正在寻找一种解决一般问题的方法,即只对符合给定条件的子序列进行排序(如果不符合,则只对符合特定条件的子序列进行排序,即给定前缀后跟一组数字)。

您可以使用以下键函数。如果有数字,它将返回格式为
(字母,数字)
的元组;如果没有数字,它将返回格式为
(字母,)
的元组。这是因为
('A',)<('A',1)


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

你可以这样做

data = ['A', 'B' , 'B1', 'B11', 'B2', 'B21', 'B22', 'C', 'C1', 'C11', 'C2']
data.sort(key=lambda elem: (elem[0], int(elem[1:]))
但是,由于这会对没有数字尾随的元素引发异常,我们可以继续,只创建一个函数(无论如何,我们不应该使用lambda!)

有了这个排序键函数,我们就可以按

data.sort(key=sortKey)

此外,如果您愿意,您可以继续调整sortKey功能,以优先考虑某些字母。

要准确回答您描述的内容,可以执行以下操作:

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回答的,但更简单):


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

'AB123' -> ['AB', 123]
'CD'    -> ['CD']
'456'   -> ['', 456]
注意:在最后一种情况下,空字符串
'
在CPython 2.x中不是严格必需的,因为整数在字符串之前排序–但这是一个实现细节,而不是语言的保证,在Python 3.x中它是必需的,因为字符串和整数根本无法比较

您可以使用列表来构建这样一个键函数,并且:

这就是它的作用:

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

一旦您添加了限制,即只有具有特定前缀的字符串才可以对其尾部数字进行数字排序,事情就会变得更复杂一些

以下函数生成并返回满足要求的关键函数:

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

如果在没有参数的情况下调用,
prefixed_digits()
将返回一个键函数,该键函数的行为与
training_digits
相同:

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

  • 由于Python的
    re
    模块中关于lookbhehind语法的限制,多个前缀必须具有相同的长度

  • 在Python2.x中,纯粹是数字的字符串将按数字排序,而不管为
    前缀\u digits()
    提供了哪些前缀。在Python3中,它们将导致一个异常(除非在没有参数的情况下调用,或者在
    key=prefixed_digits(“”)
    的特殊情况下,它将按数字和字母顺序对纯数字字符串进行排序)。用一个复杂得多的正则表达式修复这个问题是可能的,但我在大约20分钟后放弃了尝试

  • 您可以使用ord()将示例“B11”转换为数值:

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

    大多数答案都集中在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__)
    

    我仍然在寻找一个神奇的键,但是它可以让东西保持原位

    如果你想用不同的规则对不同的子组进行排序,你可以使用元组作为排序键。在这种情况下,项目将按层分组和排序:首先按第一个元组项目,然后在每个子组中按第二个元组项目,依此类推。这允许我们在不同的子组中有不同的排序规则。唯一的限额项目应在各组内具有可比性。例如,同一子组中不能有
    int
    str
    类型键,但可以在不同的子组中有它们

    让我们尝试将其应用于任务。我们将为B元素准备具有元素类型(
    str
    int
    )的元组,为所有其他元素准备具有(
    str
    str
    )的元组

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

    总之,这些关键函数非常简单,因此您可以根据实际需要采用它们。

    如果我理解正确,您的最终目标是对子序列进行排序, 而不考虑不属于子序列的项目

    在您的示例中,子序列定义为以“B”开头的项目。
    您的示例列表碰巧包含按字典顺序排列的项, 有点太方便了, 并且可能会分散寻找广义解的注意力。 让我们通过使用不同的示例列表来稍微混合一些内容。 那么:

    ['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2']
    
    在这里,物品不再被订购(至少我试着把它们组织起来,这样它们就不会被订购),无论是以“B”开头的还是其他的。 然而,以“B”开头的项目仍然形成单个连续子序列,占据单个范围1-6,而不是分割范围,例如0-3和6-7。 这可能会再次让人分心,我将进一步讨论这一方面

    如果我正确理解你的最终目标,你会喜欢的
    >>> 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']
    
    >>> sorted(s1, key=prefixed_digits())
    ['2', '11', 'A', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'C2', 'C11']
    
    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]))
    
    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__)
    
    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']
    
    ['X', 'B2', 'B11', 'B22', 'B', 'B1', 'B21', 'C', 'Q1', 'C11', 'C2']
    
    ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'Q1', 'C11', 'C2']
    
    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,)]
    
    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)
    
    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']
    
    ['X', 'B', 'B1', 'B2', 'B11', 'B21', 'C', 'Q1', 'C11', 'C2', 'B22']
    
    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)
    
    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)
    
    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']
    
    >>> 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']
    
    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']
    
    ['A', 'B', 'B1', 'B2', 'B11', 'B21', 'B22', 'C', 'C1', 'C2', 'C11']
    
    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']
    
    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()]
                )
            )
    
    print compound_sort(['A', 'B' , 'B11', 'B1', 'B2', 'C11', 'C2'], natural_sort_prefixes=("A","B"))
    
    # ['A', 'B', 'B1', 'B2', 'B11', 'C11', 'C2']
    
    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']
    
    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
    
    def predicate(item):
        return item.startswith('B')
    
    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