Search code examples
javascriptarraysstringalgorithmsearch

How to check whether an array contains multiple values of a string of whitespace separated values?


Using an Angular application which inserts 'ng-star-inserted' to every node element. I need to check if the target element class exists inside array

enter image description here

var footerElementClassList = [
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ];

const filtered = footerElementClassList.includes('bottom__link ng-star-inserted');
console.log(filtered); 

Tried below options but no luck

Option 1

var footerElementClassList = [
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ];
var mySet = new Set(footerElementClassList);
var hasB = mySet.has('footer-links__link ng-star-inserted');
console.log(hasB); //false

Option 2

var footerElementClassList = [
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ];

function hasMatch(value) {
return value = 'bottom__link ng-star-inserted';
}
const filtered = footerElementClassList.filter(hasMatch);
console.log(filtered); //false

Option 3

var footerElementClassList = [
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ];
console.log(footerElementClassList?.contains('footer-links__link ng-star-inserted'));

Option 4

var footerElementClassList = [
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ];
const filtered = footerElementClassList.includes('bottom__link ng-star-inserted');
console.log(filtered); 

Solution

  • There are two data-formats ...

    • the array-based list of class-names
    • the search query provided as string of space separated class-names.

    Since one has to use comparison approaches which fit the different formats, an array of string-based name values versus a string of space-separated name-values, one can come up with mainly two approaches ...

    • split the string of class-names and iterate its resulting array in order to look whether the class-list array includes every of the split class-names which makes the approaches complexity quadratic (includes nested inside every) ...

      function hasEveryClassName(list, search) {
        return search
          .trim()
          .split(/\s+/)
          .every(value => list.includes(value));
      }
      
    • create a RegExp from the split and sorted string of class-names which then gets tested against the sorted and joined string-version of the provided array of class-names. The complexity comes with the approach itself which needs to sort both participants and has to create a valid/working regex like e.g. /\bbottom__link\b.*?\bng-star-inserted\b/ on the fly ...

      function hasEveryClassName(list, search) {
        return RegExp([
          '\\b',
          search
            .trim()
            .split(/\s+/)
            .sort()
            .join('\\b.*?\\b'),
          '\\b',
        ].join('')).test(list.sort().join(' '));
      }
      

    ... working example code ...

    const classList = [
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ];
    const classNames = 'bottom__link ng-star-inserted';
    
    
    function hasEveryClassName_v1(list, search) {
      return search
        .trim()
        .split(/\s+/)
        .every(value => list.includes(value));
    }
    console.log('\n... nested `every`/`includes` approach ...\n\n');
    
    console.log(
      `hasEveryClassName_v1(\n  ${ JSON.stringify(classList) },\n  ${ JSON.stringify(classNames) },\n) ...`,
      hasEveryClassName_v1(classList, classNames),
    );
    classList.push('ng-star-inserted');
    
    console.log(
      `hasEveryClassName_v1(\n  ${ JSON.stringify(classList) },\n  ${ JSON.stringify(classNames) },\n) ...`,
      hasEveryClassName_v1(classList, classNames),
    );
    classList.pop();
    
    
    function hasEveryClassName_v2(list, search) {
      return RegExp([
        '\\b',
        search
          .trim()
          .split(/\s+/)
          .sort()
          .join('\\b.*?\\b'),
        '\\b',
      ].join('')).test(list.sort().join(' '));
    }
    console.log('\n... concatenated sorted name string and regex test based approach ...\n\n');
    
    console.log(
      `hasEveryClassName_v2(\n  ${ JSON.stringify(classList) },\n  ${ JSON.stringify(classNames) },\n) ...`,
      hasEveryClassName_v2(classList, classNames),
    );
    classList.unshift('ng-star-inserted');
    
    console.log(
      `hasEveryClassName_v2(\n  ${ JSON.stringify(classList) },\n  ${ JSON.stringify(classNames) },\n) ...`,
      hasEveryClassName_v2(classList, classNames),
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    Edit ... since the OP with his latest update introduced Set as a possible structure to work with and also in order to respond to the OP's comment on this answer ...

    Is there any way much more simpler to achieve the solution?

    Of cause, though the first introduced every/includes based solution already is as straightforward as can be, the replacement of the array/list of class-names by a Set-based representation of this array is more performant.

    The before posted solution of ...

    function hasEveryClassName(list, search) {
      return search
        .trim()
        .split(/\s+/)
        .every(value => list.includes(value));
    }
    

    ... then changes to either ...

    function hasEveryClassName(list, search) {
      const lookup = new Set(list);
    
      return search
        .trim()
        .split(/\s+/)
        .every(value => lookup.has(value))
    }
    

    ... or, even better to ...

    function hasEveryClassName(lookup, search) {
      return search
        .trim()
        .split(/\s+/)
        .every(value => lookup.has(value));
    }
    

    ... where the latter variant/implementation needs the class-names to be provided already as Set data-type/structure.

    Switching from an array- to a set-structure reduces the solution's time complexity from quadratic to linear.

    ... working example code ...

    const classNameLookup = new Set([
      'footer-links__link',
      'social__link-icon',
      'bottom__link',
    ]);
    const classNames = 'bottom__link ng-star-inserted';
    
    function hasEveryClassName(lookup, search) {
      return search
        .trim()
        .split(/\s+/)
        .every(value => lookup.has(value));
    }
    
    console.log(
      `hasEveryClassName(\n  ${ JSON.stringify([...classNameLookup.values()]) },\n  ${ JSON.stringify(classNames) },\n) ...`,
      hasEveryClassName(classNameLookup, classNames),
    );
    classNameLookup.add('ng-star-inserted');
    
    console.log(
      `hasEveryClassName(\n  ${ JSON.stringify([...classNameLookup.values()]) },\n  ${ JSON.stringify(classNames) },\n) ...`,
      hasEveryClassName(classNameLookup, classNames),
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }