Как эффективно составить список последовательности изображений? Сравнение числовых последовательностей в Python

У меня есть каталог из 9 изображений:

image_0001, image_0002, image_0003
image_0010, image_0011
image_0011-1, image_0011-2, image_0011-3
image_9999

Я хотел бы иметь возможность перечислить их эффективным способом, например так (4 записи для 9 изображений):

(image_000[1-3], image_00[10-11], image_0011-[1-3], image_9999)

Есть ли способ в python вернуть каталог изображений коротким/четким способом (без перечисления каждого файла)?

Итак, возможно, что-то вроде этого:

перечислить все изображения, отсортировать по номерам, создать список (считая каждое изображение последовательно с самого начала). Если изображение отсутствует (создайте новый список), продолжайте, пока исходный список файлов не будет завершен. Теперь у меня должно быть несколько списков, содержащих непрерывные последовательности.

Я пытаюсь упростить чтение/описание списка чисел. Если бы у меня была последовательность из 1000 последовательных файлов, она могла бы быть четко указана как файл [0001-1000], а не как файл ['0001', '0002', '0003' и т. д...]

Изменить 1 (на основе предложения): при наличии плоского списка, как бы вы получили шаблоны глобусов?

Edit2 Я пытаюсь разбить проблему на более мелкие части. Вот пример части решения: data1 работает, data2 возвращает 0010 как 64, data3 (реальные данные) не работает:

# Find runs of consecutive numbers using groupby.  The key to the solution
# is differencing with a range so that consecutive numbers all appear in
# same group.
from operator import itemgetter
from itertools import *

data1=[01,02,03,10,11,100,9999]
data2=[0001,0002,0003,0010,0011,0100,9999]
data3=['image_0001','image_0002','image_0003','image_0010','image_0011','image_0011-2','image_0011-3','image_0100','image_9999']

list1 = []
for k, g in groupby(enumerate(data1), lambda (i,x):i-x):
    list1.append(map(itemgetter(1), g))
print 'data1'
print list1

list2 = []
for k, g in groupby(enumerate(data2), lambda (i,x):i-x):
    list2.append(map(itemgetter(1), g))
print '\ndata2'
print list2

возвращает:

data1
[[1, 2, 3], [10, 11], [100], [9999]]

data2
[[1, 2, 3], [8, 9], [64], [9999]]

person user178686    schedule 13.10.2010    source источник
comment
Почему image_00[10-11], а не image_001[0-1]?   -  person eumiro    schedule 13.10.2010
comment
image_00[10-11] или image_001[0-1], да, наверное, на один символ меньше   -  person user178686    schedule 13.10.2010
comment
Цинично: да, есть способ. Я сомневаюсь (но могу ошибаться), что для этого есть какая-то библиотечная функция. Напишите какой-нибудь код, спросите что-нибудь более конкретное (например, как я могу сравнить строки на предмет сходства) после того, как вы уже сделали os.listdir(path) и т. д.   -  person Nick T    schedule 13.10.2010
comment
Было бы здорово, если бы была библиотека для этого. Я предполагаю, что программное обеспечение для редактирования видео и компоновки использует последовательности изображений как единый исходный файл, поэтому эта проблема была решена раньше.   -  person user178686    schedule 13.10.2010
comment
@Nick T, я обновил свой вопрос выше, пытаясь более подробно разобрать проблему. Благодарность   -  person user178686    schedule 13.10.2010
comment
Я согласен, что для этого, вероятно, нет библиотеки. Также еще один момент, который следует учитывать, - это то, что вы подразумеваете под эффективностью. Вы хотите полностью свести к минимуму количество записей, необходимых для описания каталога, или просто уменьшить его на определенный коэффициент? Является ли удобочитаемость результирующего списка приоритетом?   -  person waffle paradox    schedule 13.10.2010
comment
Парадокс @waffle @martineau - ну, «уменьшить на определенный коэффициент» и сделать его понятным для человека было бы приемлемой целью   -  person user178686    schedule 14.10.2010
comment
Что именно вы подразумеваете под эффективным способом?   -  person martineau    schedule 14.10.2010
comment
Это эффективно? image_['0001', '0002', '0003', '0010', '0011', '0011-1', '0011-2', '0011-3', '9999']   -  person martineau    schedule 14.10.2010
comment
@martineau перечисляет каждый файл как image_ ['0001', '0002', '0003'] - это то, чего я пытаюсь избежать. Я пытаюсь упростить чтение последовательных последовательностей в краткой форме.   -  person user178686    schedule 14.10.2010
comment
На самом деле это была одна запись для всех девяти файлов. Хорошо, тогда '0011' находится в последовательности ['0010','0011'] или ['0011', '0011-1', '0011-2', '0011-3']?   -  person martineau    schedule 14.10.2010
comment
Кроме того, если в именах файлов есть символы тире '-', не будет ли это конфликтовать с их использованием для указания диапазонов?   -  person martineau    schedule 14.10.2010
comment
Я не думаю, что у Python есть способ увеличить такое имя, то есть перейти от «image_0001» к «image_0002». Поэтому я сомневаюсь, что есть встроенное решение проблемы.   -  person Mark Ransom    schedule 14.10.2010
comment
Начальные нули в константах data2 превращают их в восьмеричные - 0100 равно 64 в базе 10. И я не вижу никакого способа распространить ваше groupby решение на строки.   -  person Mark Ransom    schedule 15.10.2010
comment
Очевидно, у Фредерика Хамиди зрение лучше, чем у меня. Проверьте его обновленный ответ.   -  person Mark Ransom    schedule 15.10.2010
comment
@Mark Ransom - Спасибо за вашу помощь. Ваше предложение «проверить каждый элемент, чтобы увидеть, равен ли он предыдущему элементу + 1» помогло мне взглянуть на проблемы по-другому и разбить их на более мелкие фрагменты. Я действительно многому научился у вас, ребята. Еще раз спасибо всем за вашу помощь (для меня это была очень интересная проблема)   -  person user178686    schedule 15.10.2010


