Найти цифры в именах файлов и сопоставить их с другими
во-первых, я быстро опишу свою мотивацию для этого и актуальную проблему:
Я постоянно имею дело с большими пакетами файлов, и, более конкретно, мне приходится переименовывать их в соответствии со следующим правилом:
Все они могут содержать слова и цифры, но только один набор цифр увеличивается, а не "константа". Мне нужно извлечь эти и только эти цифры и переименовать файлы соответственно. Например:
Foo_1_Bar_2015.jpg
Foo_2_Bar_2015.jpg
Foo_03_Bar_2015.jpg
Foo_4_Bar_2015.jpg
будет переименован:
1.jpg
2.jpg
3.jpg or 03.jpg (The leading zero can stay or go)
4.jpg
Итак, мы начинаем с Вектора с std::wstring
объекты для всех имен файлов в указанном каталоге. Я призываю вас прекратить чтение на 3 минуты и подумать о том, как подойти к этому, прежде чем я продолжу свои попытки и вопросы. Я не хочу, чтобы мои идеи подталкивали вас в том или ином направлении, а я всегда считал свежие идеи лучшими.
теперь, вот два способа, которые я могу придумать:
1) старый стиль c работа со строками и сравнения:
На мой взгляд, это влечет за собой разбор каждого имени файла и запоминание каждой позиции последовательности цифр и длины. Это легко хранится в векторе или что-не для каждого файла. Это хорошо работает (в основном использует поиск строк с увеличением смещений):
while((offset = filename_.find_first_of(L"0123456789", offset)) != filename.npos)
{
size = filename.find_first_not_of(L"0123456789", offset) - offset;
digit_locations_vec.emplace_back(offset, size);
offset += size;
}
то, что у меня есть после этого, - это вектор (местоположение, размер) пар для всех цифр в имени файла, константа (используя определение в мотивации) или нет.
После этого, хаос, как вам нужно перекрестно ссылаться на строки и выяснить, какие цифры являются теми, которые необходимо извлечь. Это будет расти экспоненциально с количеством файлов (которое имеет тенденцию быть огромным), не упомянутым, умноженным на количество последовательностей цифр в каждой строке. Кроме того, не очень читаемый, ремонтопригодный или элегантный. Бесполезный.
2) Регулярные Выражения
если и было когда-либо использование для regex, то это. Создать объект regex из первого файла и попробуй сопоставить это с тем, что будет дальше. Успех? Мгновенная возможность извлечения необходимого числа. Неудача? Добавьте оскорбительное имя файла в качестве нового объекта regex и попытайтесь сопоставить его с двумя существующими regex. Промыть и повторить. Регулярное выражение будет выглядеть так:
Foo_(d+)_Bar_(d+).jpg
или создайте регулярное выражение для каждой последовательности цифр отдельно:
Foo_(d+)_Bar_2015.jpg
Foo_1_Bar_(d+).jpg
остальное-жмых. Просто продолжайте соответствовать, как вы идете, и в лучшем случае, это может потребовать только один проход! Вопрос есть...
что мне нужно знать:
1) Можете ли вы придумать какой-либо другой превосходный способ достичь этого? Я уже несколько дней бьюсь головой о стену.
2) хотя стоимость манипуляции строками и векторного построениядеструкции может быть существенной в первом методе, возможно, она бледнеет по сравнению со стоимостью объектов регулярных выражений. Второй метод, в худшем случае: столько объектов регулярных выражений, сколько файлов. Будет ли это катастрофическим с потенциально тысячами файлы?
3) второй метод может быть скорректирован для одной из двух возможностей: несколько std::regex
объектные конструкции, многие regex_match
звонки или наоборот. Что дороже, построение объекта regex или попытка сопоставить с ним строку?
2 ответов
для меня (gcc4.6.2 32-битные оптимизации O3), ручная манипуляция строками была примерно в 2 раза быстрее, чем регулярные выражения. Не стоит таких затрат.
пример выполняемого полного кода (ссылка с boost_system и boost_regex или изменение include, если у вас уже есть regex в компиляторе):
#include <ctime>
#include <cctype>
#include <algorithm>
#include <string>
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;
#include <boost/regex.hpp>
using namespace boost;
/*
Foo_1_Bar_2015.jpg
Foo_1_Bar_2016.jpg
Foo_2_Bar_2016.jpg
Foo_2_Bar_2015.jpg
...
*/
vector<string> generateNames(int lenPerYear, int yearStart, int years);
/*
Foo_1_Bar_2015.jpg -> 1_2015.jpg
Foo_7_Bar_2016.jpg -> 7_2016.jpg
*/
void rename_method_string(const vector<string> & names, vector<string> & renamed);
void rename_method_regex(const vector<string> & names, vector<string> & renamed);
typedef void rename_method_t(const vector<string> & names, vector<string> & renamed);
void testMethod(const vector<string> & names, const string & description, rename_method_t method);
int main()
{
vector<string> names = generateNames(10000, 2014, 100);
cout << "names.size() = " << names.size() << '\n';
cout << '\n';
testMethod(names, "method 1 - string manipulation: ", rename_method_string);
cout << '\n';
testMethod(names, "method 2 - regular expressions: ", rename_method_regex);
return 0;
}
void testMethod(const vector<string> & names, const string & description, rename_method_t method)
{
vector<string> renamed(names.size());
clock_t timeStart = clock();
method(names, renamed);
clock_t timeEnd = clock();
cout << "renamed examples:\n";
for (int i = 0; i < 10 && i < names.size(); ++i)
cout << names[i] << " -> " << renamed[i] << '\n';
cout << description << 1000 * (timeEnd - timeStart) / CLOCKS_PER_SEC << " ms\n";
}
vector<string> generateNames(int lenPerYear, int yearStart, int years)
{
vector<string> result;
for (int year = yearStart, yearEnd = yearStart + years; year < yearEnd; ++year)
{
for (int i = 0; i < lenPerYear; ++i)
{
ostringstream oss;
oss << "Foo_" << i << "_Bar_" << year << ".jpg";
result.push_back(oss.str());
}
}
return result;
}
template<typename T>
bool equal_safe(T itShort, T itShortEnd, T itLong, T itLongEnd)
{
if (itLongEnd - itLong < itShortEnd - itShort)
return false;
return equal(itShort, itShortEnd, itLong);
}
void rename_method_string(const vector<string> & names, vector<string> & renamed)
{
//manually: "Foo_(\d+)_Bar_(\d+).jpg" -> _.jpg
const string foo = "Foo_", bar = "_Bar_", jpg = ".jpg";
for (int i = 0; i < names.size(); ++i)
{
const string & name = names[i];
//starts with foo?
if (!equal_safe(foo.begin(), foo.end(), name.begin(), name.end()))
{
renamed[i] = "ERROR no foo";
continue;
}
//extract number
auto it = name.begin() + foo.size();
for (; it != name.end() && isdigit(*it); ++it) {}
string str_num1(name.begin() + foo.size(), it);
//continues with bar?
if (!equal_safe(bar.begin(), bar.end(), it, name.end()))
{
renamed[i] = "ERROR no bar";
continue;
}
//extract number
it += bar.size();
auto itStart = it;
for (; it != name.end() && isdigit(*it); ++it) {}
string str_num2(itStart, it);
//check *.jpg
if (!equal_safe(jpg.begin(), jpg.end(), it, name.end()))
{
renamed[i] = "ERROR no .jpg";
continue;
}
renamed[i] = str_num1 + "_" + str_num2 + ".jpg";
}
}
void rename_method_regex(const vector<string> & names, vector<string> & renamed)
{
regex searching("Foo_(\d+)_Bar_(\d+).jpg");
smatch found;
for (int i = 0; i < names.size(); ++i)
{
if (regex_search(names[i], found, searching))
{
if (3 != found.size())
renamed[i] = "ERROR weird match";
else
renamed[i] = found[1].str() + "_" + found[2].str() + ".jpg";
}
else renamed[i] = "ERROR no match";
}
}
он производит выход для меня:
names.size() = 1000000
renamed examples:
Foo_0_Bar_2014.jpg -> 0_2014.jpg
Foo_1_Bar_2014.jpg -> 1_2014.jpg
Foo_2_Bar_2014.jpg -> 2_2014.jpg
Foo_3_Bar_2014.jpg -> 3_2014.jpg
Foo_4_Bar_2014.jpg -> 4_2014.jpg
Foo_5_Bar_2014.jpg -> 5_2014.jpg
Foo_6_Bar_2014.jpg -> 6_2014.jpg
Foo_7_Bar_2014.jpg -> 7_2014.jpg
Foo_8_Bar_2014.jpg -> 8_2014.jpg
Foo_9_Bar_2014.jpg -> 9_2014.jpg
method 1 - string manipulation: 421 ms
renamed examples:
Foo_0_Bar_2014.jpg -> 0_2014.jpg
Foo_1_Bar_2014.jpg -> 1_2014.jpg
Foo_2_Bar_2014.jpg -> 2_2014.jpg
Foo_3_Bar_2014.jpg -> 3_2014.jpg
Foo_4_Bar_2014.jpg -> 4_2014.jpg
Foo_5_Bar_2014.jpg -> 5_2014.jpg
Foo_6_Bar_2014.jpg -> 6_2014.jpg
Foo_7_Bar_2014.jpg -> 7_2014.jpg
Foo_8_Bar_2014.jpg -> 8_2014.jpg
Foo_9_Bar_2014.jpg -> 9_2014.jpg
method 2 - regular expressions: 796 ms
кроме того, я думаю, что это совершенно бессмысленно, потому что фактический ввод-вывод (получение имени файла, переименование файла) будет намного медленнее, чем любая манипуляция строками процессора, в вашем примере. Поэтому чтобы ответить на ваши вопросы:
- Я не вижу никакого высшего способа, I / O-это то, что медленно, не беспокойтесь о превосходстве
- regex object не был дорогим по моему опыту, в 2x slow-down vs manual method, это постоянное замедление и незначительное, по сравнению с тем, сколько работы он экономит
- сколько объектов std:: regex для скольких вызовов regex_match? Зависит от количества звонков зависит от: больше матчей есть, больше стоит создать конкретный объект std::regex. Однако это будет очень зависеть от библиотеки. Если есть много совпадающих вызовов, создайте отдельный, если вы не уверены, не беспокойтесь.
почему бы вам не использовать split для разделения строки между буквами и цифрами:
Regex.Split(fileName, "(?<=\D)(?=\d)|(?<=\d)(?=\D)");
затем получите любой индекс, который вам нужен для чисел, возможно, используя предложение Where, чтобы найти те, которые увеличиваются в значении, в то время как другие индексы совпадают, тогда вы можете использовать .Last (), чтобы получить расширение.