Я пытаюсь научиться функциональному программированию и не могу понять это. В моем минимальном рабочем примере у меня есть список словарей, каждый словарь содержит имя файла и размер файла. Я хочу создать новый словарь, который группирует файлы по размеру (это часть более крупного алгоритма, который находит дублированные файлы).
Вот "традиционный" подход к этому, мутирование данных:
const groupFilesBySize = (allFileData) => {
const filesSortedBySize = {};
for (const fileData of allFileData) {
if (fileData.size in filesSortedBySize) {
filesSortedBySize[fileData.size].push(fileData.file);
} else {
filesSortedBySize[fileData.size] = [fileData.file];
}
}
return filesSortedBySize;
};
А вот моя «лучшая» попытка сделать это функциональным способом:
const groupFilesBySizeFunctional = (allFileData) =>
allFileData.reduce(
(filesSortedBySize, fileData) => ({
...filesSortedBySize,
[fileData.size]: filesSortedBySize[fileData.size]
? [...filesSortedBySize[fileData.size], fileData.file]
: [fileData.file]
}),
{}
);
Я проверил их (воспроизводимый пример ниже), и функциональная версия примерно 10000 раз медленнее . Это не шутка - это просто непригодно. Я могу себе представить, что создание нового словаря каждый раз, когда мы обрабатываем файл в reduce
, является причиной задержки.
Тем не менее, теперь я вижу две возможности: либо функциональное программирование имеет ужасную производительность, либо я не могу написать правильный функциональный код. Очевидно, что второй является правильным, я хотел бы спросить: Как правильно написать функцию groupFilesBySize
функциональным способом?
Тест : Используйте эту функцию для получения массива путей к файлам и размеров файлов:
async function walk(dir) {
let files = [];
files = await fs.readdir(dir);
const parsedFiles = await Promise.all(files.map(async (fileName) => {
const filePath = path.join(dir, fileName);
const stats = await fs.lstat(filePath);
if (stats.isSymbolicLink() || stats.size === 0) {
return null;
}
if (stats.isDirectory()) {
return walk(filePath);
} else if (stats.isFile()) {
return { file: filePath, size: stats.size };
}
}));
return parsedFiles.reduce(
(all, folderContents) => (folderContents ? all.concat(folderContents) : all),
[]
);
}
Затем сравните все с помощью:
const benchMark = async () => {
const dir = path.dirname(__filename);
const allFileData = await walk(dir);
console.log(`Total files: ${allFileData.length}`);
let start = new Date();
const result1 = groupFilesBySize(allFileData);
const time1 = new Date() - start;
start = new Date();
const result2 = groupFilesBySizeFunctional(allFileData);
const time2 = new Date() - start;
console.log('\nFINAL REPORT:')
console.log(`Are results equal? ${JSON.stringify(result1) === JSON.stringify(result2)}`);
console.log(`Non functional approach: ${time1} ms`);
console.log(`Functional approach: ${time2} ms`);
};
Чтобы получить значительные данные, я решил установить пакет узла eslint
, поэтому мне нужно сгруппировать все файлы в папке node_modules
: npm install eslint
. Выход в моей машине:
Total files: 6229
FINAL REPORT:
Are results equal? true
Non functional approach: 6 ms
Functional approach: 34557 ms