68

Let's say I have an array with many Strings Called "birdBlue", "birdRed" and some other animals like "pig1", "pig2").

Now I run a for loop that goes through the array and should return all birds. What comparison would make sense here?

Animals == "bird*" was my first idea but doesn't work. Is there a way to use the operator * (or is there something similar to use?

Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
xqz313
  • 751
  • 2
  • 6
  • 9
  • 4
    [JavaScript supports regular expressions.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) – Pointy Oct 07 '14 at 22:56
  • 1
    Time to learn about regular expressions: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions, http://www.regular-expressions.info/tutorial.html – Felix Kling Oct 07 '14 at 22:56

10 Answers10

145

I think you meant something like "*" (star) as a wildcard for example:

  • "a*b" => everything that starts with "a" and ends with "b"
  • "a*" => everything that starts with "a"
  • "*b" => everything that ends with "b"
  • "*a*" => everything that has an "a" in it
  • "*a*b*"=> everything that has an "a" in it, followed by anything, followed by a "b", followed by anything

or in your example: "bird*" => everything that starts with bird

I had a similar problem and wrote a function with RegExp:

//Short code
function matchRuleShort(str, rule) {
  var escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
  return new RegExp("^" + rule.split("*").map(escapeRegex).join(".*") + "$").test(str);
}

//Explanation code
function matchRuleExpl(str, rule) {
  // for this solution to work on any string, no matter what characters it has
  var escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");

  // "."  => Find a single character, except newline or line terminator
  // ".*" => Matches any string that contains zero or more characters
  rule = rule.split("*").map(escapeRegex).join(".*");

  // "^"  => Matches any string with the following at the beginning of it
  // "$"  => Matches any string with that in front at the end of it
  rule = "^" + rule + "$"

  //Create a regular expression object for matching string
  var regex = new RegExp(rule);

  //Returns true if it finds a match, otherwise it returns false
  return regex.test(str);
}

//Examples
alert(
    "1. " + matchRuleShort("bird123", "bird*") + "\n" +
    "2. " + matchRuleShort("123bird", "*bird") + "\n" +
    "3. " + matchRuleShort("123bird123", "*bird*") + "\n" +
    "4. " + matchRuleShort("bird123bird", "bird*bird") + "\n" +
    "5. " + matchRuleShort("123bird123bird123", "*bird*bird*") + "\n" +
    "6. " + matchRuleShort("s[pe]c 3 re$ex 6 cha^rs", "s[pe]c*re$ex*cha^rs") + "\n" +
    "7. " + matchRuleShort("should not match", "should noo*oot match") + "\n"
);

If you want to read more about the used functions:

Klesun
  • 12,280
  • 5
  • 59
  • 52
