1

I'm trying to implement a search of an object with multiple key/value pairs that can be either string or number.

This is what I'm currently using (simplified, of course):

const input = 'Hello WORld 21';   // this could also be of type number
const search = 'O w';   // this could also be of type number
let result = true;
let startTime, endTime;
const t0 = performance.now();
for (let i = 0; i < 1000000; i++) {
    let parsedInput = JSON.stringify(input).toLowerCase().replace(/(^"|"$)/g, '');
    let parsedSearch = JSON.stringify(search).toLowerCase().replace(/(^"|"$)/g, '');
    result = parsedInput.indexOf(parsedSearch);
}
const t1 = performance.now();
console.log(`Call to doSomething took ${t1 - t0} milliseconds with result = ${result}`);
// ~393ms on a Ryzen 5600X

So this works, but seems to be expensive, especially when I go through tens of thousands of objects (or even more). So I wonder if there is a more elegant way to implement a search like this.

Thank you in advance!

Edit: Based on Sergio J's answer, here is what I got now:

const t2 = performance.now();
let i, s;
for (let j = 0; j < 1000000; j++) {
    i = isNaN(input) ? input.toLowerCase() : input.toString().toLowerCase();
    s = isNaN(search) ? search.toLowerCase() : search.toString().toLowerCase();
    result = i.indexOf(s);
}
const t3 = performance.now();
console.log(`Call to doSomething took ${t3 - t2} milliseconds with result = ${result}`);
// ~66ms on a Ryzen 5600X
Markstar
  • 639
  • 9
  • 24
  • why are you using `JSON.stringify` when the input isn't json? why not `toString()`? – depperm Apr 07 '22 at 11:03
  • if the code works, codereview SE might be a better place to ask – depperm Apr 07 '22 at 11:04
  • @depperm: Errm, stupidity? As weird as it sounds, I'm still rather new and I have not used `toString()` yet. Also, I was not aware of codereview, will keep it in mind - thank you! – Markstar Apr 07 '22 at 15:09

1 Answers1

3

you may not need that Json.stringify, just toString() in the cases where the input is a number (check with isNaN()).

There are some post here talking about the efficiency of different methods.

JavaScript: indexOf vs. Match when Searching Strings? check the link to find the most suitable for your needs and users.

Here is a code snippet with a indexOf version, extracted in a function, that checks input and search values to convert them to string if necessary, and tells you what has been found.

function stringFinderIndexOf(input, search) {
  let isNumber = !isNaN(input);
  if (isNumber) {
    input = input.toString();
  }

  let isSearchANumber = !isNaN(search);
  if (isSearchANumber) {
    search = search.toString();
  }

  let foundElement = input.toLowerCase().indexOf(search.toLowerCase()) !== -1;

  if (foundElement) {
    let isNumberText = isNumber ? "a numeric value" : "a text";
    console.log("found with indexOf in " + isNumberText);
  }
}

Of course, if you have as I understand an object, and you want to loop through, you could use Object.values() and a for loop.

function stringInObjectFinder(input, search) {

  let values = Object.values(input)
  for (const key in values) {
    stringFinderIndexOf(key, search);
  }

}

Find full code in a sandbox with a couple of functions using other methods as string.match(regex).

https://codesandbox.io/s/focused-chatterjee-gqijlr?file=/src/index.js

-------- Addition --------

Edit: as author suggested cleaner if, it is always a good idea to extract the if as a method with an explanatory name. In this case was left in order to put all code in a function to explain myself.

You can see in the function duplicated code already (smells).

function transformToStringIfNecessary(input) {
  let isInputANumber = !isNaN(input);
  if (isInputANumber) {
    input = input.toString();
  }
  return input;
}

function containsInputTheSearchValue(input, search) {
  return input.toLowerCase().indexOf(search.toLowerCase()) !== -1;
}

function stringFinderIndexOf(input, search) {
  let isNumber = !isNaN(input);
  input = transformToStringIfNecessary(input);
  search = transformToStringIfNecessary(search);
  let parsedInput = containsInputTheSearchValue(input, search);
  if (parsedInput) {
    let isNumberText = isNumber ? "a numeric value" : "a text";
    console.log("found with indexOf in " + isNumberText);
  }
}

You could clean the code even more, but guess you got the point. Hope it helps.

Sergio J
  • 61
  • 4
  • Thank you for your answer! Your apprach is ~6 times faster than mine, so that's definitely an improvement! I wonder if there is a way to optimize the `if`-clause away as well. I'm going to edit my OP with the results from your suggestion. – Markstar Apr 07 '22 at 14:58
  • 1
    Hi! Glad could help, I've edited the answer to clean up the code, extracting if out of the function. – Sergio J Apr 08 '22 at 09:33
  • Thanks so much for your edit! Interestingly, it is actually faster than your previous implementation (by 1-5%, depending on the inputs)! I did not expect that from Javascript. I know `C++` optimizes away many simple functions, but didn't expect this here! – Markstar Apr 08 '22 at 11:32