假设我有一个排序的字符串列表,如:
['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进行排序,或者任何具有尾随数字的字符串)。理想情况下,我正在寻找一种解决一般问题的方法,即只对与给定标准匹配的子序列进行排序(或者失败,只有满足给定前缀的特定标准后跟一串数字的那些)。
答案 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']
<强>注意事项:强>
由于Python的re
模块中有关于后瞻语法的限制,多个前缀必须具有相同的长度。
在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']
为了使这项工作,我们需要一个返回元组的键函数,例如:
这可以像这样实现,并使用一些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}}在外部范围内,不修改其值。
ord1
和key
功能的用途是什么?如果我们传递给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()
值的函数。
最后但同样重要的是,请注意doctests
:ord1
评论不仅仅是文档,它是可执行测试。 """ ... """
行是在Python shell中执行的代码,以下行是预期的输出。如果您将此程序放在名为>>>
的文件中,则可以使用script.py
运行测试。当所有测试通过时,您没有输出。当测试失败时,您会得到一个很好的报告。通过演示的示例,这是验证您的程序是否有效的好方法。您可以使用空白行分隔多个测试用例,以涵盖有趣的角落案例。在此示例中,有两个测试用例,包括原始排序输入和修改后的未排序输入。
然而,正如@zero-piraeus提出了一个有趣的评论:
我可以看到你的解决方案依赖于
python -m doctest script.py
从左到右扫描列表(这是合理的 - 我无法想象TimSort将会很快被替换或彻底改变 - 但是Python AFAIK无法保证,并且有些排序算法不能像那样工作)。
我试图自我批评,并怀疑从左到右的扫描是否合理。 但我认为是。 毕竟,排序真的是基于键, 不是实际值。 我认为Python很可能是这样的:
sorted()
获取键值列表,从左到右访问值。[key(value) for value in input]
包含原始项目的键列表zip
构建键值列表时,
可以在多个线程上工作,
让我们说两个,第一个线程填充前半部分,第二个线程并行填充后半部分。
那会弄乱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;开头的项目按数值排序, 即使他们没有形成连续的子序列?
使用神奇的按键功能无法实现。 但肯定有可能,还有更多的腿部工作。 你可以:
如果您需要最后一次实施方面的帮助,请与我们联系。
答案 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)
如果您希望使用不同子组的不同规则进行排序,则可以使用元组作为排序键。在这种情况下,项目将被逐层分组和排序:首先是第一个元组项目,然后是每个子组中的第二个元组项目,依此类推。这允许我们在不同的子组中具有不同的排序规则。唯一限制 - 每个组内的项目应具有可比性。例如,您不能在同一子组中包含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']
无论如何,这些关键功能非常简单,因此您可以根据实际需要采用它们。
答案 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_prefixes
:string
或tuple
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
帮助程序与适当的predicate
和key
函数一起使用,
您可以对列表中任意选择的元素进行排序。