基于具有不完整值的另一个较短列表对列表进行排序

时间:2018-04-18 10:04:04

标签: python list sorting python-3.6

我有一个文件路径列表,在阅读和处理文件之前,我需要以特定方式订购。 特定方式由较小的列表定义,该列表仅包含一些文件名称,但不包含所有文件。 presorted_list中未列出的所有其他文件路径都必须保持其先前的顺序。

实施例

some_list = ['path/to/bar_foo.csv',
             'path/to/foo_baz.csv',
             'path/to/foo_bar(ignore_this).csv',
             'path/to/foo(ignore_this).csv',
             'other/path/to/foo_baz.csv']

presorted_list = ['foo_baz', 'foo']

expected_list = ['path/to/foo_baz.csv',
                 'other/path/to/foo_baz.csv',
                 'path/to/foo(ignore_this).csv',
                 'path/to/bar_foo.csv',
                 'path/to/foo_bar(ignore_this).csv']

我发现了一些相关帖子:

但据我所知,问题和答案总是依赖于两个相同长度的列表,这些列表我没有(导致ValueError: 'bar_foo' is not in list之类的错误)或者需要的预先排序列表包含我无法提供的所有可能值。

我的想法

我提出了一个似乎有用的解决方案,但我不确定这是否是解决问题的好方法:

import os
import re

EXCPECTED_LIST = ['path/to/foo_baz.csv',
                  'other/path/to/foo_baz.csv',
                  'path/to/foo(ignore_this).csv',
                  'path/to/bar_foo.csv',
                  'path/to/foo_bar(ignore_this).csv']

PRESORTED_LIST = ["foo_baz", "foo"]


def sort_function(item, len_list):
    # strip path and unwanted parts
    filename = re.sub(r"[\(\[].*?[\)\]]", "", os.path.basename(item)).split('.')[0]

    if filename in PRESORTED_LIST:
        return PRESORTED_LIST.index(filename)
    return len_list


def main():
    some_list = ['path/to/bar_foo.csv',
                 'path/to/foo_baz.csv',
                 'path/to/foo_bar(ignore_this).csv',
                 'path/to/foo(ignore_this).csv',
                 'other/path/to/foo_baz.csv',]
    list_length = len(some_list)
    sorted_list = sorted(some_list, key=lambda x: sort_function(x, list_length))

    assert sorted_list == EXCPECTED_LIST


if __name__ == "__main__":
    main()

是否还有其他(更短,更pythonic)方法来解决这个问题?

4 个答案:

答案 0 :(得分:1)

以下是我认为我会这样做的方式:

import re
from collections import OrderedDict
from itertools import chain

some_list = ['path/to/bar_foo.csv',
             'path/to/foo_baz.csv',
             'path/to/foo_bar(ignore_this).csv',
             'path/to/foo(ignore_this).csv',
             'other/path/to/foo_baz.csv']
presorted_list = ['foo_baz', 'foo']
expected_list = ['path/to/foo_baz.csv',
                 'other/path/to/foo_baz.csv',
                 'path/to/foo(ignore_this).csv',
                 'path/to/bar_foo.csv',
                 'path/to/foo_bar(ignore_this).csv']

def my_sort(lst, presorted_list):
    rgx = re.compile(r"^(.*/)?([^/(.]*)(\(.*\))?(\.[^.]*)?$")
    d = OrderedDict((n, []) for n in presorted_list)
    d[None] = []
    for p in some_list:
        m = rgx.match(p)
        n = m.group(2) if m else None
        if n not in d:
            n = None
        d[n].append(p)
    return list(chain.from_iterable(d.values()))

print(my_sort(some_list, presorted_list) == expected_list)
# True

答案 1 :(得分:1)

一个简单的实现是在排序之前向线添加一些标记。所以不需要特定的订购。如果所有文件名都遵循您提供的模式,也可以避免使用正则表达式:

for n,file1 in enumerate(presorted_list):
    for m,file2 in enumerate(some_list):
        if '/'+file1+'.' in file2 or '/'+file1+'(' in file2:
            some_list[m] = "%03d%03d:%s" % (n, m, file2)
some_list.sort()
some_list = [file.split(':',1)[-1] for file in some_list]
print(some_list)

结果:

['path/to/foo_baz.csv',
 'other/path/to/foo_baz.csv',
 'path/to/foo(ignore_this).csv',
 'path/to/bar_foo.csv',
 'path/to/foo_bar(ignore_this).csv']

答案 2 :(得分:0)

让我想一想。这是一个独特的问题,我将尝试提出解决方案

only_sorted_elements = filter(lambda x:x.rpartition("/")[-1].partition(".")[0] in presorted_list , some_list)
only_sorted_elements.sort(key = lambda x:presorted_list.index(x.rpartition("/")[-1].partition(".")[0]))
expected_list = []
count = 0
for ind, each_element in enumerate(some_list):
    if each_element not in presorted_list:
       expected_list.append(each_element)
    else:
       expected_list[ind].append(only_sorted_elements[count])
       count += 1

希望这能解决您的问题。 我首先只过滤那些在presorted_list中的元素, 然后我按照presorted_list

中的顺序对这些元素进行排序

然后我遍历列表并相应追加。

编辑:

将文件名中的索引参数更改为精确文件名。 这将保留那些不在预先排序列表中的文件的原始索引。

已编辑: 新编辑的代码将更改参数并首先提供排序结果,然后再排序。

some_list = ['path/to/bar_foo.csv',
             'path/to/foo_baz.csv',
             'path/to/foo_bar(ignore_this).csv',
             'path/to/foo(ignore_this).csv',
             'other/path/to/foo_baz.csv']
presorted_list = ['foo_baz', 'foo']

only_sorted_elements = filter(lambda x:x.rpartition("/")[-1].partition("(")[0].partition(".")[0] in presorted_list , some_list)
unsorted_all = filter(lambda x:x.rpartition("/")[-1].partition("(")[0].partition(".")[0] not in presorted_list , some_list)
only_sorted_elements.sort(key = lambda x:presorted_list.index(x.rpartition("/")[-1].partition("(")[0].partition(".")[0]))
expected_list = only_sorted_elements + unsorted_all
print expected_list

结果:

['path/to/foo_baz.csv', 
'other/path/to/foo_baz.csv', 
'path/to/foo(ignore_this).csv', 
'path/to/bar_foo.csv', 
'path/to/foo_bar(ignore_this).csv']

答案 3 :(得分:0)

由于python的排序已经稳定,您只需要为排序键提供粗略的分组。

鉴于您的排序要求的具体情况,使用函数可以做得更好。例如:

def presort(presorted):        
    def sortkey(name):
        filename = name.split("/")[-1].split(".")[0].split("(")[0]
        if filename in presorted:
              return presorted.index(filename)
        return len(presorted)
    return sortkey

sorted_list = sorted(some_list,key=presort(['foo_baz', 'foo']))

为了保持流程的通用性和易用性,presorted_list应作为参数提供,sort键函数应使用它来生成分组键。这是通过返回一个捕获预先排序列表参数的函数(sortkey)来实现的。

此sortkey()函数返回presorted_list中文件名的索引或超出不匹配文件名的数字。因此,如果presorted_list中有2个名称,它们将在排序键值0和1下对相应的文件进行分组。所有其他文件将在组2中。

用于确定应该在presorted_list中找到文件名的哪个部分的条件有点不清楚,所以我只讨论了左括号的具体情况。在sortkey()函数中,您可以添加更复杂的解析来满足您的需求。