How can I capture multiple groups, multiple times with javascript regex - javascript

I have this code snippet aiming to extract next and last link values from github api...
var types = {},
str = '<https://api.github.com/repositories/1424470/issues?access_token=f554f90b4c95c78091a4202150d4583082dab1ce&page=2>; rel="next", <https://api.github.com/repositories/1424470/issues?access_token=f554f90b4c95c78091a4202150d4583082dab1ce&page=7>; rel="last"',
rex = /\s*<https?:\/\/api.github.com\/.+?&page=(\d+)>;\s*rel="(\w+?)"(?:,|$)/g;
// use regex replace method to capture multiple groups multiple times
str.replace(rex, function(_, page, type){
types[type] = +page;
});
console.log(types);
// {next: 2, last: 7}
It is functioning correctly, but feels like a mis-use of regex replace method, where I am not returning anything, but using it only for the sake of having a callback for each match, which I use to build up an output object.
I would prefer some kind of matchAll, returning multi-dimensional array of matches, and parts.
Is there a better way to handle this case in javascript?

You can use the exec() method in a loop, pushing the match results to a multi-dimensional array.
function find_all(re, s) {
var types = [];
while (m = re.exec(s)) {
types.push([m[2], m[1]])
}
return types;
}
var regex = new RegExp('<https?://[^>]+page=(\\d+)>;\\s*rel="([^"]+)"', 'gi');
find_all(regex, str) //=> [ [ 'next', '2' ], [ 'last', '7' ] ]

Related

Filter array with match regex only returning the first match (javascript)

I have to filter and map an array of strings into another array,the problem is that it only matches the first match and the other string doesnt match it when it should because I have tried the apttern on regex101 and it worked.
I have an object called 'stacks' with multiple variables, for simplicity I only write the one im interested(stackName).
"stacks" object:
[
{
"stackName": "cc-dev-euw1d-c-1"
},
{
"stackName": "ngi-prd-euw1b-c-3"
}
]
I made the following code so I can filter and map this "stacks" object into another array adding a string "none" at the beginning and then filtering it with a regex pattern that looks if the letter at the end before the number is a 'c':
this.oldstacks = ['none', ...stacks
.filter(stack => stack.stackName.match('c(?=-\d)'))
.map(stack => stack.stackName)];
For example these string should match:
cc-dev-agw1d-c-1
dd-prd-agw1b-c-3
But these one dont match, because they dont have a "c" before the number:
dd-prd-agw1b-d-3
dd-prd-agw1b-r-3
I would like to filter the stacks object into the oldStacks objects so it only takes the string that match the patter but I cant seem to find out how because this way it only takes the first match and I would like all the matches of the stacks.
Thanks
The issue is that match takes a regex instead of a string, and you can use a match only instead of the lookahead assertion.
const stacks = [{
"stackName": "cc-dev-euw1d-c-1"
},
{
"stackName": "ngi-prd-euw1b-c-3"
}
];
const oldstacks = ['none', ...stacks
.filter(stack => stack.stackName.match(/c-\d/))
.map(stack => stack.stackName)
];
console.log(oldstacks);
Instead of first filtering and then map, you might also use reduce:
const stacks = [{
"stackName": "cc-dev-euw1d-c-1"
},
{
"stackName": "ngi-prd-euw1b-c-3"
}
];
const oldstacks = ['none', ...stacks
.reduce((acc, curr) => {
if (/c-\d/.test(curr.stackName)) acc.push((curr.stackName))
return acc;
}, [])
];
console.log(oldstacks);

JS Variable pulling other variables into it