Spenhouet
  • 6,556
  • 12
  • 51
  • 76
  • Theres a potential problem with this answer. Javascript's .replace() with a string on the first argument will only replace the first character found, so the 4rth example \*a\* would not work. You can use .replace(/\\*/g, '.*') to replace all *s – ThadeuLuz Mar 23 '16 at 20:57
  • @ThadeuLuz: That's not correct. Javascript's .replace() takes a regex and replaces all characters found that match the regex => http://www.w3schools.com/jsref/jsref_replace.asp – Spenhouet Mar 29 '16 at 10:18
  • 1
    Not with a string on the first argument, like in line See it for yourself: <"world world".replace("world", "hello")> – ThadeuLuz Mar 30 '16 at 12:44
  • @ThadeuLuz: Yes, you are right. Thank you for your help! I have replaced the .replaced with a split.join solution (instead of the /\\*/g global option). I have also added some examples that cover the \*a\* case. – Spenhouet Mar 30 '16 at 14:36
  • @Spen can this be modified to support negation? For example let's say I want to find all bird* matches except bird124. The user would give something like "bird* -bird124" and all strings containing "-" shouldn't match. Is this possible? – XeniaSis Dec 06 '16 at 08:13
  • 5
    @Spen, your answer got me most of the way there, however if you're using urls, there is the potential for false positive. For example `matchRuleShort("https://evil.com", "https://*.il.com)` evaluates to true! To prevent this I had to escape all non * characters with the escaped equivalent. `return new RegExp("^" + rule.replace(/[.?+^$[\]\\(){}|-]/g, "\\$&");.split("*").join(".*") + "$").test(str);` Regex borrowed from http://stackoverflow.com/a/2593661/238638 –  Feb 14 '17 at 15:38
  • @Spen, Doesn't it make more sense for `rule.split("*").join(".*")` to be non-greedy to reduce the potential to be exploited? `rule.split("*").join(".*?");` I haven't thought this through completely, yet. –  Feb 14 '17 at 15:45
  • @am17torres: The solution provided by me is for a special usecase where you want to test if something is in a normal string or not with wildcards. The solution is using normal regexps. An URL does contain regexp specific characters that would need to be escaped befor running the function provided in my answer. This is not a problem of my solution rather than problem of regexp not beeing a good solution for your usecase. – Spenhouet Feb 15 '17 at 15:52
  • @am17torres For your usecase you could write a function that replaces all regexp specific characters with something else (that normaly isn't in an URL and isn't a regexp specific char). You would need to do the same thing for the other string you are trying to match. The use of `?` wouldn't make any sense to me. The major take away for you should be that not every solution is for every problem. – Spenhouet Feb 15 '17 at 15:52
  • How would you modify the answer above to support question marks and tilde? - Asterisk - zero or more characters ((this is supported in the current answer) - Question mark (?) - any one character - Tilde (~) - escape for literal character (~*) a literal question mark (~?), or a literal tilde (~~). – Gian Sep 14 '19 at 14:35
11

You should use RegExp (they are awesome) an easy solution is:

if( /^bird/.test(animals[i]) ){
    // a bird :D
}
Davsket
  • 1,248
  • 1
  • 9
  • 14
10

Converter

This function convert wildcard to regexp and make test (it supports . and * wildcharts)

function wildTest(wildcard, str) {
  let w = wildcard.replace(/[.+^${}()|[\]\\]/g, '\\$&'); // regexp escape 
  const re = new RegExp(`^${w.replace(/\*/g,'.*').replace(/\?/g,'.')}$`,'i');
  return re.test(str); // remove last 'i' above to have case sensitive
}

function wildTest(wildcard, str) {
  let w = wildcard.replace(/[.+^${}()|[\]\\]/g, '\\$&'); // regexp escape 
  const re = new RegExp(`^${w.replace(/\*/g,'.*').replace(/\?/g,'.')}$`,'i');
  return re.test(str); // remove last 'i' above to have case sensitive
}


// Example usage

let arr = ["birdBlue", "birdRed", "pig1z", "pig2z", "elephantBlua" ];

let resultA = arr.filter( x => wildTest('biRd*', x) );
let resultB = arr.filter( x => wildTest('p?g?z', x) );
let resultC = arr.filter( x => wildTest('*Blu?', x) );

console.log('biRd*',resultA);
console.log('p?g?z',resultB);
console.log('*Blu?',resultC);
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
7

You could use Javascript's substring method. For example:

var list = ["bird1", "bird2", "pig1"]

for (var i = 0; i < list.length; i++) {
  if (list[i].substring(0,4) == "bird") {
   console.log(list[i]);
  }
}

Which outputs:

bird1
bird2

Basically, you're checking each item in the array to see if the first four letters are 'bird'. This does assume that 'bird' will always be at the front of the string.


So let's say your getting a pathname from a URL :

Let's say your at bird1?=letsfly - you could use this code to check the URL:

var listOfUrls = [
                  "bird1?=letsfly",
                  "bird",
                  "pigs?=dontfly",
                 ]

for (var i = 0; i < list.length; i++) {
  if (listOfUrls[i].substring(0,4) === 'bird') {
    // do something
  }
}

The above would match the first to URL's, but not the third (not the pig). You could easily swap out url.substring(0,4) with a regex, or even another javascript method like .contains()


Using the .contains() method might be a little more secure. You won't need to know which part of the URL 'bird' is at. For instance:

var url = 'www.example.com/bird?=fly'

if (url.contains('bird')) {
  // this is true
  // do something
}
Cody Reichert
  • 1,620
  • 15
  • 13
  • Thanks for your reply. In my real world end-scenarion the strings will by hyperlinks though and only the end will differ. And I'll be using document.URL to compare them, so I cant use RegEx, can I? To make it fit to the example: http://bird1 should be found/returned, and http://bird1?param=letsfly too Any suggestions? Which is the smartest way to do? – xqz313 Oct 07 '14 at 23:54
  • You definitely could use regex, but you don't need to. You could use a couple of things, really. I just updated my answer a bit, is that closer to what you are looking for? The list of URLS could come from anywhere, including `document.url`. – Cody Reichert Oct 08 '14 at 00:10
  • I think `url.substring(0,4)` will return `http` for all 3. That is if the `url` variable exists (`var url = list[i]`) – James Hay Oct 08 '14 at 00:13
  • Ah, good catch. Updated it to assume that the array is filled with pathname's, not URL's. Since that's not what the question was really about. Thanks – Cody Reichert Oct 08 '14 at 00:17
  • My problem is that it is the other way round. On the one hand I have a lot of different urls in the array (that basically all should be RegEx Expressions like http://bird1.com* that needs to be compared to document.url. SO either I should put /document.url*/ as a regex expression, which doesnt work I think, or I should have to put all strings in the array as regex expression with * in the end. I tried both and both methods dont work. Since the urls have a different lengths the substring method isn't really good either I think? :/ – xqz313 Oct 08 '14 at 00:20
  • I actually just updated using the `.contains()` method which i think will work better for you because you don't need to know where the string you're looking for is within the URL – Cody Reichert Oct 08 '14 at 00:23
  • I also came up with this but I think the regex expression is wrong cause it takes any url: `if(document.URL == url || (url + '/?param=*/'))` – xqz313 Oct 08 '14 at 00:29
  • what is assigned to `url`? – Cody Reichert Oct 08 '14 at 00:33
  • The whole loop: `for(var l=0;l – xqz313 Oct 08 '14 at 00:39
  • instead of `document.URL == url || (url + '/?filter=*/'))` try `document.URL == url || url.contains('bird').` In the case of `var url = www.example.com/bird?=1` the first check is false and the second check is true. If that doesn't work, try posting your code with the array and everything so we can take a look. – Cody Reichert Oct 08 '14 at 00:46
  • The problem in `'/?filter=*/'` is that * doesnt mean "anything" in RegEx, it means "the preceding character 0 or more times". If there was an Expression for "anything" I would be good I think. I will try your solution and report back in a second. – xqz313 Oct 08 '14 at 00:52
  • Still doesn't work but I found this: http://stackoverflow.com/questions/4419000/regex-match-everything-after-question-mark Current try: `if(document.URL == url || document.URL == url + '\?.*')` I think im close, I just need to figure out how to use RegEx correctly. Is 'RegEx' correct or am I doing it wrong? – xqz313 Oct 08 '14 at 01:06
  • contains is not supported in some browsers! [link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes), using indexof is safer! – hatef Nov 15 '16 at 10:25
  • `.contains()` is deprecated, use `.includes()` instead – FFmpegEnthusiast Jun 22 '23 at 21:06
1

When you have a rule wildcard string and a text string to match:

function wcMatch(rule, text) {
  return (new RegExp('^' + rule.replaceAll(/([.+?^=!:${}()|\[\]\/\\])/g, "\\$1").replaceAll('*', '(.*)') + '$')).test(text)
}

First replaceAll escapes special characters, second one replaces * with (.*) (an expression means "any zero or more characters")

For example, a string *&utm_* will be turned into an expression /^(.*)\&utm_(.*)$/

Mik
  • 374
  • 2
  • 13
1

We Can Simply Use The String.includes() & Array.filter() methods in conjuction for this.

let items = ["bird1", "bird2", "pig1", "pig2"]
let birds = items.filter((item) => {
    return item.includes("bird")
})

let pigs = items.filter((item) => {
    return item.includes("pig")
})

The filter() method creates a new array with all elements that pass the test implemented by the provided function.

The includes() method performs a case-sensitive search to determine whether one string may be found within another string, returning true or false as appropriate.

EDIT

As Suggested By @OXiGEN, Using String.prototype.startsWith() would be more performant & appropriate in this case.

let items = ["bird1", "bird2", "pig1", "pig2"]

let birds = items.filter(item => item.startsWith("bird"))
let pigs = items.filter(item => item.startsWith("pig"))

Using RegExp

Another Simple Solution Using Regex Would Be:

let items = ["bird1", "bird2", "pig1", "pig2"]

let birds = items.filter(item => item.match(/bird\d/g))
let pigs = items.filter(item => item.match(/pig\d/g))

Reference: Array.prototype.filter(), String.prototype.includes(), String.prototype.startsWith(), String.prototype.match(), Regular Expressions

  • 1
    Clean and more human-readable solution (compared to regex) for simpler searches. Some use case (such as filtering by namespaces) may benefit from `starstWith()` or `endsWith()` related methods. – OXiGEN Aug 18 '22 at 00:47
0
var searchArray = function(arr, str){
    // If there are no items in the array, return an empty array
    if(typeof arr === 'undefined' || arr.length === 0) return [];
    // If the string is empty return all items in the array
    if(typeof str === 'undefined' || str.length === 0) return arr;

    // Create a new array to hold the results.
    var res = [];

    // Check where the start (*) is in the string
    var starIndex = str.indexOf('*');

    // If the star is the first character...
    if(starIndex === 0) {

        // Get the string without the star.
        str = str.substr(1);
        for(var i = 0; i < arr.length; i++) {

            // Check if each item contains an indexOf function, if it doesn't it's not a (standard) string.
            // It doesn't necessarily mean it IS a string either.
            if(!arr[i].indexOf) continue;

            // Check if the string is at the end of each item.
            if(arr[i].indexOf(str) === arr[i].length - str.length) {                    
                // If it is, add the item to the results.
                res.push(arr[i]);
            }
        }
    }
    // Otherwise, if the star is the last character
    else if(starIndex === str.length - 1) {
        // Get the string without the star.
        str = str.substr(0, str.length - 1);
        for(var i = 0; i < arr.length; i++){
            // Check indexOf function                
            if(!arr[i].indexOf) continue;
            // Check if the string is at the beginning of each item
            if(arr[i].indexOf(str) === 0) {
                // If it is, add the item to the results.
                res.push(arr[i]);
            }
        }
    }
    // In any other case...
    else {            
        for(var i = 0; i < arr.length; i++){
            // Check indexOf function
            if(!arr[i].indexOf) continue;
            // Check if the string is anywhere in each item
            if(arr[i].indexOf(str) !== -1) {
                // If it is, add the item to the results
                res.push(arr[i]);
            }
        }
    }

    // Return the results as a new array.
    return res;
}

var birds = ['bird1','somebird','bird5','bird-big','abird-song'];

var res = searchArray(birds, 'bird*');
// Results: bird1, bird5, bird-big
var res = searchArray(birds, '*bird');
// Results: somebird
var res = searchArray(birds, 'bird');
// Results: bird1, somebird, bird5, bird-big, abird-song

There is an long list of caveats to a method like this, and a long list of 'what ifs' that are not taken into account, some of which are mentioned in other answers. But for a simple use of star syntax this may be a good starting point.

Fiddle

James Hay
  • 7,115
  • 6
  • 33
  • 57
  • Thanks for the awesome reply. Isn't this inbuilt in JS and can be used "out-of-the-box" though? :/ – xqz313 Oct 07 '14 at 23:58
  • No not really, the javascript built-in would be it's use of regex for pattern matching. @Davsket answer shows how you can do this and the comments on your question provide more details on how to use Regex. Personally, for something like this I would take the regex approach, but to specifically address your 'bird*' format this answer will suffice for simple usage. – James Hay Oct 08 '14 at 00:03
  • The Problem with Regex is that have is that in the real world scenario the content of the array will be hyperlinks and I will compare them to (document.URL). Let's say the string/url in the array is http://bird1.com If the documentUrl is http://bird1.com I need to return that, but if it is http://bird1.com?param=letsfly that needs to be returned, too. How do I solve that the most efficient way? :/ – xqz313 Oct 08 '14 at 00:06
  • Well I'm still going to say regex. Im terrible at it so I'm not a good person to ask but I guarantee you, someone will know how to do it in regex, or you could try learning yourself. An interesting library I found is [here](https://github.com/isaacs/minimatch) which converts global patterns `example: bird*` to regex under the good. Used by the node.js package manager so it's likely quite robust. – James Hay Oct 08 '14 at 00:10
  • Your solution is fine, but it's not gonna match e.g i have a word "hat" so if a star is in center of word like "h*t" it will not match the word "hat", rather it should match this. – Imran Ali Jul 10 '22 at 19:09
0

I used the answer by @Spenhouet and added more "replacements"-possibilities than "*". For example "?". Just add your needs to the dict in replaceHelper.

/**
 * @param {string} str
 * @param {string} rule
 * checks match a string to a rule
 * Rule allows * as zero to unlimited numbers and ? as zero to one character
 * @returns {boolean}
 */
function matchRule(str, rule) {
  const escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
  return new RegExp("^" + replaceHelper(rule, {"*": "\\d*", "?": ".?"}, escapeRegex) + "$").test(str);
}

function replaceHelper(input, replace_dict, last_map) {
  if (Object.keys(replace_dict).length === 0) {
    return last_map(input);
  }
  const split_by = Object.keys(replace_dict)[0];
  const replace_with = replace_dict[split_by];
  delete replace_dict[split_by];
  return input.split(split_by).map((next_input) => replaceHelper(next_input, replace_dict, last_map)).join(replace_with);
}
Lukas
  • 446
  • 3
  • 11
-3
if(mas[i].indexOf("bird") == 0)
    //there is bird

You.can read about indexOf here: http://www.w3schools.com/jsref/jsref_indexof.asp

lenden
  • 800
  • 2
  • 14
  • 38
  • 1
    Since the OP is a beginner, I'm sure they appreciate an **explanation**. – Felix Kling Oct 07 '14 at 23:02
  • 3
    Notice that this has a significant overhead on long strings that don't begin with `bird`. Using [`startsWith` might be the better solution](http://stackoverflow.com/q/646628/1048572) – Bergi Oct 07 '14 at 23:08
-9

Instead Animals == "bird*" Animals = "bird*" should work.