Свой tree-фильтр на C++: убрал 20к дублирующихся файлов из вывода одной командой
Привет, хабр! История началась банально: скачал датасет для нейронки по Computer Vision. Описание обещало 50k изображений .png с разметкой в .txt. Скачал — разочарование.
Полез искать .txt файлы с описанием объектов на .png. tree показал 20 тысяч дублирующихся objects.txt, labels.txt подряд в каждом втором каталоге. Экран кончился на 300-й строке, структуру не видно.
Проблема в деталях
text$ tree dataset/
├── images/
│ ├── img001.png
│ └── img002.png
├── labels/ │ ├── objects.txt <- 20k раз подряд
│ ├── labels.txt <- 20k раз подряд │ └── ...
└── annotations/
└── objects.txt <- ещё 10k$ tree dataset/ ├── images/ │ ├── img001.png │ └── img002.png ├── labels/ │ ├── objects.txt <- 20k раз подряд │ ├── labels.txt <- 20k раз подряд │ └── ... └── annotations/ └── objects.txt <- ещё 10k
Командная строка умерла — не помещается 20k файлов. grep *.txt ломает отступы tree. Нужен фильтр, который покажет структуру + только первое упоминание каждого типа файла.
Решение: 40 строк C++
cpp#include <cstdio>
#include <iostream>
#include <regex>
#include <string>
std::regex wildcardToRegex(const std::string& mask) {
std::string regexStr = "^";
for (char c : mask) {
if (c == '*') regexStr += ".*";
else if (c == '?') regexStr += ".";
else if (c == '.' || c == '+' || c == '(' || c == ')') {
regexStr += '\\'; regexStr += c;
} else regexStr += c;
}
return std::regex(regexStr + "$");
}
int main(int argc, char* argv[]) {
std::string mask = argv[1];
std::regex pattern = wildcardToRegex(mask);
std::cout << "Маска: " << mask << "\n\n";
std::string s;
bool skip = false;
while (std::getline(std::cin, s)) {
if (std::regex_match(s, pattern)) {
if (skip) continue; // пропускаем дубли подряд
skip = true;
} else {
skip = false;
}
printf("%s\n", s.c_str()); // ANSI цвета tree живы!
}
}Компиляция (секунда):
bashg++ -O2 -o treefilter treefilter.cpp
sudo cp treefilter /usr/local/bin/g++ -O2 -o treefilter treefilter.cpp sudo cp treefilter /usr/local/bin/
Как пользоваться для датасетов
text# Только разметка
tree dataset/ | treefilter "*.txt"
# Изображения
tree dataset/ | treefilter "*.png|*.jpg"
# JSON аннотации
tree dataset/ | treefilter "*.json"
# Глубже в поддиректории
tree -L 3 dataset/ | treefilter "labels/*.txt"Результат — магия!
textdataset/
├── images/
│ ├── *.png <- только первое упоминание
├── labels/ │ ├── objects.txt <- только первое
│ └── labels.txt <- только первое
└── annotations/
└── *.json <- читаемо!dataset/ ├── images/ │ ├── .png <- только первое упоминание ├── labels/ │ ├── objects.txt <- только первое │ └── labels.txt <- только первое └── annotations/ └── .json <- читаемо!
Зачем это круто для ML/Data Science
Читаемая структура датасетов на 100GB+
0.1 сек на гигабайт вывода (C++ speed™)
Универсальный — работает с
find,ls, любым выводомANSI-цвета tree сохраняются
Бонус: умная дедупликация
Раскомментируй в коде — ловит похожие имена с конца строки:
cppsize_t others = 0;
for (size_t i = 0; i < std::min(s.size(), last_line.size()); ++i) {
if (s[s.size() - i - 1] == last_line[last_line.size() - i - 1]) ++others;
}
if (others * 2 >= s.size()) continue; // file1.txt, file2.txt = дублиПланы на развитие
Потихоньку добавлю фичи:
Счетчик совпадающих строк:
objects.txt [x15000]Группировка по директориям
Интерактивный режим —
+/-для папокКонфиг масок для типичных датасетов (COCO, YOLO, VOC)
JSON/YAML вывод структуры
Студенческий лайфхак №47: датасет не помещается на экране? Пиши фильтр за вечер. Теперь tree dataset/ -C | treefilter "*.txt" — и вся структура как на ладони! 🚀 https://github.com/aleksejbiriulin/tree_group