I have a list of files that I'd like to sort primarily by name, but if the names (minus hash and extension) match, I'd like to sort by extension. The extensions (and order) are as follows: .js, .js.map, .esm.js, .esm.js.map, .css, and .css.map.
The issue I'm running up against (despite having a working solution) is that this becomes very complex very quickly. It's quite hard (IMO) to follow what this is doing, so I'm hoping I'm just being silly and there's a simpler, more scalable way to go about doing this sort of thing?
I very well might be able to simplify the code below, only just got it working (assuming there's no edge cases I haven't missed), but I'm more looking for frameworks/patterns for doing sorting of this kind. Devolving into a bunch of nested if conditions (as I might do if a couple more extensions were tossed into the mix) seems not great.
Current code:
files.sort((a, b) => {
const assetName = ({ name }) => name.match(/([^.]*)/);
if (assetName(a)?.[1] === assetName(b)?.[1]) {
if (/\.css\.map$/.test(a.name) && /\.css$/.test(b.name)) return 1;
if (/\.css(\.map)?$/.test(b.name)) return -1;
if (
/(?<!\.esm)\.js$/.test(a.name) ||
(/(?<!\.esm)\.js(\.map)?$/.test(a.name) && /\.esm\.js(\.map)?/.test(b.name))
) {
return -1;
}
}
return a.name < b.name ? -1 : 1;
});
Example data in desired output order:
bundle.abcde.js
bundle.abcde.js.map
bundle.12345.esm.js
bundle.12345.esm.js.map
bundle.abc12.css
bundle.abc12.css.map
Thanks!
If you know the extension order desired, and you don't have a huge number of possible extensions, you can put them into an array and sort based off of that array.
A potential complication is the overlap of extensions. A filename that ends in esm.js also ends in .js - for the general case, how would you determine which one takes priority? It'd be good if you could unambiguously separate the extension part from the rest of the filename - for example, one approach would be to configure things such that the final part of the filenames have at least 4 characters, which can be distinguished from the extension part that has 3 or fewer.
const extensions = '.js, .js.map, .esm.js, .esm.js.map, .css, .css.map'.split(', ');
const files = `bundle.12345.esm.js
bundle.abcde.js
bundle.abc12.css
bundle.abcde.js.map
bundle.abc12.css.map
bundle.12345.esm.js.map`.split('\n');
const getExt = filename => filename.match(/(?:\.[^\.]{1,3})+$/)[0];
files.sort((a, b) =>
extensions.indexOf(getExt(a)) -
extensions.indexOf(getExt(b))
);
console.log(files);
Someone posted the following answer, but it seems to have been deleted:
const extOrder = [
{ ext: /(?<!\.esm)\.js$/, order: 1 },
{ ext: /(?<!\.esm)\.js\.map$/, order: 2 },
{ ext: /\.esm\.js$/, order: 3 },
{ ext: /\.esm\.js\.map$/, order: 4 },
{ ext: /\.css$/, order: 5 },
{ ext: /\.css\.map$/, order: 6 },
];
files.sort((a, b) => {
const assetName = ({ name }) => name.match(/([^.]*)/);
const extIndex = ({ name }) => extOrder.findIndex(e => e.ext.test(name));
if (assetName(a)?.[1] === assetName(b)?.[1]) {
return extIndex(a) - extIndex(b);
}
return a.name < b.name ? -1 : 1;
});
This was exactly the sort of thing I was looking for, but was too tunnel visioned to think of.
Related
I build on a web-scrapper, that, lets say scrap URLs from google
I get an array of URLs from google results:
const linkSelector = 'div.yuRUbf > a'
let links = await page.$$eval(linkSelector, link => {
return link.map( x => x.href)
})
the output of 'links' is something like that:
[
'https://google.com/.../antyhing'
'https://amazon.com/.../antyhing'
'https://twitter.com/.../antyhing'
]
Now I have a 'blacklist', with something like that:
[
'https://amazon.com'
]
At the moment I stuck at that point where I can compare both arrays, and remove these URLs from 'links' which are listed within my blacklist.
So I came up with the idea, to get the domain of the url within my links array - like so:
const linkList = []
for ( const link of links ) {
const url = new URL(link)
const domain = url.origin
linkList.push(domain)
}
Yes, now i got two arrays which i can compare against each other and remove the blacklisted domain, but i lost the complete url i need to work with...
for( let i = linkList.length - 1; i >= 0; i--){
for( let j=0; j < blacklist.length; j++){
if( linkList[i] === blacklist[j]){
linkList.splice(i, 1);
}
}
}
Code Snippet is part of the give answer, here:
Compare two Javascript Arrays and remove Duplicates
Any ideas how can i do this, with puppeteer and node.js?
I couldn't find an obvious dupe, so converting my comments to an answer:
.includes:
const allowedLinks = links.filter(link => !blacklist.some(e => link.includes(e)))
.startsWith:
const allowedLinks = links.filter(link => !blacklist.some(e => link.startsWith(e)))
The second version is more precise. If you want to use the URL version, this should work:
const links = [
"https://google.com/.../antyhing",
"https://amazon.com/.../antyhing",
"https://twitter.com/.../antyhing",
];
const blacklist = ["https://amazon.com"];
const allowedLinks = links.filter(link =>
!blacklist.some(black =>
black.startsWith(new URL(link).origin) // or use ===
)
);
console.log(allowedLinks);
As for Puppeteer, I doubt it matters whether you do this Node-side or browser-side, unless these arrays are enormous. On that train of thought, technically we have a quadratic algorithm here but I wouldn't worry about it unless you have many hundreds of thousands of elements and are noticing slowness. In that case, you can put the blacklisted origins into a Set data and look up each link's origin in that. The problem with this is it's a precise ===, so you'd have to build a prefix set if you need to preserve .startsWith semantics. This is likely unnecessary and out of scope for this answer, but worth mentioning briefly.
i created multiple conditions by using map, but i think there are other methods to optimize this code:
if (content.risks) {
content.risks.map((risk) => {
if (risk.coverageCode === "PUB" || risk.coverageCode === "SPPI" || risk.coverageCode === "PROD" || risk.coverageCode === "PR" || risk.coverageCode === "DO") {
risk._class = "com.generali.gip.local.hk.domain.RiskPublicLiability";
}
if (risk.coverageCode === "PD") {
risk._class = "com.generali.gip.local.hk.domain.RiskMaterialDamage";
}
if (risk.coverageCode === "BI") {
risk._class = "com.generali.gip.local.hk.domain.RiskBusinessInterruption";
}
});
}
How i can to rewrite this code by using find or indexof?
My objective in software development is to express my intents in a way that is clear and unambiguous.
For this kind of "problems", I will always reach out for the humble map. There's no extra ceremonies (i.e. if/else, switch/case, etc.). It just says "if you give me an X, I'll give you a Y".
const risk_map =
{ PUB: "com.generali.gip.local.hk.domain.RiskPublicLiability"
, SPPI: "com.generali.gip.local.hk.domain.RiskPublicLiability"
, PROD: "com.generali.gip.local.hk.domain.RiskPublicLiability"
, PR: "com.generali.gip.local.hk.domain.RiskPublicLiability"
, DO: "com.generali.gip.local.hk.domain.RiskPublicLiability"
, PD: "com.generali.gip.local.hk.domain.RiskMaterialDamage";
, BI: "com.generali.gip.local.hk.domain.RiskBusinessInterruption"
};
Regardless of your programming background, the "time to get it" is low.
It can indeed lead to duplication but I don't think it comes at the expense of readability or maintenance. (How hard would it be to replace these duplicated strings with a constant for example?)
Now how can we "query" this map? Let's build a high-order function for that:
We say "Give me a map, then give me a key, I'll get you a value":
const get_value = map => key => map[key];
get_value(risk_map)("PUB");
//=> "com.generali.gip.local.hk.domain.RiskPublicLiability"
The get_value is a high-order function. We're meant to build specialised functions out of it. Let's just do that:
const get_risk = get_value(risk_map);
get_risk("PUB");
//=> "com.generali.gip.local.hk.domain.RiskPublicLiability"
With this we can solve your "problem" quite simply:
const content_risks =
content.risks
? content.risks.map(risk => get_risk(risk.coverageCode))
: null;
I'll leave it as an exercise to combine and adapt this to fit your exact needs.
Hope this helps though.
I'm setting up a test to ensure that a faceted Solr query 'contents' are correctly displayed within a page element, using javascript.
The Solr query result, which I've named "ryanlinkstransmissionpage", is;
{ Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] }
What I would like to do is extract the 'Manual' and 'Automatic' only, so I can then test that these values are displayed on a page.
However, it is more the functionality involved in this that I cannot get my head around, as I will be using this method on other Solr query results.
To possibly complicate things, this Solr query result "ryanlinkstransmissionpage" is from a dynamic 'live' Solr, so the values may change each time it's run (so there may be more or less values within this array when it's tested on the following day for example).
I've tried a few javascript commands, but to no avail.
JSON.parse(ryanlinkstransmissionpage)
JSON.stringify(ryanlinkstransmissionpage)
Object.values(ryanlinkstransmissionpage)
Any help would be greatly appreciated. Thanks.
If possible, i highyl recommend changing the transmission field to be an object, rather than an array. That will give you far greater ability to read the data within.
Ignoring that, are you looking to extract the string values and the number values that follow them? ie. "Manual" and "12104"? Or are you simply trying to assert that the string values are present on the page?
Either way, here are two possible approaches.
const ryanlinkstransmissionpage = { Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] };
// Pull out the string values
const strngVals = ryanlinkstransmissionpage.Transmission.filter(val => typeof val === 'string');
// Pull out the string values and the numbers that follow
const strngNumVals = ryanlinkstransmissionpage.Transmission.reduce((keyVals, val, idx, srcArr) => {
if (typeof val === 'string') keyVals[val] = srcArr[idx + 1];
return keyVals;
}, {});
The reduce approach is not stable or robust to changes in data provided from this Solr query result you refer to, nor is it tested. #shrug
Javascript has a built in method called Array.prototype.find(() =>). If you just want to check if this value exists to ensure its on the page, you can simply do:
const ryanlinkstransmissionpage = { Transmission: [ 'Manual', 12104, 'Automatic', 9858 ] };
const manual = ryanlinkstransmissionpage.Transmission.find((ele) => ele === 'Manual'); // returns 'Manual'
const automatic = ryanlinkstransmissionpage.Transmission.find((ele) => ele === 'Automatic'); // returns 'Automatic'
console.log(automatic);
console.log(manual);
// or
const findInArray = (arr, toFind) => {
const result = arr.find((ele) => ele === toFind);
return !!result;
}
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'Automatic')); // true
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'HelloWorld')); // false
console.log(findInArray(ryanlinkstransmissionpage.Transmission, 'Manual')); // true
I'm trying to write a routing function that will return all possible routes from any two given locations (I'm calling them "spaces"), but I'm stuck on writing the recursive function.
My data will look something like this:
const allSpaces = [
{
id: 0,
name: 'Living Room',
connectedSpaces: [1,2]
},
{
id: 1,
name: 'Hallway A',
connectedSpaces: [0,4]
},
{
id: 2,
name: 'Hallway B',
connectedSpaces: [0,4]
},
{
id: 3,
name: 'Bedroom',
connectedSpaces: [1,2]
}
];
So, calling a getAllRoutes(0,3) method would walk all possible routes and return an array of arrays:
[
[0,1,3],
[0,2,3]
]
Keep in mind that this may not always be as simplistic of a dataset as my example (i.e., Hallway A could have an offshoot that provides an alternate route, or could re-intersect with previously visited spaces).
I'm pretty stumped. I've made several attempts at a recursive function but keep ending up with incomplete lists or infinite loops. Any help would be appreciated!
Visualizing your data
Whenever you find yourself stuck with a problem like this, it helps to think of an easy way to visualize what's happening. To get a feel for the graph you're working with, I wrote a few lines of code to visualize the graph.
Through the visualization, I noticed there's probably a small error in the data. I figured spaces 1 and 2 should be connected to 0 and 3 rather than 0 and 4. I adjusted this in the data, and added an additional space for testing.
If you like, you can check out the visualization by expanding the snippet below.
const allSpaces=[{id:0,name:"Living Room",connectedSpaces:[1,2]},{id:1,name:"Hallway A",connectedSpaces:[0,3,4]},{id:2,name:"Hallway B",connectedSpaces:[0,3]},{id:3,name:"Bedroom",connectedSpaces:[1,2]}, {id:4,name:"Master bedroom",connectedSpaces:[1]}];
const Edge = (() => {
// Do not support two way edges. Cache from and to internally:
const cache = new Map();
const Edge = (from, to) => {
const id = `${Math.min(from, to)}.${Math.max(from, to)}`;
const length = 1;
if (!cache.has(id)) {
cache.set(id, { from, to, id, length });
}
return cache.get(id);
}
return (from => to => Edge(from, to));
})();
const edges = uniques(allSpaces.reduce(
(edges, node) => edges.concat(
node.connectedSpaces.map(Edge(node.id))
), []
));
const Node = ({ id, name }) => ({ id, label: name });
const nodes = allSpaces.map(Node);
const network = new vis.Network(
document.getElementById("cvs"),
{ nodes, edges },
{}
);
function uniques(arr) { return Array.from(new Set(arr).values()); }
<link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis-network.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js"></script>
<div id="cvs" style="height: 300px"></div>
Being able to see our data, makes it easier to check if our function works! Now, you've asked to find all paths to get from A to B. Note that this the number of possible paths can very quickly increase as you add more nodes. (e.g., have a look at the Traveling Salesman Problem).
Shortest path?
If you're really looking for the shortest path, you might want to adapt the code below to use Dijkstra's Shortest Path algorithm, as SLaks suggested in their comment.
Brute force approach
But, since the example set is small, and you asked for all routes, let's brute force it:
Define an empty collection of paths
Define a start node
Add its id to the current path
For every node it links to:
Check if it's our destination; if so: return the collection with the current path added
Check if it's already in our path; if so: skip it (we don't want to move in circles)
If it's not in our path nor our destination, add it to the path and move deeper
Or, in code:
const walk = (destination, paths, path, node) => {
// Walking in circles
if (path.includes(node.id)) // <-- expensive, for large paths use a Set
return paths;
// End reached
if (node.id === destination)
return paths.concat([path.concat(node.id)]);
// Take next step recursively
return node.connectedSpaces
.reduce(
(acc, id) => walk(destination, acc, path.concat(node.id), spaceMap.get(id)),
paths
);
}
Here's a running snippet you can use to step through and see what happens:
const allSpaces=[{id:0,name:"Living Room",connectedSpaces:[1,2]},{id:1,name:"Hallway A",connectedSpaces:[0,3,4]},{id:2,name:"Hallway B",connectedSpaces:[0,3]},{id:3,name:"Bedroom",connectedSpaces:[1,2]}, {id:4,name:"Master bedroom",connectedSpaces:[1]}];
const spaceMap = new Map(allSpaces.map(s => [s.id, s]));
const walk = (destination, paths, path, node) => {
// Walking in circles
if (path.includes(node.id)) // <-- expensive, for large paths use a Set
return paths;
// End reached
if (node.id === destination)
return paths.concat([path.concat(node.id)]);
// Take next step recursively
return node.connectedSpaces
.reduce(
(acc, id) => walk(destination, acc, path.concat(node.id), spaceMap.get(id)),
paths
);
}
const calcRoute = (from, to) => {
const routes = walk(to, [], [], spaceMap.get(from));
return `
Found ${routes.length} route(s) to ${spaceMap.get(to).name}
${routes.map(r => r.map(id => spaceMap.get(id).name).join(" -> ")).join("\n")}
`;
}
console.log(calcRoute(0, 3));
console.log(calcRoute(0, 4));
i got some sort of an issue with my custom sorting. So, basically i have this array:
[ 'src/app/account/account.js',
'src/app/account/dashboard/characters/characters.js',
'src/app/account/dashboard/characters/detail/detail.js',
'src/app/account/dashboard/dashboard.ctrl.js',
'src/app/account/dashboard/dashboard.js',
'src/app/account/dashboard/panels/admin.ctrl.js',
'src/app/account/dashboard/panels/users.ctrl.js',
'src/app/account/donate/donate.ctrl.js',
'src/app/account/donate/donate.js',
'src/app/account/settings/settings.ctrl.js',
'src/app/account/settings/settings.js',
'src/app/account/vote/vote.ctrl.js',
'src/app/account/vote/vote.js',
'src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',
'src/app/membership/membership.ctrl.js',
'src/app/membership/membership.module.js',
'src/app/news/news.ctrl.js',
'src/app/news/news.js',
'src/app/noctis.ctrl.js',
'src/app/noctis.js',
'src/app/widgets/playersOnline/playersOnline.js',
'src/app/widgets/rankings/rankings.js',
'src/app/widgets/serverDetails/serverDetails.js',
'src/common/directives/feeds/feeds.js',
'src/common/directives/panel/panel.js' ]
And what i would like that after the src/app/ the very first js that comes after the very first folder after src/app/ in our case: account, membership(can be more custom names) to be loaded first, like in the next example:
['src/app/membership/membership.module.js',
'src/app/membership/membership.ctrl.js',
'src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',]
Can you guys help me with some code for my needs? src/app will always be a fixed name except of the next directory that comes after src/app/.
Basically what comes after the unknown name of the directory after src/app, the sub directories in our case(dialogs) or can be something else like(detail, detail/character), to be loaded latest no matter what.
Basically this is the whole function:
function sortJSFiles(files) {
var src = [];
var vendor = [];
files.forEach(function(item) {
if (item.startsWith('src')) {
src.push(item);
} else {
vendor.push(item);
}
});
src.sort(function(a, b) {
var replace = ['noctis.js', 'noctis.ctrl.js', '.module.js', '.ctrl.js'];
function replaceCB(previousValue, currentValue, currentIndex, array) {
return previousValue.replace(currentValue, currentIndex);
}
return replace.reduce(replaceCB, a).localeCompare(replace.reduce(replaceCB, b));
});
return vendor.concat(src);
}
What it does, is that in paramater files comes a lot of paths with js files and i'm trying to sort them after my rule. The problem is, for example taking membership example:
['src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',
'src/app/membership/membership.module.js',
'src/app/membership/membership.ctrl.js']
It succesffully change the sort like loading *.js files that starts first with .module.js and than with .ctrl.js but there is a problem in my code that i need that any js file that comes after src/app/somefolder to be loaded first and any subfolders that are in that somefolder to be loaded latest no matter what.
I am not sure I understood you correctly (it would have been nice if you would have added the literal expected output for your sample data).
I think you want to have the folders sorted, but within the same folder, you want the files in there to be sorted before any of the subfolders in that same folder. And this should be true at every nested level.
To get the files sorted first in every folder, you should in fact extract the folders only, and sort those, and only when two items have exactly the same folder sequence, sort by the file name.
This you can do as follows:
src = src.map(function (path) {
var i = path.lastIndexOf('/');
return [path.substr(0, i), path.substr(i)];
}).sort(function (a, b) {
var i = +(a[0] == b[0]);
return a[i].localeCompare(b[i]);
}).map(function (pair) {
return pair[0] + pair[1];
});
var src = [ 'src/app/account/account.js',
'src/app/account/dashboard/characters/characters.js',
'src/app/account/dashboard/characters/detail/detail.js',
'src/app/account/dashboard/dashboard.ctrl.js',
'src/app/account/dashboard/dashboard.js',
'src/app/account/dashboard/panels/admin.ctrl.js',
'src/app/account/dashboard/panels/users.ctrl.js',
'src/app/account/donate/donate.ctrl.js',
'src/app/account/donate/donate.js',
'src/app/account/settings/settings.ctrl.js',
'src/app/account/settings/settings.js',
'src/app/account/vote/vote.ctrl.js',
'src/app/account/vote/vote.js',
'src/app/membership/dialogs/login.ctrl.js',
'src/app/membership/dialogs/register.ctrl.js',
'src/app/membership/dialogs/termsOfService.ctrl.js',
'src/app/membership/membership.ctrl.js',
'src/app/membership/membership.module.js',
'src/app/news/news.ctrl.js',
'src/app/news/news.js',
'src/app/noctis.ctrl.js',
'src/app/noctis.js',
'src/app/widgets/playersOnline/playersOnline.js',
'src/app/widgets/rankings/rankings.js',
'src/app/widgets/serverDetails/serverDetails.js',
'src/common/directives/feeds/feeds.js',
'src/common/directives/panel/panel.js' ];
src = src.map(function (path) {
var i = path.lastIndexOf('/');
return [path.substr(0, i), path.substr(i)];
}).sort(function (a, b) {
var i = +(a[0] == b[0]);
return a[i].localeCompare(b[i]);
}).map(function (pair) {
return pair[0] + pair[1];
});
console.log(src);
Explanation of the sort callback function
The sort callback function will receive argument a and b. Each of them is a pair including a path at index 0, and a filename at index 1.
The callback uses a variable i that is intended to get the value 0 or 1. If the paths of a and b are the same, then i will be 1, else it will be 0. It determines whether a comparison is needed on the paths or on the filenames.
The unitary + is used to convert the boolean expression a[0] == b[0] to a number. The conversion is: true=>1, false=>0.