Moving Imperative to Declarative- character limitations - javascript

One module in my app pulls records from a mongodb and prints out the fields for the user to see. The fields are displayed in a particular order. The order is stored in an array. The mongodb document (pers) may not have all of the fields possible filled out. Anyway, here is an example of my original code. It works. I want to move onto something better. I will explain as I go.
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
let entries = []
let entry = ''
keys.forEach(key => {
if (pers[key]) entries.push(pers[key])
})
for (let i=0; i<entries.length; i++) {
if ((entries.length + entries[i].length < 10) entry += '\n' + entries[i]
else {
console.log(entry) //message.send in my app
entry = entries[i]
}
}
console.log(entry)
So in my app, the message that is sent cannot be longer than 2000 characters. I am using 10 characters for the example here in the post. And I do not want them split up in the middle either. So for instance 'word' + 'e t' is the maximum I can concatenate together. If I try to add 'scrabble' to it, the total length will be over 10 characters in length. So I want to send 'word e t' to the console and then start the variable entry over. I also do not want to send 'word' and then 'e' and then 't' and then 'scrabble'. Like I said before, my existing code works. I just want to try and be clear on my intent.
Moving forward... Below is my second iteration of the code. I'm trying to move away from imperative statements to declarative. So I replaced the forEach block. This also works great.
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
let entry = ''
const entries = keys.filter(key => pers.hasOwnProperty(key)).map(key => pers[key])
for (let i=0; i<entries.length; i++) {
if ((entries.length + entries[i].length < 10) entry += '\n' + entries[i]
else {
console.log(entry)
entry = entries[i]
}
}
console.log(entry)
The console output for both of these is:
word
e t
scrabble
That is exactly what I wanted. Now I really want to move away from that for block if possible. Surely there is a way to add another chain to my declarative statement to take care of that portion? I could possibly see a way of doing it if I didn't have the character limitation.
Anyway, if I have been unclear in any of my description or intent, please let me know and I will do my best to clarify.
Thanks!!

Try using .reduce like this. Your original code had a syntax error, so I'm assuming that was just a typo and not something important.
While that's functionally identical to your current code, it's still pretty convoluted. If I were you, I'd create an array of entryStrs while iterating, and then console.log them all later, like this:
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
const { currentMessage, messages } = keys
.filter(key => pers.hasOwnProperty(key))
.map(key => pers[key])
.reduce(({ currentMessage = '', messages = [] }, entry, i, entries) => {
if (entries.length + entry.length < 10) {
currentMessage += currentMessage
? ('\n' + entry)
: entry;
} else {
messages.push(currentMessage)
currentMessage = entry;
}
return { currentMessage, messages };
}, {});
const allMessages = [...messages, currentMessage];
console.log(allMessages.join('\n-----\n-----\n'));

since in the for block you have to iterate and change an external variable and not changing anything in the array itself, you can use Conditional (tenary) Operator and replace for with forEach()
const keys = ['banana', 'orange', 'apple', 'grape']
const pers = {
banana: 'word',
orange: 'e t',
apple: 'scrabble',
}
let entry = ''
const entries = keys.filter(key => pers.hasOwnProperty(key)).map(key => pers[key])
entries.forEach(e => {
entries.length + e.length < 10 ? entry += '\n' + e : (console.log(entry), entry = e)
});
console.log(entry)

Related

JavaScript Trying to Print The Number of Times a Letter Appears In String But It Prints More than Once

In the code below, I am trying to check how many times a letter in a string appears. Problem with the code below is that it prints each letter more than once. It needs to collect all the same letters and show the number of times it occurs in the string and display it once.
const string = 'mississippi'
const letters = [...string]
let currentLetter = ''
let letterOccurance = []
for(let i = 0; i < letters.length; i++){
let letterFrequency = letters.filter((letter)=>{
return letter === letters[i]
})
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
}
console.log(letterOccurance)
That's too much code just to get the number of times a letter appears in a string. Try the following code:
const string = 'mississippi';
let frequency = {};
for (let letter of string) {
if (frequency[letter]) {
frequency[letter]++;
} else {
frequency[letter] = 1;
}
}
console.log(frequency);
You're always pushing the letter to the array, whether it already exists there or not:
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
You could check if it exists first:
if (!letterOccurance.find(l => l[0] === letters[i])) {
letterOccurance.push([`${letters[i]}`,letterFrequency.length])
}
Or even skip it entirely if you've already seen it, since the first time you find any letter you already know its complete count:
for(let i = 0; i < letters.length; i++){
if (letterOccurance.find(l => l[0] === letters[i])) {
continue;
}
// the rest of the loop...
}
There's honestly a variety of ways you could step back and re-approach the problem. But for the question about why letters are repeating, that's simply because each iteration of the loop unconditionally appends the letter to the resulting array.
How about writing a more generic item-counting function and then layering countLetters as a simple partial application of the identity function?
const countBy = (fn) => ([...xs]) =>
xs .reduce ((a, x) => {const k = fn (x); a [k] = (a[k] || 0) + 1; return a}, {})
const countLetters = countBy (x => x)
console .log (countLetters ('missisippi'))
countBy is fairly generic. You pass it a function to convert your values to strings, and pass your array of items to the function it returns. Strings are array-like enough that this just works for our simple countLetters. But we could use it for other counts as well, such as:
countBy (x => x .grade) ([{id: 1, grade: 'A'}, {id: 2, grade: 'B'}, {id: 3, grade: 'A'}])
//=> {"A": 2, "B": 1}
Here's a solution using a Set to get the individual letters and String.split() to count.
const countChars = str => Object.fromEntries(
[...new Set(str)]
.map(c => [c, str.split(c).length-1])
)
console.log(countChars('mississippi'));
Using reduce to build the object
const countChars = str => [...str].reduce(
(a, c) => (a[c] ? a[c]++ : a[c]=1, a),
{}
)
console.log(countChars('mississippi'));
var result =

Algorithm Challenge: Fuzzy Search

I recently took part in an algorithm challenge to create a Fuzzy search with the following criteria:
Given a set array, create a function that receives one argument and returns a new array containing only the values that start with either:
A) The argument provided
B) The argument provided but with 1 difference (i.e. 1 incorrect letter)
The array was: fruits = [apple, apricot, banana, pear, mango, cherry, tomato]
so:
fuzzySearch('ap') = ['apple, apricot']
fuzzySearch('app') = ['apple', 'apricot']
fuzzySearch('appl') = ['apple']
fuzzySearch('pa') = ['banana', 'mango']
This is the solution I came up with:
const fruits = ['apple', 'apricot', 'banana', 'pear', 'mango', 'cherry', 'tomato']
function fuzzySearch(str) {
return fruits.filter(fruit =>
{
let letterCount = 0
const fruitLetArr = fruit.toLowerCase().split('')
const strArr = str.toLowerCase().split('')
for (var i = 0; i < strArr.length; i++) {
console.log(fruitLetArr[i], strArr[i], i, letterCount)
if (fruitLetArr[i] !== strArr[i]) letterCount++
if (letterCount === 2) break;
}
if (letterCount < 2) return true
});
}
fuzzySearch(str)
Can anyone think of a faster way that doesn't involve iterating over every value before a soltion can be found?
Here's something that should be slightly more efficient. Also easier to read. In this solution, I am assuming that by "difference" you mean a substitution of a letter for another letter, rather than the addition of another letter.
const fruits = ['apple', 'apricot', 'banana', 'pear', 'mango', 'cherry', 'tomato'];
const fuzzySearch = (str) => {
return fruits.filter((fruit) => {
// If our first case is met, immediately return
if (fruit.startsWith(str)) return true;
// Split the fruit based on the length of input string
const test = fruit.slice(0, str.length).split('');
let diffs = 0;
// Compare + keep track of differences between input + sliced fruit
test.forEach((letter, i) => letter !== str[i] && diffs++);
// If we have more than one difference, it doesn't meet case #2
if (diffs > 1) return false;
return true;
});
};
const testCases = ['ap', 'app', 'appl', 'pan', 'bp'];
for (const testCase of testCases) {
console.log(fuzzySearch(testCase));
}

