Reputation: 3686
The following code processes a list of file paths and should return only the file names (without extension) of XML files. Currently I got to this:
const filteredFiles = files
.map(f => f.match(/.*\/(.*)\.xml/)) // map to regex match with capture
.filter(v => v) // non-matches returned null and will be filtered out here
.map(m => m[1]) // map out the regex capture
I find this code quite cumbersome. Is there no way to combine the matching and filtering in a more "efficient" way? And by "efficient" I mean code-readable-efficient and not time-efficient as the input array holds 100 values at most but most of the time between 10 and 20.
Upvotes: 1
Views: 2157
Reputation: 8163
This doesn't solve your need of mapping and filtering out the non matching values in one shot... but it makes one step easier by using the optional chaining operator ?.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
I also slightly changed the regex to allow the filename with no path specifed.
const files = [
'./path/to/filename_01.xml',
'non_matching_value',
'./path/to/filename_02.xml',
'./path/to/filename_03.xml',
'./path/to/filename_04.xml',
'filename_05.xml',
];
//Approach using filter over map to ignore non matching array items
//-------------------------------------------------------------------------------
const filteredFiles = files
.map(filename => filename.match(/^(.*\/)?(.*)\.xml/)?.[2])
.filter(filename => filename);
console.log(filteredFiles);
//Approach using flatMap as suggested by another user answering the same question
//-------------------------------------------------------------------------------
const filteredFiles2 = files.flatMap((f)=>{
//here you are forced to put the match result in a dedicated variable..
const match = f.match(/^(.*\/)?(.*)\.xml/);
//because you need to use it both on the condition and the positive outcome
return ( match ) ? [match[2]] : [];
});
console.log(filteredFiles2);
//Approach using flatMap as suggested by another user answering the same question
//AND using the null coealeshing operator to return empty array in case of non matching string
//-------------------------------------------------------------------------------
const filteredFiles3 = files.flatMap(f => f.match(/^(.*\/)?(.*)\.xml/)?.[2] ?? []);
console.log(filteredFiles3);
Upvotes: 2
Reputation: 10262
As I added in comment, you could use reduce
method of array to achieve this in single iteration
Example
const regex = /\/(.*)\.xml$/;
const filteredFiles = files.reduce((r, f) => {
const value = f.match(regex);
if (value?.[1]) {
return [...r, value[1]];//If matches found then return previous result + new value
}
return r; // If not matches found then return previous result
}, []);
Upvotes: 2
Reputation: 164744
Map and filter, otherwise known as reduce
const rx = /\/(.*)\.xml$/;
const filteredFiles = files.reduce((arr, f) => {
const match = f.match(rx);
return match ? [...arr, match[1]] : arr;
}, []);
Upvotes: 2
Reputation: 8011
You can (ab)use flat map:
const filteredFiles = files.flatMap((f)=>{
let match = f.match('...');
if (match) {
return [match[1]]
} else {
return []
}
})
Not sure if it's actually better than the original though.
Upvotes: 1