使用另一个列表对Python列表中的字符串进行排序

6

假设我有以下列表:

List1=['Name1','Name3','Color1','Size2','Color3','Color2','Name2','Size1', 'ID']
List2=['ID','Color1','Color2','Size1','Size2','Name1','Name2']

每个列表都将有名为“ID”变量的元素,然后是另外三个类别(名称、颜色和尺寸),其中每个类别中的元素数量不确定。
我想使用以下“排序列表”对这些变量进行排序,而不知道每个类别中将有多少个元素:
SortList=['ID','Name','Size','Color']

我可以得到期望的输出(见下文),虽然我想象中还有更好/更符合Python风格的方法。

>>> def SortMyList(MyList,SortList):       
...     SortedList=[]       
...     for SortItem in SortList:
...         SortItemList=[]
...         for Item in MyList:
...             ItemWithoutNum="".join([char for char in Item if char.isalpha()])  
...             if SortItem==ItemWithoutNum:
...                 SortItemList.append(Item)
...         if len(SortItemList)>1:
...             SortItemList=[SortItem+str(I) for I in range(1,len(SortItemList)+1)]
...         for SortedItem in SortItemList:
...             SortedList.append(SortedItem)
...     return SortedList
... 
>>> 
>>> SortMyList(List1, SortList)
['ID', 'Name1', 'Name2', 'Name3', 'Size1', 'Size2', 'Color1', 'Color2', 'Color3']
>>> SortMyList(List2, SortList)
['ID', 'Name1', 'Name2', 'Size1', 'Size2', 'Color1', 'Color2']
>>> 

您有什么建议可以改进我的方法或代码吗?


我有点不清楚这里... "Name" 类别的 do 项是否都以子字符串 "Name" 开头? - mgilson
你是否可以有像 Name11 这样的条目,你希望它在 Name10 之后、Name12 之前? - DSM
这可能属于 代码审查 - 我的初步建议是修复您的大小写 - StudlyCase 单词适用于类定义,而不是函数或变量。pep8pyflakes 是两个 linters,它们将帮助指出您代码中至少的样式问题。尽管您可能需要对它们的建议持保留态度。grain of salt - Wayne Werner
@ mgilson - 是的,同一类别中的所有项目都将以相同的子字符串开头。是的,@DSM,条目可以超过1个数字,如果是这样,Name11应该放在Name10和Name12之间。 - AJG519
4个回答

5
您可以使用自定义键函数对列表进行排序,该函数返回一个2元组,用于主要排序和次要排序。
主要排序是按照您的“标签”顺序(首先是ID,然后是名称等)进行的。 次要排序是按其后面的数字值进行的。
tags = ['ID','Name','Size','Color']
sort_order = { tag : i for i,tag in enumerate(tags) }

def elem_key(x):
    for tag in tags:
        if x.startswith(tag):
            suffix = x[len(tag) : ]
            return ( sort_order[tag],
                     int(suffix) if suffix else None )
    raise ValueError("element %s is not prefixed by a known tag. order is not defined" % x)

list1.sort(key = elem_key)

你可以解释一下 assert 0 这行代码吗? - 8one6
@8one6,好的。请在断言行中查看更多细节。 - shx2
它确保这个自定义键函数在遇到意外输入时主动出错(而不是被动失败)吗? - 8one6
无论如何都会失败(尝试将元组与键函数可能返回的其他内容进行比较,例如None),但是这种方式可以明确地报告错误条件。 - shx2

2
只要你知道List2只包含以sortList中的内容开头的字符串,这个方法就能正常工作。
List2=['ID','Color4','Color2','Size1','Size2','Name2','Name1']
sortList=['ID','Name','Size','Color']
def sort_fun(x):
    for i, thing in enumerate(sortList):
        if x.startswith(thing):
            return (i, x[len(thing):])

print sorted(List2, key=sort_fun)

1
你只需要提供适当的密钥:

List1.sort( key = lambda x : ('INSC'.index(x[0]),x[-1]))
# ['ID', 'Name1', 'Name2', 'Name3', 'Size1', 'Size2', 'Color1', 'Color2', 'Color3']

元素将按第一个字母排序,然后根据最后一位数字(如果存在)排序。这里可以正常工作,因为所有第一个字母都不同,并且数字最多只有一位。
编辑:对于多位数字,可以使用更加难懂的解决方案。
List1.sort( key =lambda x : ('INSC'.index(x[0]),int("0"+"".join(re.findall('\d+',x)))))
 # ['ID', 'Name1', 'Name2', 'Name10', 'Size1', 'Size2', 'Color1', 'Color2', 'Color3']

2
怎么样 ['Color10', 'Color2']。结果可能对于大于9的数字是错误的。 - user2683246

0

在这种情况下,有没有比简单的正则表达式更容易从字符串中提取数据的方法?

import re

def keygen(sort_list):
    return lambda elem: (
        sort_list.index(re.findall(r'^[a-zA-Z]+', elem)[0]),
        re.findall(r'\d+$', elem)
    )

使用方法:

   SortList = ['ID', 'Name', 'Size', 'Color']
   List1 = ['Name1', 'Name3', 'Color1', 'Size2', 'Color3', 'Color2','Name2', 'Size1', 'ID']
   List2 = ['ID', 'Color1', 'Color2', 'Size1', 'Size2', 'Name1', 'Name2']
   sorted(List1, key=keygen(SortList))
=> ['ID', 'Name1', 'Name2', 'Name3', 'Size1', 'Size2', 'Color1', 'Color2', 'Color3']
   sorted(List2, key=keygen(SortList))
=> ['ID', 'Name1', 'Name2', 'Size1', 'Size2', 'Color1', 'Color2']

解释:

^[a-zA-Z]+匹配字符串开头的字母部分,\d$匹配字符串末尾的数字部分。

keygen返回一个使用字符串作为输入,并返回两个元素的元组:
第一个元素是列表中字母部分的位置(如果列表中没有该元素则引发ValueError异常),
第二个元素是包含字符串末尾数字部分的单元素列表,如果字符串不以数字结尾则为空列表。

一些可能的改进:

  • sort_list.index的调用复杂度为O(n),对于列表中的每个元素都会调用一次;可以用O(1)的字典查找来加快排序速度(我没有这样做是为了保持简单),
  • 数字部分可以转换为实际的整数 (1 < 2 < 10,但'1' < '10' < '2'

应用这些改进后:

import re

def keygen(sort_list):
    index = {(word, index) for index, word in enumerate(sort_slist)}
    return lambda elem: (
        index[re.findall(r'^[a-zA-Z]+', elem)[0]],
        [int(s) for s in re.findall(r'\d+$', elem)]
    )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接