How to compare two arrays of strings, find all matches, when there might be a sentence?

This is a fun one - I am building a profanity checker!
For the purposes of this exercise, let's have an array of forbidden words, like so:
const forbiddenWords = ['apples', 'oranges', 'blue carrots', 'mushy green peas'];
Then I will have an input field for a user to input the something. It could be an infinite combo of words, but I would like to find ALL instances of the forbidden words and return them as an array. I've thought of this function, which gets me really close:
const forbiddenWords = ['apples', 'oranges', 'blue carrots', 'mushy green peas'];
const userInput = 'Broccoli is my favorite food. I hate oranges and blue carrots';
const checkIfValidInput = () => {
// Will return an array that looks like: ['Broccoli', 'is', 'my', 'favorite', ...]
const splitWords = userInput.split(" ");
const array = [];
for (let i in forbiddenWords) {
if (splitWords.indexOf(forbiddenWords[i]) !== -1) {
array.push(forbiddenWords[i]);
}
}
return array.sort((x, y) => x - y);
};
Running the above will make the result of array be ['oranges', 'blue', 'carrots']. How could I build out the function to check for 'blue carrots' or 'mushy green peas' all in one? I'd like the above function to return: ['oranges', 'blue carrots'].
Thoughts?
You can use regex for this situation. This will also give you ability to match with case insensitivity
const checkIfValidInput = () => {
const forbiddenWords = ['apples', 'oranges', 'blue carrots', 'mushy green peas'];
const userInput = 'Broccoli is my favorite food. I hate oranges and blue carrots';
const result = forbiddenWords.reduce((acc, words) => {
const match = userInput.match(new RegExp(words, ['gi']))
return match ? acc.concat(match) : acc
}, [])
result.sort()
console.log(result)
}
checkIfValidInput()
Also, array.sort((x, y) => x - y); is not required as the values you have are string. You can rely on array.sort() or if you really want to do manual sort, try string.localeCompare(string2)
You're probably best off creating a regular expression. This way, you can skip the next to split and ignore capitalization issues
You can do this like
var regexStr = "";
for (let i = 0; i < forbiddenWords.length; i++) {
if (i != 0) regexStr += "|";
regexStr+= "(" + forbiddenWords[i] + ")";
}
var regex = RegExp.compile(regexStr,"gim");
var matches = userInput.match(regex);
You can check the sentence with every single phrases whether it's a match or not and if it's then you can return the phrase and store in a variable.
Technically, using of filter() and includes() would resolve the task. Lemme know if it helps.
let matched = forbiddenWords.filter(phrase => {
if(userInput.includes(phrase)) return phrase;
});
return matched;

