Problems with finding unique values in regEx - javascript

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.

Related

Checking if str1 can be re-arranged as str2 wth JavaScript

There are two strings called str1 and str2 and I'm trying to check if str1 can be re-arranged as str2.
FOR EXAMPLE: lets say str1 = "aabbcamaomsccdd" and str2="commas".
Is it possible to write the word "commas" out of "str1"
function scramble(str1, str2) {
let arr=[];
let str1arr = str1.split("");
let str2arr = str2.split("");
let j=0;
for(let i=0; i<str1.length; i++){
if(str1arr[i]==str2arr[j]){
arr.push(str1arr[i]);
str1arr=str1arr.splice(i,1);
j++;
i=0;
}
}if(arr.toString()===str2arr.toString()){
return true;
}else{
return false;
}
}
What I tried basically if str1arr[i]==str2arr[j] it will put the str1arr[i] value on a new array called arr and at the end it will compare str2 and the arr and return True or False.
The reason why I used str1arr=str1arr.splice(i,1); to delete the i after the match is because the for loop is reseting it self to check from the "i=0" each time i and j matches and that i would match with other duplicate letters (I hope thats what it does atleast :D).
It is an internet question and im not passing the tests. I only pass if the result is FALSE.
I want to know what I'm doing and thinking wrong here. Its not performance efficent too so any comment on that would be great too.
You could take arrays and sort them and check each character of the second string/array against the first one.
function compare([...a], [...b]) {
a.sort();
return b.sort().every((i => v => {
while (i < a.length && a[i] !== v) i++;
return a[i++] === v;
})(0));
}
console.log(compare("aabbcamaomsccdd", "commas")); // true
console.log(compare("aabbcamaomccdd", "commas")); // false
You should just check that both strings contain the same chars like so:
function scramble(str1, str2) {
var s1 = str1.split('');
var s2 = str2.split('');
var i;
for (i = 0; i < s2.length; i++) {
const idx = s1.indexOf(s2[i]);
if (idx === -1) {
return false;
}
s1.splice(idx, 1);
}
return s1.length === 0;
}
console.log(scramble('xcab1c', 'abxcc1'));
You could count the frequency of each character in your first string. Below I have used .reduce() to build an object with key-value pairs, where the key represents a character from your s1 string and the value is how many times it appears. You can then loop through the characters in s2 and check that every character appears in the frequency map. When you see a character you can subtract one from the value from the frequency object to signify that the character has been "used". If the .every() callback returns a falsy value (such as 0 for the value), then the result will be false, as your string can't be re-arranged:
const scramble = (s1, s2) => {
const s1Freq = [...s1].reduce((o, c) => ({...o, [c]: (o[c] || 0) +1}), {});
return [...s2].every(char => s1Freq[char]--);
}
console.log(scramble("aabbcamaomsccdd", "commas")); // true
console.log(scramble("abc321", "123")); // true
console.log(scramble("a3b2c11", "1231")); // true
console.log(scramble("a", "a")); // true
console.log(scramble("xyz", "xyt")); // false

Determine if an array has repeated element by sorting the array

