1

We all know that regExp can match two strings using a common word in both strings as criterion for the match. This code will match "I like apples" to "apples" because they both have "apples" in common.

var str1 = "apples";
var test1 = "I like apples";
var reg1 = new RegExp(str1, "i");

if (test1.match(reg1)) {
    console.log(str1 + " is matched");
}

But what if instead of matching the strings using a single common word (or a common part of the strings), I need to match the two strings using multiple common words which may be separated by other words ? Let me show you an example :

test2 = "I like juicy red fruits" should match str2 = "juicy fruits" because test2 contains all of str2 second key words (see object bellow), even though it has other words inbetween.

Tricky part is that I can't know exaclty what one the strings will be, so I have to match them dynamically. The string I don't know is the value of a user input field, and there are many possibilities. This value is to be matched to the strings stored in this object :

var str2 = {
    "red apples": "fruits",
    "juicy fruits": "price : $10"
};

So whatever the user types in the input field, it must be a match if and only if it contains all the words of one of the object properties. So in this example, "juicy red and ripe fruits" should match the second object property, because it contains all of its keywords.

Edit : My goal is to output the value associated to the strings I'm matching. In my example if the first key is matched, 'fruits' should be output. If 'juicy fruits' is matched, it should output 'price : $10'. Getting the strings associated to the object keys is the reason why the user has to search for them using the input.

Is it possible to do this with regExp using pure javascript ?

Here is what I (poorly) tried to do : https://jsfiddle.net/Hal_9100/fzhr0t9q/1/

4
  • Build a list of lookaheads for each word you want to check, like (?=.*\bjuicy\b)(?=.*\bfruits\b) Commented Oct 11, 2016 at 21:37
  • str2 is an object that has "red apples" and "juicy fruits" as keys. test2 only has "juicy", "red", and "apples". I don't understand the claim that, "test2 contains all of str2 words...". What am I misunderstanding? Commented Oct 11, 2016 at 21:52
  • @wcarroll : My sentence was wrongly worded : I wanted to say that test2 contains of all str2 second key words. I edited my post, thanks for pointing it out. Commented Oct 12, 2016 at 2:22
  • @Hal_9100 what's the deal with the stringified numbers as values in the str2 object? That part is still unclear to me. Is this data structure really necessary? Commented Oct 12, 2016 at 2:53

2 Answers 2

1

For the situation you're describing, you don't even need regular expressions. If you split the search string on spaces; you can check every one of the words to match is contained within the array of search words.

function matchesAllWords(searchWords, inputString) {
  var wordsToMatch = inputString.toLowerCase().split(' ');

  return wordsToMatch.every(
    word => searchWords.indexOf(word) >= 0);
}

In the snippet below, typing in the input causes a recalculation of the searchWords. The matching li elements are then given the .match class to highlight them.

function updateClasses(e) {
  var searchWords = e.target.value.toLowerCase().split(' ');

  listItems.forEach(listItem => listItem.classList.remove('match'));

  listItems.filter(
      listItem =>
      matchesAllWords(searchWords, listItem.innerText))
    .forEach(
      matchingListItem =>
      matchingListItem.classList.add('match'));
}

function matchesAllWords(searchWords, inputString) {
  var wordsToMatch = inputString.toLowerCase().split(' ');
  
  return wordsToMatch.every(
    word => searchWords.indexOf(word) >= 0);
}

function searchProperties(e) {
  var searchWords = e.target.value.toLowerCase().split(' ');

  for (var property in propertiesToSearch) {
    if (matchesAllWords(searchWords, property)) {
      console.log(property, propertiesToSearch[property]);
    }
  }
}

var propertiesToSearch = {
  "red apples": 1,
  "juicy fruit": 2
};

listItems = [].slice.call(
  document.getElementById('matches').querySelectorAll('li')
);

document.getElementById('search').addEventListener('keyup', updateClasses);
document.getElementById('search').addEventListener('keyup', searchProperties);
.match {
  color: green;
}
<label for="search">
  Search:
</label>
<input type="text" name="search" id="search" />

<ul id="matches">
  <li>red apples</li>
  <li>juicy fruits</li>
</ul>

Update To use this kind of implementation to search for a property, use a for .. in loop like below. Again, see the snippet for this working in context.

function searchProperties(e) {
  var searchWords = e.target.value.toLowerCase().split(' ');

  for (var property in propertiesToSearch) {
    if (matchesAllWords(searchWords, property)) {
      console.log(property, propertiesToSearch[property]);
    }
  }
}

var propertiesToSearch = {
  "red apples": 1,
  "juicy fruit": 2
};
Sign up to request clarification or add additional context in comments.

7 Comments

Thanks, your answer is really good. Problem is that I need to get the string/value (represented by numbers in my demo) associated to the strings I'm matching. That's why the user has to search for them in the first place. I tried to adapt your solution to make it work with keys in an object instead of text in DOM elements, but I can't get it to work. I'm not trying to search through the DOM but throught the keys of an object in order to get their value once the match is done. Is there any way to do this using your method ? Thanks again for the time and effort you put in your answer
You could use a for...in to loop through the properties, check if the input matchesAllWords in the property name, and if so, output the corresponding value (str2[key] or whatever)
@Hal_9100 See update – as qxz suggests, it's possible to use matchesAllWords to search keys using a for ... in loop.
Thank you, your code does almost exactly what I'm trying to do except for solving the matching problem : how to match an object key if and only if the input value contains all of its words, even though they are separated by other words ? The way your code works, 'red fruits' is matched even though I only type 'r' and it doesn't match the key anymore if I insert other words between 'red' and 'fruits' in the input. And that's where I'm stuck myself. How to code a function that will only match an object key to a string if it contains all of its words, even though the user types words inbetween ?
@Hal_9100 I've updated again now so that matchesAllWords checks that all the words of the matching string are contained in the list of words given in the search.
|
0

I think you might benefit from transforming your data structure from an object literal to an array like so:

const wordGroupings = [
  'red apples',
  'juicy fruits'
];

I wrote a processString function that accepts a string and some word-groupings as inputs and returns a filtered list of the word-groupings from where each word in the word-grouping occurs in the input string.

In other words let's imagine the test string is:

const testString = 'I like big, frozen whales.';

And let's further imagine that your wordGroupings array looked something like this:

const wordGroupings = [
  'whales frozen',
  'big frozen whales toucan',
  'big frozen',
  'sweaty dance club',
  'frozen whales big'
]

The output of calling processString(wordGroupings, testString) would be:

[
  'whales frozen',
  'big frozen',
  'frozen whales big'
]

If this is what you're looking for, here is the implementation of processString(..):

function processString(wordGroupings, testString) {
  var regexes = wordGroupings.map(words => ({
      origString: words,
      regex: new RegExp(words.replace(/\s+/g, '|'), 'g'),
      expCount: words.split(/\s+/g).length
    })
  );

  filtered = regexes.filter(({regex, expCount}) =>
    (testString.match(regex) || []).length === expCount
  );

  return filtered.map(dataObj => dataObj.origString);
}

Hope this helps!

4 Comments

Thank you so much for your answer, this function is really awsome. It is not what I was trying to do in the first place but this will really help with further projects. I actually used an object because my goal is to output the value associated to the strings I'm matching (which I oddly represented with stringified numbers ; I understand that this part was confusing in my question, sorry about that). Getting the value associated to the strings is the reason why the user has to search for them using the input. Is it possible to adapt your solution with an object instead of an array ?
@Hal_9100 I'm not sure I understand what you mean by "...the value associated to the strings...". What do you mean by value? In the context of your problem, how can a string have value? Is it the number of successful matches it registers with the input string?
By value I mean the string associated to the object key I'm matching In my example when 'red apples' is matched, it should output its associated string 'fruits'. This is why I used an object : var str2 = { "red apples": "fruits", "juicy fruits": "price : $10" };. If 'juicy fruits' is matched, it should output 'price : $10'. I edited my question to make it clearer.
@Hal_9100 Answering this question is beginning to feel like hitting a moving target. You should make sure that your question is appropriately phrased and the requirements are easily understood before posting it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.