Fast anagrams checker algorithm (HackerRank)

Problem:
Given two arrays of strings, for every string in list (query), determine how many anagrams of it are in the other list (dictionary).
It should return an array of integers.
Example:
query = ["a", "nark", "bs", "hack", "stair"]
dictionary = ['hack', 'a', 'rank', 'khac', 'ackh', 'kran', 'rankhacker', 'a', 'ab', 'ba', 'stairs', 'raits']
The answer would be [2, 2, 0, 3, 1] since query[0] ('a') has 2 anagrams in dictionary: 'a' and 'a' and so on...
This was the code I came up with:
function sortArray(array) {
let answer = [];
for(let i = 0; i< array.length ; i++) {
let data = array[i].split('').sort().join('');
answer.push(data);
}
return answer;
}
function stringAnagram(dictionary, query) {
// Write your code here
let sortedDict = sortArray(dictionary);
let sortedQuery = sortArray(query);
let answer = [];
console.log(sortedDict.length);
console.log(sortedQuery.length);
sortedQuery.map(data => {
let i = 0;
sortedDict.forEach(dictData => {
if(data === dictData)
i++;
})
answer.push(i);
})
return answer;
}
However it is returning timeout error for longer test cases. Need some help optimizing it. Any suggestions? I'm trying to achieve it in JavaScript.
You may want to avoid using (expensive) Array.prototype.sort() to detect anagram and give your anagram detection algorithm as much shortcuts as possible.
So, if assume, anagrams should be the strings of the same length with the same count of the same characters, you may go something, like that:
const query = ["a", "nark", "bs", "hack", "stair"],
dictionary = ['hack', 'a', 'rank', 'khac', 'ackh', 'kran', 'rankhacker', 'a', 'ab', 'ba', 'stairs', 'raits'],
charCount = s => [...s].reduce((acc,c) =>
(acc[c]=(acc[c]||0)+1, acc), {}),
areAnagrams = (s1, s2) => {
if(s1.length != s2.length) return false
const s1CharCount = charCount(s1),
s2CharCount = charCount(s2),
result = Object
.keys(s1CharCount)
.every(char =>
s2CharCount[char] == s1CharCount[char])
return result
},
outcome = query.map(word =>
dictionary
.filter(_word => areAnagrams(word, _word))
.length
)
console.log(outcome)
Slightly more verbose way of doing it - but it works and makes sense to me - for each word in the original array, find the words in the target array that are the same length and then count those that are anagrams of the original word (an anagram is another word made up of the same letters in any order).
So the steps are -
Iterate over the firat array and for each word - filter the target array to get all words that are the same length as that word (potentialAnagrams)
Then iterate over that potentialAnagrams array and pass each word to a function that checks if all the letters and only the letters in the original word are present (in the given example - that would be [2, 2, 0, 3, 1]
Add up all anagrams of the word and pass the count to an array that is logged as the final result.
const queryArr = ["a", "nark", "bs", "hack", "stair"];
const dictionaryArr = ['hack', 'a', 'rank', 'khac', 'ackh', 'kran', 'rankhacker', 'a', 'ab', 'ba', 'stairs', 'raits'];
let anagramsArr = [];
queryArr.forEach(function(query){
let anagramsCount = 0
const potentialAnagrams = dictionaryArr.filter(el => el.length === query.length);
potentialAnagrams.forEach(function(potentialAnagram){
if(isAnagram(query,potentialAnagram)){
anagramsCount++
}
})
anagramsArr.push(anagramsCount);
})
function isAnagram(word1, word2){
let count = 0;
const word1Arr = word1.split('');
const word2Arr = word2.split('');
if( word1Arr.length !== word2Arr.length) {
return 'Invalid data - words 1 and 2 are of different lengths';
}
word1Arr.forEach(function(letter){
if(word2.indexOf(letter) !== -1) {
count++
}
})
return count === word1Arr.length
}
console.log(isAnagram('ab', 'bab')); //gives 'Invalid data - words 1 and 2 are of different lengths';
console.log(anagramsArr); //gives [2, 2, 0, 3,1];

Check if an array contains any element of another array in JavaScript

I have a target array ["apple","banana","orange"], and I want to check if other arrays contain any one of the target array elements.
For example:
["apple","grape"] //returns true;
["apple","banana","pineapple"] //returns true;
["grape", "pineapple"] //returns false;
How can I do it in JavaScript?
Vanilla JS
ES2016:
const found = arr1.some(r=> arr2.includes(r))
ES6:
const found = arr1.some(r=> arr2.indexOf(r) >= 0)
How it works
some(..) checks each element of the array against a test function and returns true if any element of the array passes the test function, otherwise, it returns false. indexOf(..) >= 0 and includes(..) both return true if the given argument is present in the array.
vanilla js
/**
* #description determine if an array contains one or more items from another array.
* #param {array} haystack the array to search.
* #param {array} arr the array providing items to check for in the haystack.
* #return {boolean} true|false if haystack contains at least one item from arr.
*/
var findOne = function (haystack, arr) {
return arr.some(function (v) {
return haystack.indexOf(v) >= 0;
});
};
As noted by #loganfsmyth you can shorten it in ES2016 to
/**
* #description determine if an array contains one or more items from another array.
* #param {array} haystack the array to search.
* #param {array} arr the array providing items to check for in the haystack.
* #return {boolean} true|false if haystack contains at least one item from arr.
*/
const findOne = (haystack, arr) => {
return arr.some(v => haystack.includes(v));
};
or simply as arr.some(v => haystack.includes(v));
If you want to determine if the array has all the items from the other array, replace some() to every()
or as arr.every(v => haystack.includes(v));
ES6 solution:
let arr1 = [1, 2, 3];
let arr2 = [2, 3];
let isFounded = arr1.some( ai => arr2.includes(ai) );
Unlike of it: Must contains all values.
let allFounded = arr2.every( ai => arr1.includes(ai) );
Hope, will be helpful.
If you're not opposed to using a libray, http://underscorejs.org/ has an intersection method, which can simplify this:
var _ = require('underscore');
var target = [ 'apple', 'orange', 'banana'];
var fruit2 = [ 'apple', 'orange', 'mango'];
var fruit3 = [ 'mango', 'lemon', 'pineapple'];
var fruit4 = [ 'orange', 'lemon', 'grapes'];
console.log(_.intersection(target, fruit2)); //returns [apple, orange]
console.log(_.intersection(target, fruit3)); //returns []
console.log(_.intersection(target, fruit4)); //returns [orange]
The intersection function will return a new array with the items that it matched and if not matches it returns empty array.
ES6 (fastest)
const a = ['a', 'b', 'c'];
const b = ['c', 'a', 'd'];
a.some(v=> b.indexOf(v) !== -1)
ES2016
const a = ['a', 'b', 'c'];
const b = ['c', 'a', 'd'];
a.some(v => b.includes(v));
Underscore
const a = ['a', 'b', 'c'];
const b = ['c', 'a', 'd'];
_.intersection(a, b)
DEMO: https://jsfiddle.net/r257wuv5/
jsPerf: https://jsperf.com/array-contains-any-element-of-another-array
If you don't need type coercion (because of the use of indexOf), you could try something like the following:
var arr = [1, 2, 3];
var check = [3, 4];
var found = false;
for (var i = 0; i < check.length; i++) {
if (arr.indexOf(check[i]) > -1) {
found = true;
break;
}
}
console.log(found);
Where arr contains the target items. At the end, found will show if the second array had at least one match against the target.
Of course, you can swap out numbers for anything you want to use - strings are fine, like your example.
And in my specific example, the result should be true because the second array's 3 exists in the target.
UPDATE:
Here's how I'd organize it into a function (with some minor changes from before):
var anyMatchInArray = (function () {
"use strict";
var targetArray, func;
targetArray = ["apple", "banana", "orange"];
func = function (checkerArray) {
var found = false;
for (var i = 0, j = checkerArray.length; !found && i < j; i++) {
if (targetArray.indexOf(checkerArray[i]) > -1) {
found = true;
}
}
return found;
};
return func;
}());
DEMO: http://jsfiddle.net/u8Bzt/
In this case, the function could be modified to have targetArray be passed in as an argument instead of hardcoded in the closure.
UPDATE2:
While my solution above may work and be (hopefully more) readable, I believe the "better" way to handle the concept I described is to do something a little differently. The "problem" with the above solution is that the indexOf inside the loop causes the target array to be looped over completely for every item in the other array. This can easily be "fixed" by using a "lookup" (a map...a JavaScript object literal). This allows two simple loops, over each array. Here's an example:
var anyMatchInArray = function (target, toMatch) {
"use strict";
var found, targetMap, i, j, cur;
found = false;
targetMap = {};
// Put all values in the `target` array into a map, where
// the keys are the values from the array
for (i = 0, j = target.length; i < j; i++) {
cur = target[i];
targetMap[cur] = true;
}
// Loop over all items in the `toMatch` array and see if any of
// their values are in the map from before
for (i = 0, j = toMatch.length; !found && (i < j); i++) {
cur = toMatch[i];
found = !!targetMap[cur];
// If found, `targetMap[cur]` will return true, otherwise it
// will return `undefined`...that's what the `!!` is for
}
return found;
};
DEMO: http://jsfiddle.net/5Lv9v/
The downside to this solution is that only numbers and strings (and booleans) can be used (correctly), because the values are (implicitly) converted to strings and set as the keys to the lookup map. This isn't exactly good/possible/easily done for non-literal values.
Using filter/indexOf:
function containsAny(source,target)
{
var result = source.filter(function(item){ return target.indexOf(item) > -1});
return (result.length > 0);
}
//results
var fruits = ["apple","banana","orange"];
console.log(containsAny(fruits,["apple","grape"]));
console.log(containsAny(fruits,["apple","banana","pineapple"]));
console.log(containsAny(fruits,["grape", "pineapple"]));
You could use lodash and do:
_.intersection(originalTarget, arrayToCheck).length > 0
Set intersection is done on both collections producing an array of identical elements.
const areCommonElements = (arr1, arr2) => {
const arr2Set = new Set(arr2);
return arr1.some(el => arr2Set.has(el));
};
Or you can even have a better performance if you first find out which of these two arrays is longer and making Set out for the longest array, while applying some method on the shortest one:
const areCommonElements = (arr1, arr2) => {
const [shortArr, longArr] = (arr1.length < arr2.length) ? [arr1, arr2] : [arr2, arr1];
const longArrSet = new Set(longArr);
return shortArr.some(el => longArrSet.has(el));
};
I wrote 3 solutions. Essentially they do the same. They return true as soon as they get true. I wrote the 3 solutions just for showing 3 different way to do things. Now, it depends what you like more. You can use performance.now() to check the performance of one solution or the other. In my solutions I'm also checking which array is the biggest and which one is the smallest to make the operations more efficient.
The 3rd solution may not be the cutest but is efficient. I decided to add it because in some coding interviews you are not allowed to use built-in methods.
Lastly, sure...we can come up with a solution with 2 NESTED for loops (the brute force way) but you want to avoid that because the time complexity is bad O(n^2).
Note:
instead of using .includes() like some other people did, you can use
.indexOf(). if you do just check if the value is bigger than 0. If
the value doesn't exist will give you -1. if it does exist, it will give you
greater than 0.
indexOf() vs includes()
Which one has better performance? indexOf() for a little bit, but includes is more readable in my opinion.
If I'm not mistaken .includes() and indexOf() use loops behind the scene, so you will be at O(n^2) when using them with .some().
USING loop
const compareArraysWithIncludes = (arr1, arr2) => {
const [smallArray, bigArray] =
arr1.length < arr2.length ? [arr1, arr2] : [arr2, arr1];
for (let i = 0; i < smallArray.length; i++) {
return bigArray.includes(smallArray[i]);
}
return false;
};
USING .some()
const compareArraysWithSome = (arr1, arr2) => {
const [smallArray, bigArray] =
arr1.length < arr2.length ? [arr1, arr2] : [arr2, arr1];
return smallArray.some(c => bigArray.includes(c));
};
USING MAPS Time complexity O(2n)=>O(n)
const compararArraysUsingObjs = (arr1, arr2) => {
const map = {};
const [smallArray, bigArray] =
arr1.length < arr2.length ? [arr1, arr2] : [arr2, arr1];
for (let i = 0; i < smallArray.length; i++) {
if (!map[smallArray[i]]) {
map[smallArray[i]] = true;
}
}
for (let i = 0; i < bigArray.length; i++) {
if (map[bigArray[i]]) {
return true;
}
}
return false;
};
Code in my:
stackblitz
I'm not an expert in performance nor BigO so if something that I said is wrong let me know.
You can use a nested Array.prototype.some call. This has the benefit that it will bail at the first match instead of other solutions that will run through the full nested loop.
eg.
var arr = [1, 2, 3];
var match = [2, 4];
var hasMatch = arr.some(a => match.some(m => a === m));
I found this short and sweet syntax to match all or some elements between two arrays. For example
// OR operation. find if any of array2 elements exists in array1. This will return as soon as there is a first match as some method breaks when function returns TRUE
let array1 = ['a', 'b', 'c', 'd', 'e'], array2 = ['a', 'b'];
console.log(array2.some(ele => array1.includes(ele)));
// prints TRUE
// AND operation. find if all of array2 elements exists in array1. This will return as soon as there is a no first match as some method breaks when function returns TRUE
let array1 = ['a', 'b', 'c', 'd', 'e'], array2 = ['a', 'x'];
console.log(!array2.some(ele => !array1.includes(ele)));
// prints FALSE
Hope that helps someone in future!
Just one more solution
var a1 = [1, 2, 3, 4, 5]
var a2 = [2, 4]
Check if a1 contain all element of a2
var result = a1.filter(e => a2.indexOf(e) !== -1).length === a2.length
console.log(result)
What about using a combination of some/findIndex and indexOf?
So something like this:
var array1 = ["apple","banana","orange"];
var array2 = ["grape", "pineapple"];
var found = array1.some(function(v) { return array2.indexOf(v) != -1; });
To make it more readable you could add this functionality to the Array object itself.
Array.prototype.indexOfAny = function (array) {
return this.findIndex(function(v) { return array.indexOf(v) != -1; });
}
Array.prototype.containsAny = function (array) {
return this.indexOfAny(array) != -1;
}
Note: If you'd want to do something with a predicate you could replace the inner indexOf with another findIndex and a predicate
Here is an interesting case I thought I should share.
Let's say that you have an array of objects and an array of selected filters.
let arr = [
{ id: 'x', tags: ['foo'] },
{ id: 'y', tags: ['foo', 'bar'] },
{ id: 'z', tags: ['baz'] }
];
const filters = ['foo'];
To apply the selected filters to this structure we can
if (filters.length > 0)
arr = arr.filter(obj =>
obj.tags.some(tag => filters.includes(tag))
);
// [
// { id: 'x', tags: ['foo'] },
// { id: 'y', tags: ['foo', 'bar'] }
// ]
Good perfomance solution:
We should transform one of arrays to object.
const contains = (arr1, mainObj) => arr1.some(el => el in mainObj);
const includes = (arr1, mainObj) => arr1.every(el => el in mainObj);
Usage:
const mainList = ["apple", "banana", "orange"];
// We make object from array, you can use your solution to make it
const main = Object.fromEntries(mainList.map(key => [key, true]));
contains(["apple","grape"], main) // => true
contains(["apple","banana","pineapple"], main) // => true
contains(["grape", "pineapple"], main) // => false
includes(["apple", "grape"], main) // => false
includes(["banana", "apple"], main) // => true
you can face with some disadvantage of checking by in operator (eg 'toString' in {} // => true), so you can change solution to obj[key] checker
Adding to Array Prototype
Disclaimer: Many would strongly advise against this. The only time it'd really be a problem was if a library added a prototype function with the same name (that behaved differently) or something like that.
Code:
Array.prototype.containsAny = function(arr) {
return this.some(
(v) => (arr.indexOf(v) >= 0)
)
}
Without using big arrow functions:
Array.prototype.containsAny = function(arr) {
return this.some(function (v) {
return arr.indexOf(v) >= 0
})
}
Usage
var a = ["a","b"]
console.log(a.containsAny(["b","z"])) // Outputs true
console.log(a.containsAny(["z"])) // Outputs false
My solution applies Array.prototype.some() and Array.prototype.includes() array helpers which do their job pretty efficient as well
ES6
const originalFruits = ["apple","banana","orange"];
const fruits1 = ["apple","banana","pineapple"];
const fruits2 = ["grape", "pineapple"];
const commonFruits = (myFruitsArr, otherFruitsArr) => {
return myFruitsArr.some(fruit => otherFruitsArr.includes(fruit))
}
console.log(commonFruits(originalFruits, fruits1)) //returns true;
console.log(commonFruits(originalFruits, fruits2)) //returns false;
When I looked at your answers, I could not find the answer I wanted.
I did something myself and I want to share this with you.
It will be true only if the words entered (array) are correct.
function contains(a,b) {
let counter = 0;
for(var i = 0; i < b.length; i++) {;
if(a.includes(b[i])) counter++;
}
if(counter === b.length) return true;
return false;
}
let main_array = ['foo','bar','baz'];
let sub_array_a = ['foo','foobar'];
let sub_array_b = ['foo','bar'];
console.log(contains(main_array, sub_array_a)); // returns false
console.log(contains(main_array,sub_array_b )); // returns true
Array .filter() with a nested call to .find() will return all elements in the first array that are members of the second array. Check the length of the returned array to determine if any of the second array were in the first array.
getCommonItems(firstArray, secondArray) {
return firstArray.filter((firstArrayItem) => {
return secondArray.find((secondArrayItem) => {
return firstArrayItem === secondArrayItem;
});
});
}
It can be done by simply iterating across the main array and check whether other array contains any of the target element or not.
Try this:
function Check(A) {
var myarr = ["apple", "banana", "orange"];
var i, j;
var totalmatches = 0;
for (i = 0; i < myarr.length; i++) {
for (j = 0; j < A.length; ++j) {
if (myarr[i] == A[j]) {
totalmatches++;
}
}
}
if (totalmatches > 0) {
return true;
} else {
return false;
}
}
var fruits1 = new Array("apple", "grape");
alert(Check(fruits1));
var fruits2 = new Array("apple", "banana", "pineapple");
alert(Check(fruits2));
var fruits3 = new Array("grape", "pineapple");
alert(Check(fruits3));
DEMO at JSFIDDLE
Not sure how efficient this might be in terms of performance, but this is what I use using array destructuring to keep everything nice and short:
const shareElements = (arr1, arr2) => {
const typeArr = [...arr1, ...arr2]
const typeSet = new Set(typeArr)
return typeArr.length > typeSet.size
}
Since sets cannot have duplicate elements while arrays can, combining both input arrays, converting it to a set, and comparing the set size and array length would tell you if they share any elements.
A short way of writing this:
const found = arr1.some(arr2.includes)
With underscorejs
var a1 = [1,2,3];
var a2 = [1,2];
_.every(a1, function(e){ return _.include(a2, e); } ); //=> false
_.every(a2, function(e){ return _.include(a1, e); } ); //=> true
Vanilla JS with partial matching & case insensitive
The problem with some previous approaches is that they require an exact match of every word. But, What if you want to provide results for partial matches?
function search(arrayToSearch, wordsToSearch) {
arrayToSearch.filter(v =>
wordsToSearch.every(w =>
v.toLowerCase().split(" ").
reduce((isIn, h) => isIn || String(h).indexOf(w) >= 0, false)
)
)
}
//Usage
var myArray = ["Attach tag", "Attaching tags", "Blah blah blah"];
var searchText = "Tag attach";
var searchArr = searchText.toLowerCase().split(" "); //["tag", "attach"]
var matches = search(myArray, searchArr);
//Will return
//["Attach tag", "Attaching tags"]
This is useful when you want to provide a search box where users type words and the results can have those words in any order, position and case.
Update #Paul Grimshaw answer, use includes insteed of indexOf for more readable
let found = arr1.some(r=> arr2.indexOf(r) >= 0)
let found = arr1.some(r=> arr2.includes(r))
I came up with a solution in node using underscore js like this:
var checkRole = _.intersection(['A','B'], ['A','B','C']);
if(!_.isEmpty(checkRole)) {
next();
}
You are looking for intersection between the two arrays. And you have two major intersection types: 'every' and 'some'. Let me give you good examples:
EVERY
let brands1 = ['Ford', 'Kia', 'VW', 'Audi'];
let brands2 = ['Audi', 'Kia'];
// Find 'every' brand intersection.
// Meaning all elements inside 'brands2' must be present in 'brands1':
let intersectionEvery = brands2.every( brand => brands1.includes(brand) );
if (intersectionEvery) {
const differenceList = brands1.filter(brand => !brands2.includes(brand));
console.log('difference list:', differenceList);
const commonList = brands1.filter(brand => brands2.includes(brand));
console.log('common list:', commonList);
}
If condition is not met (like if you put 'Mercedes' in brands2) then 'intersectionEvery' won't be satisfied - will be bool false.
If condition is met it will log ["Ford", "VW"] as difference and ["Kia", "Audi"] as common list.
Sandbox: https://jsfiddle.net/bqmg14t6/
SOME
let brands1 = ['Ford', 'Kia', 'VW', 'Audi'];
let brands2 = ['Audi', 'Kia', 'Mercedes', 'Land Rover'];
// Find 'some' brand intersection.
// Meaning some elements inside 'brands2' must be also present in 'brands1':
let intersectionSome = brands2.some( brand => brands1.includes(brand) );
if (intersectionSome) {
const differenceList = brands1.filter(brand => !brands2.includes(brand));
console.log('difference list:', differenceList);
const commonList = brands1.filter(brand => brands2.includes(brand));
console.log('common list:', commonList);
}
Here we are looking for some common brands, not necessarily all.
It will log ["Ford", "VW"] as difference and ["Kia", "Audi"] as common brands.
Sandbox: https://jsfiddle.net/zkq9j3Lh/
Personally, I would use the following function:
var arrayContains = function(array, toMatch) {
var arrayAsString = array.toString();
return (arrayAsString.indexOf(','+toMatch+',') >-1);
}
The "toString()" method will always use commas to separate the values. Will only really work with primitive types.
console.log("searching Array: "+finding_array);
console.log("searching in:"+reference_array);
var check_match_counter = 0;
for (var j = finding_array.length - 1; j >= 0; j--)
{
if(reference_array.indexOf(finding_array[j]) > 0)
{
check_match_counter = check_match_counter + 1;
}
}
var match = (check_match_counter > 0) ? true : false;
console.log("Final result:"+match);

Categories