<script>
var arr = [];
function repeater(str){
for (var i=0; i<str.length;i++)
{arr.push(str[i])}
arr.sort()
for (var g=0;g<arr.length;g++) {
if (arr[g]==arr[g+1])
{return false}
else {return true}
}
}
document.write(repeater("jrtgrt"))
console.log(arr)
</script>
Create a function that takes a string and returns either true or false depending on whether or not it has repeating characters.
The array is working by console, but the second part doesn't seem to be running.
Your loop is terminating after the first comparison because either way return is called.
<script>
var arr = [];
function repeater(str) {
for (var i = 0; i < str.length; i++) {
arr.push(str[i])
}
arr.sort()
for (var g = 0; g < arr.length - 1; g++) {
console.log(arr[g], arr[g + 1])
if (arr[g] == arr[g + 1]) {
return true
}
}
return false;
}
document.write(repeater("12s35sd46"))
console.log(arr)
</script>
There are a few ways to simplify your code.
You don't need a for loop to create an array from a string. You can use the split function.
arr = str.split("");
Your entire function can literally be simplified to this using a regular expression.
function repeater(str) {
return /(.).*\1/.test(str);
}
Here's an explanation of each part of the regular expression:
(.) any character, capture to use later
.* any character any number of times
\1 first captured character
An approach might not necessarily rely on an array's sort method. One, for instance, could stepwise shorten the list of characters and then, via an array's some method, determine whether the current character has a duplicate counterpart in the yet remaining list of characters. Thus one also keeps the amount of iteration cycles low ... example ...
function hasDuplicateChars(stringValue) {
var hasDuplicate = false;
var charList = stringValue.split('');
var char;
// continue taking the first entry of `charList` while mutating the latter.
while (!hasDuplicate && (char = charList.shift())) { // `char` either will be a string or a undefined value.
hasDuplicate = charList.some(function (listItem) { return (char === listItem); })
}
return hasDuplicate;
}
console.log('hasDuplicateChars("") ? ', hasDuplicateChars(""));
console.log('hasDuplicateChars("x") ? ', hasDuplicateChars("x"));
console.log('hasDuplicateChars("abcdefghijklmnopqrstuvwxyz") ? ', hasDuplicateChars("abcdefghijklmnopqrstuvwxyz"));
console.log('hasDuplicateChars("TheQuickBrownFox") ? ', hasDuplicateChars("TheQuickBrownFox"));
console.log('hasDuplicateChars("Hallo, world.") ? ', hasDuplicateChars("Hallo, world."));
console.log('hasDuplicateChars(" ") ? ', hasDuplicateChars(" "));
.as-console-wrapper { max-height: 100%!important; top: 0; }

Comparing values between two arrays

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])

Permutations filtered with no repeating characters

This is a task from freeCodeCamp.
My goal is to create a function which:
Takes any string with any characters.
Creates an array with all the permutations possible out of that string.
Filters the array and returns only the strings which don't have repeated consecutive letters.
Return the number of total permutations of the provided string that don't have repeated consecutive letters. Assume that all characters in
the provided string are each unique. For example, aab should return 2
because it has 6 total permutations (aab, aab, aba, aba, baa, baa),
but only 2 of them (aba and aba) don't have the same letter (in this
case a) repeating.
I can't figure out what have i wrote wrong. I think the problem lies either in the filter function or the permutation list is faulty.
function permAlone(str) {
if (str.length == 1) {
return str;
}
// Creates all possible Permutations and pushes to an array
var arr = [];
var p = 0; // position of the element which needs to be swapped
// Loop count equal to number of Permutations.
var loops = factorialize(str.length);
for (var i = 0; i < loops; i++) {
// if the position is not the last element in the strig keep swapping with next following character
if (p != str.length - 1) {
var splitStr = str.split('')
arraySwapElements(splitStr, p, p + 1);
str = splitStr.join('');
arr.push(str);
p += 1;
// when position is at the last index, change position to 0
} else {
p = 0;
i -= 1;
}
}
// swaps 2 items in an array
function arraySwapElements(arr, a, b) {
var item = arr[a];
arr[a] = arr[b];
arr[b] = item;
};
// outputs a factorial of a number
function factorialize(num) {
if (num === 0) {
return 1;
} else {
return num * factorialize(num - 1);
}
}
// filters any array which has 2 or more repeating characters
var x = arr.filter(function(str) {
var re = /(.)\1+/;
var result = re.test(str);
if (!result) {
return str;
}
})
// returns the filtered arrays length
return x.length
}
console.log(permAlone('abfdefa'));
When testing:
permAlone("aab") should return a number. // Correct
permAlone("aab") should return 2. // Correct
permAlone("aaa") should return 0. // Correct
permAlone("aabb") should return 8. // Correct
permAlone("zzzzzzzz") should return 0.// Correct
permAlone("a") should return 1.// Correct
permAlone("aaab") should return 0.// Correct
permAlone("abcdefa") should return 3600. // Incorrect
permAlone("abfdefa") should return 2640.// Incorrect
permAlone("aaabb") should return 12. // Incorrect
The issue stems from the logic used in the for loop. While the loop does generate the right number of total permutations, it doesn't generate all permutations.
For example, if our string to be permuted was "abcd", the swapping mechanism would generate strings like this:
bacd bcad bcda
cbda cdba cdab
dcab dacb dabc
adbc abdc abcd
Uh oh! That last arrangement is the same as the starting string. When we start swapping again, we're going to get the same set that we did on the first pass. We're never going to get a permutation like "acbd". Thus the resulting array contains higher numbers of some permutations and lower numbers of others.
I'm not sure how to fix it with the approach you're using, but a recursive function to get permutations could be written like this:
// Returns an array of all permutations of a string
function getPerms(str) {
// Base case. If the string has only one element, it has only one permutation.
if (str.length == 1) {
return [str];
}
// Initialise array for storing permutations
let permutations = [];
// We want to find the permutations starting with each character of the string
for (let i = 0; i < str.length; i++) {
// Split the string to an array
let splitStr = str.split('');
// Pull out the character we're checking for permutations starting with
let currentElem = splitStr.splice(i, 1)[0];
// Get permutations of the remaining characters
let subPerms = getPerms(splitStr.join(''));
// Concat each of these permutations with the character we're checking
// Add them to our list
subPerms.forEach(function (combination) {
permutations.push(currentElem.concat(combination));
});
}
// return our list
return combinations;
}

