I have a javascript regEx that is supposed to find all values with curly brackets around them eg {} and return a list of the unique values. I thought that it was working perfectly but I found that it doesn't work depending on the sequence of values.
For example: If the target document contains {lorem}{lorem}{ipsem}{ipsem} the script logs what's wanted [lorem, ipsem] but {lorem}{ipsem}{ipsem}{lorem} the script logs [lorem, ipsem,lorem]. What am I doing wrong!?
function getVariables() {
var doc = DocumentApp.getActiveDocument();
var str = doc.getText(); //get the text of the document
var result = str.match(/{.*?}/g).map(function(val) {
return val.replace(/[\])}[{(]/g, "");
//return val.replace(/(^.*\[|\].*$)/g,'');
});
//The purpose of sort_unique is to find one of every value or string represented in an array
function sort_unique(arr) {
if (result.length === 0) return arr;
arr = arr.sort(function(a, b) {
return a * 1 - b * 1;
});
var ret = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i - 1] !== arr[i]) {
ret.push(arr[i]);
}
}
for (var index = 0; index < ret.length; index++) {
Logger.log(ret[index]);
}
return ret;
}
result = sort_unique(result);
Logger.log("Getting final result for front end....");
Logger.log(result);
return result;
}
I believe part of your problem is the sort method. If you replace
arr = arr.sort(function(a, b) {
return a * 1 - b * 1;
});
with
arr = arr.sort();
Then the function appears to work, at least on my side.
This will run in O(n log n) time. You can do better without sorting, if you store the values you've found so far in a map instead of an array. This would run in linear time.
(Also you'll want to replace if (result.length === 0) return arr; with if (arr.length === 0) return arr; just to make your sort_unique function completely independent of the surrounding function.)
The simplest method would be to use a Set. Store each of the regex matches in a set, then return Array.from(mySet).
var mySet = new Set();
str.match(/{.*?}/g).forEach(function(val) {
mySet.add(val.replace(/[\])}[{(]/g, ""));
});
return Array.from(mySet);
A set's add() function is O(1) so the total running time is O(n) where n is the number of matches in your string. Though, realistically, the regex search will be where most of the processing time occurs.
You check if the subsequent items are the same and those that are not subsequent land in the resulting array.
Check if the found value is in the result, and if not add the match, else, ignore.
Use the code like
function getVariables() {
var doc = DocumentApp.getActiveDocument();
var str = doc.getText(); //get the text of the document
var m, result=[], rx = /{([^{}]*)}/g;
while (m=rx.exec(str)) {
if (result.indexOf(m[1]) == -1) {
result.push(m[1]);
}
}
result.sort(); // If you really want to sort use this
// Logger.log(result); // View the result
}
The /{([^{}]*)}/g regex matches {, then captures into Group 1 zero or more chars other than { and }. So, the value you need is in m[1]. The if (result.indexOf(m[1]) == -1) checks if the value is in result.
I'm trying to set up a function that checks if a word or a text is a palindrome. To do that, it splits the text so that every letter is an element of a new array, it takes rid of the white spaces and it makes the reverse array.
Then it checks if every element of the two arrays, at the same positions, are equal. If not it returns false, if yes it returns true.
Here the function:
function palindrome(str) {
var low = str.toLowerCase();
var newArray = low.split("");
var noSpace = newArray.filter(function(val) {
return val !== " ";
});
var reverse = noSpace.reverse();
function check (a, b) {
console.log(`checking '${a}' against '${b}'`);
var partial;
var result = 1;
for (var i = 0; i < a.length; i++) {
console.log(`comparing '${a[i]}' and '${b[i]}'`);
if (a[i] !== b[i]) {
result = 0;
} else {
partial = 1;
result *= partial;
}
}
return result;
}
var result = check(noSpace, reverse);
if (result == 1) {
return true;
} else {
return false;
}
}
palindrome("r y e");
I don't know what's wrong but it seems that the function keeps on returning a true value no matter what word or text I pass to the function. What is wrong with that?
Your issue seems to be because reverse() changes the actual array as well. So doing
var reverse = noSpace.reverse();
Will reverse noSpace and assign a reference to it on the variable reverse. That is, both arrays will be the same (reversed) array.
To bypass that, I've used .slice() to create a copy of the original array, and then called .reverse() on that new array, ridding you of any conflicts.
Here's a working snippet of what it looks like:
function palindrome(str) {
var str_array = str.toLowerCase().split("");
var no_space = str_array.filter(function(val) {
return val !== " ";
});
// By applying '.slice()', we create a new array
// reference which can then be reversed and assigned
// to the 'reverse' variable
var reverse = no_space.slice().reverse();
function check(a, b) {
var partial;
var result = 1;
for(var i=0; i < a.length; i++) {
if(a[i] !== b[i]) {
// We don't need to keep
// comparing the two, it
// already failed
return 0;
} else {
// I've kept this part even though
// I don't really know what it is
// intended for
partial = 1;
result *= partial;
}
}
return result;
}
return check(no_space, reverse) === 1;
}
console.log(palindrome("a b a"));
console.log(palindrome("r y e"));
The way you have coded for palindrome is way too complicated.
But there is one problem with your code: when you do a reverse() it changes the original array as well.
So you will need to make sure that you copy it via slice().
Also you can directly send a boolean result rather than doing a 1 and 0.
At result *= partial;, 1 * 1 will always equal 1
I didn't correct your code, but here is a optimized solution for you.
function palindrom(string) {
var arr = string.split("");
var lengthToCheck = Math.floor(arr.length / 2);
for (var i = 0; i < lengthToCheck; i++) {
if (arr[i] != arr[arr.length - (1 + i)]) {
return false;
}
}
return true;
}
First I split the array after every charater of the passed String. After that I get the half of the length of the array as it's enough to check just one half.
With the for-loop I compare the first half with the second half. As soon as I found two characters that do not match I return false. In case the whole first half matches the second half of the array, the for-loop will be completed and after that true will be returned.
What's actually happening is .reverse() reverses an array in place, it then stores a reference to that array which is not what you're calling in your check() method.
Simple fix would be to change your if statement:
if (a[i] !== b.reverse()[i])
I've a strange thing to do but I don't know how to start
I start with this vars
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1];
So to start all the 3 array have the same length and the very first operation is to see if there is a duplicate value in sky array, in this case the 0 is duplicated and only in this case is at the end, but all of time the sky array is sorted. So I've to remove all the duplicate (in this case 0) from sky and remove the corresponding items from base and sum the corresponding items on ite. So if there's duplicate on position 4,5 I've to manipulate this conditions. But let see the new 3 array:
var new_base = [1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var new_sky = [0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var new_ite = [139,38,13,15,6,4,6,3,2,1,2,1,1,1];
If you see the new_ite have 139 instead the 64,52,23, that is the sum of 64+52+23, because the first 3 items on sky are the same (0) so I remove two corresponding value from base and sky too and I sum the corresponding value into the new_ite array.
There's a fast way to do that? I thought a for loops but I stuck at the very first for (i = 0; i < sky.length; i++) lol, cuz I've no idea on how to manipulate those 3 array in that way
J
When removing elements from an array during a loop, the trick is to start at the end and move to the front. It makes many things easier.
for( var i = sky.length-1; i>=0; i--) {
if (sky[i] == prev) {
// Remove previous index from base, sky
// See http://stackoverflow.com/questions/5767325/how-to-remove-a-particular-element-from-an-array-in-javascript
base.splice(i+1, 1);
sky.splice(i+1, 1);
// Do sum, then remove
ite[i] += ite[i+1];
ite.splice(i+1, 1);
}
prev = sky[i];
}
I won't speak to whether this is the "fastest", but it does work, and it's "fast" in terms of requiring little programmer time to write and understand. (Which is often the most important kind of fast.)
I would suggest this solution where j is used as index for the new arrays, and i for the original arrays:
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1];
var new_base = [], new_sky = [], new_ite = [];
var j = -1;
sky.forEach(function (sk, i) {
if (!i || sk !== sky[i-1]) {
new_ite[++j] = 0;
new_base[j] = base[i];
new_sky[j] = sk;
}
new_ite[j] += ite[i];
});
console.log('new_base = ' + new_base);
console.log('new_sky = ' + new_sky);
console.log('new_ite = ' + new_ite);
You can use Array#reduce to create new arrays from the originals according to the rules:
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330];
var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17];
var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1];
var result = sky.reduce(function(r, n, i) {
var last = r.sky.length - 1;
if(n === r.sky[last]) {
r.ite[last] += ite[i];
} else {
r.base.push(base[i]);
r.sky.push(n);
r.ite.push(ite[i]);
}
return r;
}, { base: [], sky: [], ite: [] });
console.log('new base:', result.base.join(','));
console.log('new sky:', result.sky.join(','));
console.log('new ite:', result.ite.join(','));
atltag's answer is fastest. Please see:
https://repl.it/FBpo/5
Just with a single .reduce() in O(n) time you can do as follows; (I have used array destructuring at the assignment part. One might choose to use three .push()s though)
var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330],
sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17],
ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1],
results = sky.reduce((r,c,i) => c === r[1][r[1].length-1] ? (r[2][r[2].length-1] += ite[i],r)
: ([r[0][r[0].length],r[1][r[1].length],r[2][r[2].length]] = [base[i],c,ite[i]],r),[[],[],[]]);
console.log(JSON.stringify(results));
I have a variable 'getDuplicates', which contains values like this:
getDuplicates = 100,120,450,490,600,650, ...
These are pairs and ranges: 1stbegin,1stend,2ndbegin,2ndend
Now I have to loop them to apply these ranges.
var getDuplicates = $element.attr('duplicates');
if (getDuplicates !== undefined && getDuplicates !== null) {
var noOfDuplicates = (getDuplicates.split(',').length) / 2;
console.log(getDuplicates, 'Counter:', noOfDuplicates);
for (var i = 0; i < noOfDuplicates; i++) {
newRange = rangy.createRange();
newRange.selectCharacters(rangyElement, **BEGIN, END**);
var newApplier = rangy.createClassApplier(highlightClass, {
elementTagName : "span"
});
newApplier.applyToRange(newRange);
}
}
Actually, I have no idea how to set BEGIN and END. Thank you for your tips
Iterate the array with a for loop, using steps of 2 (i += 2). Get the start and end using the brackets ([]) notation.
Since the loop uses steps of 2, use array.length in the stop condition instead of the noOfDuplicates;
var getDuplicates = "100,120,450,490,600,650";
// you need to split the string, so you'll have an array you can iterate
var duplicates = getDuplicates.split(',');
for(var i = 0; i < duplicates.length; i += 2) {
console.log(duplicates[i], duplicates[i + 1]); // 1st is start, 2nd is end
}
So i have this array
[ 'vendor/angular/angular.min.js',
'vendor/angular-nice-bar/dist/js/angular-nice-bar.min.js',
'vendor/angular-material/modules/js/core/core.min.js',
'vendor/angular-material/modules/js/backdrop/backdrop.min.js',
'vendor/angular-material/modules/js/dialog/dialog.min.js',
'vendor/angular-material/modules/js/button/button.min.js',
'vendor/angular-material/modules/js/icon/icon.min.js',
'vendor/angular-material/modules/js/tabs/tabs.min.js',
'vendor/angular-material/modules/js/content/content.min.js',
'vendor/angular-material/modules/js/toolbar/toolbar.min.js',
'vendor/angular-material/modules/js/input/input.min.js',
'vendor/angular-material/modules/js/divider/divider.min.js',
'vendor/angular-material/modules/js/menu/menu.min.js',
'vendor/angular-material/modules/js/select/select.min.js',
'vendor/angular-material/modules/js/radioButton/radioButton.min.js',
'vendor/angular-material/modules/js/checkbox/checkbox.min.js',
'vendor/angular-material/modules/js/switch/switch.min.js',
'vendor/angular-material/modules/js/tooltip/tooltip.min.js',
'vendor/angular-material/modules/js/toast/toast.min.js',
'vendor/angular-clipboard/angular-clipboard.js',
'vendor/angular-animate/angular-animate.min.js',
'vendor/angular-aria/angular-aria.min.js',
'vendor/angular-messages/angular-messages.min.js',
'vendor/angular-ui-router/release/angular-ui-router.js',
'src/app/about/about.js',
'src/app/hekate.cfg.js',
'src/app/hekate.ctrl.js',
'src/app/hekate.module.js',
'src/app/home/home.js',
'src/app/user/dialog/user.signIn.ctrl.js',
'src/app/user/dialog/user.signIn.module.js',
'src/app/user/user.cfg.js',
'src/app/user/user.ctrl.js',
'src/app/user/user.module.js',
'src/common/services/toast.service.js',
'templates-common.js',
'templates-app.js'
]
And taking the following part from the above array as example:
[
'src/app/hekate.cfg.js',
'src/app/hekate.ctrl.js',
'src/app/hekate.module.js',
]
I want to sort it like
[
'src/app/hekate.module.js',
'src/app/hekate.cfg.js',
'src/app/hekate.ctrl.js',
]
So more specific of what i want is to find in that array where string is duplicated and after check if has at the end [.cfg.js, .ctrl.js, .module.js] and automatic order them to [.module.js, .cfg.js, .ctrl.js]
Can anyone please help me with that?
A single sort proposal.
var array = ['src/app/about/about.js', 'src/app/hekate.cfg.js', 'src/app/hekate.ctrl.js', 'src/app/hekate.module.js', 'src/app/home/home.js', 'src/app/user/dialog/user.signIn.ctrl.js', 'src/app/user/dialog/user.signIn.module.js', 'src/app/user/user.cfg.js', 'src/app/user/user.ctrl.js', 'src/app/user/user.module.js'];
array.sort(function (a, b) {
function replaceCB(r, a, i) { return r.replace(a, i); }
var replace = ['.module.js', '.cfg.js', '.ctrl.js'];
return replace.reduce(replaceCB, a).localeCompare(replace.reduce(replaceCB, b));
});
document.write('<pre>' + JSON.stringify(array, 0, 4) + '</pre>');
To prevent so much replaces, i suggest to have a look to sorting with map.
You can try something like this:
Algo:
Group based on path and store file names as value.
Check for existence of one of special file ".cfg.js"
Sort following list based on custom sort.
Loop over object's property and join key with values to form full path again.
If you wish to sort full array, you can sort keys itself and then merge path with names. I have done this. If you do not wish to do this, just remove sort function from final loop.
Sample
var data=["vendor/angular/angular.min.js","vendor/angular-nice-bar/dist/js/angular-nice-bar.min.js","vendor/angular-material/modules/js/core/core.min.js","vendor/angular-material/modules/js/backdrop/backdrop.min.js","vendor/angular-material/modules/js/dialog/dialog.min.js","vendor/angular-material/modules/js/button/button.min.js","vendor/angular-material/modules/js/icon/icon.min.js","vendor/angular-material/modules/js/tabs/tabs.min.js","vendor/angular-material/modules/js/content/content.min.js","vendor/angular-material/modules/js/toolbar/toolbar.min.js","vendor/angular-material/modules/js/input/input.min.js","vendor/angular-material/modules/js/divider/divider.min.js","vendor/angular-material/modules/js/menu/menu.min.js","vendor/angular-material/modules/js/select/select.min.js","vendor/angular-material/modules/js/radioButton/radioButton.min.js","vendor/angular-material/modules/js/checkbox/checkbox.min.js","vendor/angular-material/modules/js/switch/switch.min.js","vendor/angular-material/modules/js/tooltip/tooltip.min.js","vendor/angular-material/modules/js/toast/toast.min.js","vendor/angular-clipboard/angular-clipboard.js","vendor/angular-animate/angular-animate.min.js","vendor/angular-aria/angular-aria.min.js","vendor/angular-messages/angular-messages.min.js","vendor/angular-ui-router/release/angular-ui-router.js","src/app/about/about.js","src/app/hekate.cfg.js","src/app/hekate.ctrl.js","src/app/hekate.module.js","src/app/home/home.js","src/app/user/dialog/user.signIn.ctrl.js","src/app/user/dialog/user.signIn.module.js","src/app/user/user.cfg.js","src/app/user/user.ctrl.js","src/app/user/user.module.js","src/common/services/toast.service.js","templates-common.js","templates-app.js"];
// Create groups based on path
var o = {};
data.forEach(function(item) {
var lastIndex = item.lastIndexOf('/') + 1;
var path = item.substring(0, lastIndex);
var fname = item.substring(lastIndex);
if (!o[path]) o[path] = [];
o[path].push(fname);
});
var manualOrder= [".module.js", ".cfg.js", ".ctrl.js"];
Array.prototype.fuzzyMatch = function(search){
return this.some(function(item){
return item.indexOf(search)>-1;
});
}
Array.prototype.fuzzySearchIndex = function(search){
var pos = -1;
this.forEach(function(item, index){
if(search.indexOf(item)>-1){
pos = index;
}
});
return pos;
}
function myCustomSort(a,b){
var a_pos = manualOrder.fuzzySearchIndex(a);
var b_pos = manualOrder.fuzzySearchIndex(b);
return a_pos > b_pos ? 1 : a_pos < b_pos ? -1 : 0;
}
// Check for ".cfg.js" and apply custom sort
for (var k in o) {
if (o[k].fuzzyMatch(".cfg.js")) {
o[k].sort(myCustomSort);
}
}
// Merge Path and names to create final value
var final = [];
Object.keys(o).sort().forEach(function(item) {
if (Array.isArray(o[item])) {
final = final.concat(o[item].map(function(fn) {
return item + fn
}));
} else
final = final.concat(o[item]);
});
console.log(final);
First make an array for names like 'hekate'.
Then make an array for final results.
We need 3 searching loops for ctrls, cfgs and modules.
If string contains arrayWithNames[0] + '.module' push the whole record to new array that you created. Same with ctrls and cfgs.
var allItems = []; //your array with all elements
var namesArray = [];
var finalResultsArray = [];
//fill name array here:
for(var i=0; i<=allItems.length; i++){
//you have to split string and find the module name (like 'hekate'). i hope you know how to split strings
}
//sort by modules, cfgs, ctrls:
for(var i=0; i<=namesArray.length; i++){
if(allItems[i].indexOf(namesArray[i] + '.module') > -1) {
finalResultsArray.push(allItems[i]);
}
}
for(var i=0; i<=namesArray.length; i++){
if(allItems[i].indexOf(namesArray[i] + '.cfg') > -1) {
finalResultsArray.push(allItems[i]);
}
}
for(var i=0; i<=namesArray.length; i++){
if(allItems[i].indexOf(namesArray[i] + '.ctrl') > -1) {
finalResultsArray.push(allItems[i]);
}
}
//now finalResultsArray have what you wanted
You can provide your own compare function to array.sort (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
Write one that returns the correct order for modules, ctrls and cfgs:
It should first remove the suffixes, and if the rest is the same, use the correct logic to return the order according to the suffix. Otherwise return a value according to the alphabetical order.
Update
I didn't test this code (not is it finished), but it should look something like that:
arr.sort(function(a, b) {
if ((a.endsWith(".cfg.js") || a.endsWith(".ctrl.js") || a.endsWith(".module.js")) &&
(b.endsWith(".cfg.js") || b.endsWith(".ctrl.js") || b.endsWith(".module.js"))) {
var sortedSuffixes = {
".module.js": 0,
".cfg.js": 1,
".ctrl.js": 2
};
var suffixAIdx = a.lastIndexOf(".cfg.js");
if (suffixAIdx < 0) suffixAIdx = a.lastIndexOf(".ctrl.js");
if (suffixAIdx < 0) suffixAIdx = a.lastIndexOf(".module.js");
var suffixBIdx = b.lastIndexOf(".cfg.js");
if (suffixBIdx < 0) suffixBIdx = b.lastIndexOf(".ctrl.js");
if (suffixBIdx < 0) suffixBIdx = b.lastIndexOf(".module.js");
var prefixA = a.substring(0, suffixAIdx);
var prefixB = b.substring(0, suffixAIdx);
if (prefixA != prefixB)
{
return a.localeCompare(b);
}
var suffixA = a.substring(suffixAIdx);
var suffixB = b.substring(suffixBIdx);
return sortedSuffixes[suffixA] - sortedSuffixes[suffixB];
} else {
return a.localeCompare(b);
}
});
Update 2
Here is a fiddle (https://jsfiddle.net/d4fmc7ue/) that works.