Ответы (3)


Вот рабочая реализация того, чего вы хотите достичь, используя код, который вы добавили в качестве отправной точки:

#!/usr/bin/env python

import itertools
import re

# This algorithm only works if DATA is sorted.
DATA = ["image_0001", "image_0002", "image_0003",
        "image_0010", "image_0011",
        "image_0011-1", "image_0011-2", "image_0011-3",
        "image_0100", "image_9999"]

def extract_number(name):
    # Match the last number in the name and return it as a string,
    # including leading zeroes (that's important for formatting below).
    return re.findall(r"\d+$", name)[0]

def collapse_group(group):
    if len(group) == 1:
        return group[0][1]  # Unique names collapse to themselves.
    first = extract_number(group[0][1])  # Fetch range
    last = extract_number(group[-1][1])  # of this group.
    # Cheap way to compute the string length of the upper bound,
    # discarding leading zeroes.
    length = len(str(int(last)))
    # Now we have the length of the variable part of the names,
    # the rest is only formatting.
    return "%s[%s-%s]" % (group[0][1][:-length],
        first[-length:], last[-length:])

groups = [collapse_group(tuple(group)) \
    for key, group in itertools.groupby(enumerate(DATA),
        lambda(index, name): index - int(extract_number(name)))]

print groups

Это печатает ['image_000[1-3]', 'image_00[10-11]', 'image_0011-[1-3]', 'image_0100', 'image_9999'], что вам и нужно.

ИСТОРИЯ: сначала я ответил на вопрос наоборот, как указал @Mark Ransom ниже. Ради истории, мой первоначальный ответ был:

Вы ищете glob. Пытаться:

import glob
images = glob.glob("image_[0-9]*")

Или, используя ваш пример:

images = [glob.glob(pattern) for pattern in ("image_000[1-3]*",
    "image_00[10-11]*", "image_0011-[1-3]*", "image_9999*")]
images = [image for seq in images for image in seq]  # flatten the list
person Frédéric Hamidi    schedule 13.10.2010
comment
Я думаю, что это решение отличается от того, что задает вопрос. Учитывая сглаженный список, как бы вы получили шаблоны глобусов? - person Mark Ransom; 14.10.2010
comment
@ Марк, ты прав, я неправильно понял вопрос (и его заголовок действительно должен быть « Учитывая сплющенный список, как бы вы получили шаблоны глобусов?»). Я думаю, что я немного посплю, прежде чем попробовать еще раз :] - person Frédéric Hamidi; 14.10.2010
comment
@Фредерик @Марк. Спасибо вам обоим за вашу помощь. Я действительно наслаждаюсь этой проблемой. Я учусь на ходу. - person user178686; 14.10.2010
comment
@Frédéric, основываясь на количестве голосов, которые вы получили, я бы сказал, что вы не единственный, кто неправильно понял. - person Mark Ransom; 14.10.2010
comment
@Frédéric - СПАСИБО - это именно то, чего я хотел добиться. Это действительно четкий и читаемый результат. Я поэкспериментирую с этим и вернусь к вам. :о) Идеально - person user178686; 15.10.2010
comment
Добро пожаловать, ваш вопрос был довольно интересной задачей для решения :) - person Frédéric Hamidi; 15.10.2010
comment
@FrédéricHamidi список «Понимание» не читается, можете ли вы разбить его, чтобы он был читабельным. - person Ciasto piekarz; 13.07.2017

Итак, я нашел ваш вопрос увлекательной головоломкой. Я оставил вам, как «сжимать» числовые диапазоны (помечены как TODO), поскольку существуют разные способы сделать это в зависимости от того, как вам нравится форматирование, и хотите ли вы минимальное количество элементов или минимальную строку. длина описания.

В этом решении используется простое регулярное выражение (цифровые строки) для классификации каждой строки на две группы: статические и переменные. После того, как данные классифицированы, я использую groupby для сбора статических данных в самые длинные совпадающие группы для достижения суммарного эффекта. Я смешиваю целочисленные индикаторы индекса с результатом (в matchGrouper), чтобы я мог повторно выбрать различные части из всех элементов (в распаковке).

import re
import glob
from itertools import groupby
from operator import itemgetter