I know the heading of this questions seems vague - but it's because I simply don't know how to summarize it appropriately.
I'm working on a project where I enter some text, and it's translated into something else.
There's a fiddle here.
If I enter 4, it translates to the word for.
If I enter b4, it should translate to before.
Instead, it translates to bfor, because it's capturing the variable 4 as a separate variable.
I've tried changing the order, but it doesn't work. Is this a regex problem?
My variables are identified in the JS.
var replaceValues = {
'4' : 'for',
'b4' : 'before'
}
$('.bs-text').keyup(function (event) {
newText = event.target.value;
for (var txt in replaceValues) {
var temp = new RegExp(txt, 'gim');
newText = newText.replace(temp, replaceValues[txt]);
}
$('.human-text').text(newText);
});
As I noted in the comments, JS objects does not have defined order of its keys, so it is not a good idea to count on this when you know the dictionary will get much bigger.
More about this in another SO question: Does JavaScript Guarantee Object Property Order?
Instead, use simple array that will have the order you define. Sorting of this dictionary array can be done in JS too, you do not need to handle this by your own.
var replaceValues = [
{key: '4', value: 'for'},
{key: 'b4', value: 'before'},
];
// sort the values so longer keys go first
replaceValues.sort((a, b) => b.key.length - a.key.length);
$('.bs-text').keyup(function (event) {
var newText = event.target.value;
for (var txt in replaceValues) {
var replacement = replaceValues[txt];
var temp = new RegExp(replacement.key, 'gim');
newText = newText.replace(temp, replacement.value);
}
$('.human-text').text(newText);
});
You could also use ES6 Map, it should have order guarantied. But be aware that it is not enough to create Map from Object:
A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
It should be noted that a Map which is a map of an object, especially a dictionary of dictionaries, will only map to the object's insertion order—which is random and not ordered.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map#Objects_and_maps_compared
As mentioned in the comments, you have to look for the longest match first. One option is to generate a single regular expression from the search words, ordered by length, and use a callback to get the correct replacement value.
var replaceValues = {
'4': 'for',
'b4': 'before'
};
// generates something equivalent to `/b4|4/gmi`
var pattern = new RegExp(
Object.keys(replaceValues)
.sort((a, b) => b.length - a.length)
.join('|'),
'gmi'
);
var newText = '4 me b4 me';
console.log(newText.replace(pattern, match => replaceValues[match]));
This works because the regex engine matches alternatives from left to right (i.e. if b4 matches it won't try to match 4). Not sure how this solution scales with more searchwords, but it might actually better because you are only matching the string once instead of n times, i.e. the regex engine doesn't have to traverse the whole string multiple times.
The object property has ":" character within property value
$('.bs-text').keyup(function (event) {
var newText = event.target.value;
if (replaceValues[newText]) {
$('.human-text').text(replaceValues[newText])
};
});
jsfiddle http://jsfiddle.net/je89deam/5/

JS: Filter object array for partial matches

Is it possible to filter for those objects, which matches for a search string?
const arr = [
{ title: 'Just an example' },
{ title: 'Another exam'},
{ title: 'Something different'}
]
I tried this
arr.filter(x => { return x.title === searchStr });
But this will filter only exact matches, but I need to find all partial matches. let searchStr = 'exam' should give me two objects (first and second), let searchStr = 'examp' should give me only one object as the result.
From your question I will assume you also want to match both uppercase and lowercase versions of your string, so here RegExps are the right (but not the only) choice.
RegExp solution:
First, define a case-insensitive RegExp with the i flag, outside of the loop (this avoids re-creating a new RegExp instance on each iteration):
const regexp = new RegExp(searchStr, 'i');
Then you can filter the list with RegExp#test (String#match would work too):
arr.filter(x => regexp.test(x.title))
String#includes solution:
You could also use the .includes method of String, converting both strings to lowercase before comparing them:
arr.filter(x => x.title.toLowerCase().includes(searchStr.toLowerCase()))
Since you are using ES6, use the includes method to test for the substring.
arr.filter(x => x.title.includes(searchStr));
You can check with the indexOf and also add toLowerCase() method to increase the possibility of a match
myArr.filter(function(x) {
return x.title.toLowerCase().indexOf(searchString.toLowerCase()) > -1;
}

Finding word in javascript

I have an sorted array e.g
var arr = [ "aasd","march","mazz" ,"xav" ];
And i want to find the first occurance of letter that starts with "m" here it would be 1 . Is thee any way how to do it without looping trought whole array?
You could use a binary search to find any word starting with that letter, then loop backwards until you get the first one.
Is there any way how to do it without looping trought whole array?
Yes, loop until you've found the match.
If you want to avoid a for or while construct, you can use Array's find() method.
For example, arr.find(word => word.startsWith("m")) should return the result you expect (or undefined if there's no such word).
You could use the find() function to search for the first match that meets your constraint.
The startsWith() function could easily handle this :
// Your array
var arr = [ "aasd","march","mazz" ,"xav" ];
// This will find the first match that starts with "m"
arr.find(function(word){ return word.startsWith('m');}); // yields "march"
Or if you needed a bit more extensive pattern matching, you could use a regular expression via the test() function, which can be seen in the following example and handles the same scenario (matching a string that begins with "m") :
// Your array
var arr = [ "aasd","march","mazz" ,"xav" ];
// First match that starts with "m"
var match = arr.find(function(word){ return /^m/i.test(word);}); // yields "march"
Example
var arr = ["aasd", "march", "mazz", "xav"];
var match = arr.find(function(word) { return /^m/i.test(word); });
alert(match);
You dont need to loop through the whole array - only until such time as you find what you're interested in
function findFirstIndex(arr, char){
for(var i=0;i<arr.length;i++){
if(arr[i].substring(0,1) === char)
return i;
}
return -1; // not found
}
You could use Array#some()
The some() method tests whether some element in the array passes the test implemented by the provided function.
function find(letter, array) {
var index;
array.some(function (a, i) {
if (a[0] === letter) {
index = i;
return true;
}
});
return index;
}
var arr = ["aasd", "march", "mazz", "xav"];
document.write(find('m', arr));

Partially string search array of javascript objects for a needle

Using the following
haystack = [{'id':'73','name':'Elvis'},{'id':'45','name':'Beatles'}, etc.]
I want to perform a search whereby I can find Elvis by searching for "elv" or "Elv" (thus, a case insensitive search). Results should return in array thus allowing more than one needle to be returned from my search.
My solution is convert my needle into lowercase, no spaces, and use a for loop to go thru my haystack making checks on a lowercase/nospace name. But I suspect there are other more resource friendly methods (I want to know if there is a better way so I can enhance my skillset/knowledge)
I had thought of using jQuery grep or inArray but both appear to be strict with their comparison. And array.filter() was another idea. But various attempts so far fail.
Thanks
Not sure what you tried but .filter() should have worked. Make sure to lowercase both the search string and the name of the searched items.
var searchTerm = 'Elv'.toLowerCase();
var results = haystack.filter(function(item){
return item.name.toLowerCase().indexOf(searchTerm) > -1;
});
alternatively you could use regexp for the comparison
var searchTerm = 'Elv',
search = new RegExp(searchTerm, 'gi');
var results = haystack.filter(function(item){
return item.name.match(search);
});
You can do it with Array#filter and some String's functions
var haystack = [{ 'id': '73', 'name': 'Elvis' }, { 'id': '45', 'name': 'Beatles' }];
var w = 'elv';
function search(word) {
return haystack.filter(function(e) {
return e.name.toLowerCase().substr(0, word.length) == word;
});
}
console.log(search(w));

Categories