How to write a character matching algorithm in JavaScript?

Given this input s1 = "dadxx" s2 = "ddxx" I'd expect the output to contain a bunch of a,b pairs wherever each character in s1 matched a character in s2 and vice versa (duplicates allowed). Among those pairs would be 0,0 because s1[0] and s2[0] are both equal to d.
The problem is that my output doesn't contain 2,1 even though s1[2] and s2[1] are both equal to d.
Can someone fix my algorithm or make a better one?
Here's a JSFiddle if it helps.
Here's my code:
// For each char, see if other string contains it
s1 = 'dadxx'
s2 = 'ddxx'
matchChars(s1,s2)
matchChars(s2,s1)
function matchChars(a,b) {
for (i = 0; i < a.length; i++) {
found = b.indexOf(a[i])
if (found >= 0) {
if (a===s1) console.log(i,found)
else console.log(found,i)
}
}
}
I believe the problem you're having is that you're only checking for a single match for s1[i] in s2 by using indexOf. That will find the first index of a matched value, not every index.
If you instead iterate through both strings and compare every character, you get the result I think you're trying to achieve.
// Define strings
s1 = 'dadxx'
s2 = 'ddxx'
matchChars(s1,s2)
matchChars(s2,s1)
function matchChars(a,b) {
// Convert strings to lower case for case insensitive matching
// Remove if case sensitive matching required
a = a.toLowerCase();
b = b.toLowerCase();
// Iterate through every letter in s1
for (i = 0; i < a.length; i++) {
// Iterate through every letter in s2
for (j = 0; j < b.length; j++) {
// Check if the letter in s1 matches letter in s2
if (a[i] === b[j]) {
// Changed per request of OP
(a === s1) ? console.log(i, j) : console.log(j, i);
// console.log([i, j]);
}
}
}
}
Working JSBin example: https://jsbin.com/wecijopohi/edit?js,console
You say duplicates are allowed but not required. I'm submitting this as a more modern approach, not as a correction to the accepted solution, which looks good to me. https://jsfiddle.net/avc705zr/3/
match = (a, b) => {
let re, match, matches = []
a.split('').forEach((l, i) => {
re = new RegExp(l, 'g')
while ((match = re.exec(b)) != null) {
matches.push([i, match.index])
}
})
return matches
}
However, in my experience when you actually need functionality like this, you only need one of the strings to exhausted. In other words, you are looking for matches in string 2 of all instances in string 1 -- which is to say, unique characters in string 1. So a modification which might come up in the real world might instead be like:
Array.prototype.unique = function() {
return this.filter(function (value, index, self) {
return self.indexOf(value) === index;
});
}
match = (a, b) => {
let re, match, matches = []
a.split('').unique().forEach(l => {
re = new RegExp(l, 'g')
while ((match = re.exec(b)) != null) {
matches.push([l, match.index])
}
})
return matches
}

Categories

Resources