def classifyGroups(iterable, reObj=re.compile('\d+')):
    """Yields successive match lists, where each item in the list is either
    static text content, or a list of matching values.

     * `iterable` is a list of strings, such as glob('images/*')
     * `reObj` is a compiled regular expression that describes the
            variable section of the iterable you want to match and classify
    """
    def classify(text, pos=0):
        """Use a regular expression object to split the text into match and non-match sections"""
        r = []
        for m in reObj.finditer(text, pos):
            m0 = m.start()
            r.append((False, text[pos:m0]))
            pos = m.end()
            r.append((True, text[m0:pos]))
        r.append((False, text[pos:]))
        return r

    def matchGrouper(each):
        """Returns index of matches or origional text for non-matches"""
        return [(i if t else v) for i,(t,v) in enumerate(each)]

    def unpack(k,matches):
        """If the key is an integer, unpack the value array from matches"""
        if isinstance(k, int):
            k = [m[k][1] for m in matches]
        return k

    # classify each item into matches
    matchLists = (classify(t) for t in iterable)

    # group the matches by their static content
    for key, matches in groupby(matchLists, matchGrouper):
        matches = list(matches)
        # Yield a list of content matches.  Each entry is either text
        # from static content, or a list of matches
        yield [unpack(k, matches) for k in key]

Наконец, мы добавляем достаточно логики для красивой печати вывода и запускаем пример.

def makeResultPretty(res):
    """Formats data somewhat like the question"""
    r = []
    for e in res:
        if isinstance(e, list):
            # TODO: collapse and simplify ranges as desired here
            if len(set(e))<=1:
                # it's a list of the same element
                e = e[0]
            else: 
                # prettify the list
                e = '['+' '.join(e)+']'
        r.append(e)
    return ''.join(r)

fnList = sorted(glob.glob('images/*'))
re_digits = re.compile(r'\d+')
for res in classifyGroups(fnList, re_digits):
    print makeResultPretty(res)

Мой каталог изображений был создан из вашего примера. Вы можете заменить fnList следующим списком для тестирования:

fnList = [
 'images/image_0001.jpg',
 'images/image_0002.jpg',
 'images/image_0003.jpg',
 'images/image_0010.jpg',
 'images/image_0011-1.jpg',
 'images/image_0011-2.jpg',
 'images/image_0011-3.jpg',
 'images/image_0011.jpg',
 'images/image_9999.jpg']

И когда я запускаю этот каталог, мой вывод выглядит так:

StackOverflow/3926936% python classify.py
images/image_[0001 0002 0003 0010].jpg
images/image_0011-[1 2 3].jpg
images/image_[0011 9999].jpg
person Shane Holloway    schedule 13.10.2010
comment
Спасибо, я очень не уверен в том, что вы делаете. Не могли бы вы добавить несколько комментариев, которые помогут мне связать пример image_0002, image_0003 и т. д. Если бы вы могли добавить список тестов, я мог бы выполнить шаг за шагом и запустить ваше решение. - person user178686; 14.10.2010
comment
СПАСИБО за ваше время, Шейн. Я продолжу изучать ваше решение itertools; Думаю, я могу многому у него научиться. Edit2 в исходном сообщении был результатом поиска в Google/изучения вашего хорошо прокомментированного решения. - person user178686; 15.10.2010

def ranges(sorted_list):
    first = None
    for x in sorted_list:
        if first is None:
            first = last = x
        elif x == increment(last):
            last = x
        else:
            yield first, last
            first = last = x
    if first is not None:
        yield first, last

Функция increment оставлена ​​читателю в качестве упражнения.

Изменить: вот пример того, как он будет использоваться с целыми числами вместо строк в качестве входных данных.

def increment(x): return x+1

list(ranges([1,2,3,4,6,7,8,10]))
[(1, 4), (6, 8), (10, 10)]

Для каждого непрерывного диапазона на входе вы получаете пару, указывающую начало и конец диапазона. Если элемент не является частью диапазона, начальное и конечное значения идентичны.

person Mark Ransom    schedule 13.10.2010
comment
спасибо, я действительно не понимаю. Итак, предположим, что я отсортировал файлы в список: sorted_list = ['image_0001','image_0002','image_0003','image_0010', 'image_0011'] ... можете ли вы объяснить, что вы мне показали. Для каждого элемента в sorted_list (если вы увеличиваете его, проверьте, существует ли он в остальной части списка)??? - person user178686; 14.10.2010
comment
@user, этот алгоритм проверяет каждый элемент, чтобы увидеть, должен ли он быть включен в текущую последовательность, проверяя, равен ли он last+1. Если это так, то текущая последовательность расширяется; в противном случае последовательность выдается как кортеж, и текущая последовательность сбрасывается на новый элемент. Если бы мы могли гарантировать, что ввод не пуст, это можно было бы даже упростить. - person Mark Ransom; 14.10.2010
comment
Спасибо. Итак, я понимаю, что он проверяет каждый элемент, чтобы увидеть, равен ли он предыдущему элементу+1. Я не понимаю, иначе последовательность выдается как кортеж... - person user178686; 14.10.2010