Java Script - How to search object within array efficiently [duplicate] - javascript

This question already has answers here:
JS - jQuery inarray ignoreCase() and contains()
(2 answers)
Closed 9 years ago.
I need to find an object (a string to be precise) inside a large array. While the below code works, it's tediously scrolling through each element of the array, a brute method. Is there a more efficient method? possibly calling on .search or .match or equivalent? Also how to make the search object (string) case insensitive? i.e object might be "abc" while array element is "ABC".
Many thanks in advance
function SearchArray(array, object){ //need to modify code to become case insensitive.
for (var i= 1; i< array.length; i++){
if (array[i] == object.toString()){
return i;
}
}
return 0;
}
I also forgot to mention that the search returns the index / position of the matched object within the one dimensional array, rather than simple true / false.

The following function does just that:
function findWord(array, word) {
return -1 < array.map(function(item) { return item.toLowerCase(); }).indexOf(word.toLowerCase());
}
What it does is:
Converting every string to lower case using the map() function.
Searching for a certain word also in lower case mode.
If the word is found, the function returns a value greater than -1, therefore the return value is either true or false

If you are only going to search the large array once then the only likely optimization is to store the object string representation instead of generating it before each comparison:
function SearchArray(array, object) {
var len=array.length, str=object.toString().toLowerCase();
for (var i=0; i<len; i++) {
if (array[i].toLowerCase() == str) { return i; }
}
return -1; // Return -1 per the "Array.indexOf()" method.
}
However, if you will be searching for many objects within the array then you will save time by storing the lower-case version of the elements:
var lowerArray = array.map(function(x){return x.toString().toLowerCase();});
var lowerObject = object.toString().toLowerCase();
lowerArray.indexOf(lowerObject); // Simply use "Array.indexOf()".
Moreover, if you will be searching this array many times, plenty of memory is available, and performance is critical then you should consider using an object for O(1) lookup:
function makeLowerCaseArrayIndexLookupFunction(array) {
var lookup = array.reduce(function(memo, x, i) {
memo[x.toString().toLowerCase()] = i;
return memo;
}, {});
return function(obj) {
var idx = lookup[obj.toString().toLowerCase()];
return (typeof(idx)==='undefined') ? -1 : idx;
}
}
var findWeekdays = makeLowerCaseArrayIndexLookupFunction([
'Mon', 'Tues', 'Weds', 'Thurs', 'Fri', 'Sat', 'Sun'
]);
findWeekdays('mon'); // => 0
findWeekdays('FRI'); // => 4
findWeekdays('x'); // => -1

Related

Isomorphic Strings Function Always Returns True

I am attempting the Isomorphic Strings problem on LeetCode and am having issues with my current solution. I'm sure there are plenty of answers on exactly how to complete this problem, but I would really prefer to finish it through my own thought process before learning the best possible way to do it. For reference, here is the problem: https://leetcode.com/problems/isomorphic-strings/?envType=study-plan&id=level-1
This is my code as it is right now:
var isIsomorphic = function(s, t) {
const map = new Map();
const array1 = [...s];
const array2 = [...t];
for (i = 0; i < s.length; i++) {
if ((map.has(array1[i]) === true) && (map.has(array2[i]) === true)) {
if (map.get(array1[i]) !== array2[i]) {
return false;
} else {
continue;
}
} else if (map.has(array1[i]) === false) {
map.set(array1[i], array2[i]);
}
}
return true;
};
It's messy but I can't figure out why it isn't giving me the desired results. Right now, it seems to always return true for any given values, even though I have the initial if statement to return false if it ever comes across previously-mapped values that don't match. Am I missing something obvious? This is my first question on SA, so I apologize if the format is wrong.
The map is set like:
map.set(array1[i], array2[i]);
The key is the character in the first string, and the value is the corresponding character in the second string. So, when iterating over a new character, checking map.has will only make sense if the character being passed is from the first string; doing map.has(array2[i]) === true)) does not test anything useful, because the second string's characters are not keys of the Map.
You need to perform two tests: that the 1st string's character corresponds to the 2nd string's character (which you're doing right), and that the 2nd string's character is not already set to a different 1st string's character (which needs to be fixed). For this second condition, consider having another Map that's the reverse of the first one - the keys are the characters from the 2nd string, and the values are the characters from the 1st string. (You don't have to have another Map - you could also iterate through the .entries of the first, check that, for every entry's value that matches the 2nd character, the entry's key matches the 1st - but that could feel a bit messy.)
Cleaning up your code some, there's also no need to turn the strings into arrays, and === true can be omitted entirely, and the i variable should be declared with let.
You also might want to check if the length of the first string is equal to the length of the second.
var isIsomorphic = function(s1, s2) {
if (s1.length !== s2.length) return false;
const map1to2 = new Map();
const map2to1 = new Map();
for (let i = 0; i < s1.length; i++) {
// Check that s1 here corresponds to s2
if (map1to2.has(s1[i]) && map1to2.get(s1[i]) !== s2[i]) {
return false;
}
// And that s2 here doesn't correspond to any other s1
if (map2to1.has(s2[i]) && map2to1.get(s2[i]) !== s1[i]) {
return false;
}
map1to2.set(s1[i], s2[i]);
map2to1.set(s2[i], s1[i]);
}
return true;
};
console.log(isIsomorphic('aa', 'bb'));
console.log(isIsomorphic('aa', 'ba'));
console.log(isIsomorphic('badc', 'baba'));

i'm trying to use a for..loop to describe the .endsWith() method

I'm basically trying to write a function which when called, performs the action of the endsWith() method
I've tried iterating through the original string and the string to be tested using for loops
function confirmEnding(str, target) {
let strWord = '';
let targetWord = '';
for (let i = (target.length - 1); i >= 0; i--) {
targetWord.concat(target.charAt(i))
for (let j = (str.length - 1); j >= 0; j--) {
strWord.concat(str.charAt(j));
}
}
if (strWord == targetWord) {
return true
} else {
return false
}
}
Now anytime i call the function, it returns true, what's wrong with my code?
concat returns the new string.
Use strWord += whatever; instead.
Primarily, what's wrong with it is that you're using nested loops. There's no need to do that.
The simple, easy way (other than using endsWith!) is to grab the end of str (using the length of target), then compare that substring against target:
function confirmEnding(str, target) {
return str.length >= target.length && str.slice(-target.length) === target;
}
Re your actual code:
concat doesn't modify the string you call it on, it returns a new string (it has to; strings are immutable [unchangeable] in JavaScript)
If you fix #1, your outer loop just copies target to targetWord but in reverse order. Your inner loop copies str to strWord in reverse order repeatedly, so you'll end up with target.length copies of it in strWord.
Nothing in the function tries to take only part of str (the last part, with the same length as target). You end up comparing the full strings (if you fix #1 and #2), not the substring at the end of str.
Finally, just a side note: Any time you find yourself writing:
if (a == b) {
return true;
} else {
return false;
}
you can more concisely and idiomatically write
return a == b;
which does exactly the same thing. :-)

if statement evaluates incorrectly

function ipsBetween(start, end){
var count = 0;
for(var i = 0; i < 4; i++) {
if(start.split('.').slice(i, i + 1) != end.split('.').slice(i, i + 1)) {
count++;
}
}
return count;
}
I am trying to find all possible IP's between a range. The above code is just a starting. I was trying to split the IP in pieces and check if they are equal or not. While I was doing so, interesingly even if the values are equal it evaluates the if statement as true and increases the count. Here is my test case:
ipsBetween("10.0.0.0", "10.0.0.50")
This test case returns 4, whereas it should return 1. I don't know why this is happening. I implicity looked the values of start.split('.').slice(i, i + 1) and end.split('.').slice(i, i + 1) and the first three element is seem to be equal.
There's really no need to use .slice() here. (That's what's causing the problem: .slice() returns an array, and two different arrays will never be equal to each other.) Split the strings first and then just use array indexing:
var count = 0;
start = start.split("."); end = end.split(".");
for (var i = 0; i < start.length; ++i)
if (start[i] != end[i])
count++;
return count;
The reason is that operator != when comparing two list objects will return true if they're not the very same object: split returns a list of strings but slice(i, i+1) will return a list of length 1.
This means that you're comparing ["10"] with another ["10"] and they're two different list objects, so != will return true.
If you just compare the contents using x.slice(".")[i] instead of using slice then the result is what you were expecting.
PS: The operator != of Javascript is terrible and you should not use it and prefer instead !==. It would be the same in this case, but it's much nicer to work with because it doesn't do crazy things when the two types are different.
PS2: Seems a good idea to split the strings at each iteration?
You are comparing arrays not strings you want to compare the string values try this instead:
function ipsBetween(start, end){
var count = 0;
for(var i = 0; i < 4; i++) {
if(start.split('.').slice(i, i + 1)[0] != end.split('.').slice(i, i + 1)[0]) {
count++;
}
}
return count;
}
console.log(ipsBetween("10.0.0.0", "10.0.0.50"));
The problem is the array objects returned won't equal each other because they are not the same array ie. they are not located at the same spot in memory...

Append array element only if it is not already there in Javascript

I need to add an element to an array only if it is not already there in Javascript. Basically I'm treating the array as a set.
I need the data to be stored in an array, otherwise I'd just use an object which can be used as a set.
I wrote the following array prototype and wanted to hear if anyone knew of a better way. This is an O(n) insert. I was hoping to do O(ln(n)) insert, however, I didn't see an easy way to insert an element into a sorted array. For my applications, the array lengths will be very small, but I'd still prefer something that obeyed accepted rules for good algorithm efficiency:
Array.prototype.push_if_not_duplicate = function(new_element){
for( var i=0; i<this.length; i++ ){
// Don't add if element is already found
if( this[i] == new_element ){
return this.length;
}
}
// add new element
return this.push(new_element);
}
If I understand correctly, you already have a sorted array (if you do not have a sorted array then you can use Array.sort method to sort your data) and now you want to add an element to it if it is not already present in the array. I extracted the binary insert (which uses binary search) method in the google closure library. The relevant code itself would look something like this and it is O(log n) operation because binary search is O(log n).
function binaryInsert(array, value) {
var index = binarySearch(array, value);
if (index < 0) {
array.splice(-(index + 1), 0, value);
return true;
}
return false;
};
function binarySearch(arr, value) {
var left = 0; // inclusive
var right = arr.length; // exclusive
var found;
while (left < right) {
var middle = (left + right) >> 1;
var compareResult = value > arr[middle] ? 1 : value < arr[middle] ? -1 : 0;
if (compareResult > 0) {
left = middle + 1;
} else {
right = middle;
// We are looking for the lowest index so we can't return immediately.
found = !compareResult;
}
}
// left is the index if found, or the insertion point otherwise.
// ~left is a shorthand for -left - 1.
return found ? left : ~left;
};
Usage is binaryInsert(array, value). This also maintains the sort of the array.
Deleted my other answer because I missed the fact that the array is sorted.
The algorithm you wrote goes through every element in the array and if there are no matches appends the new element on the end. I assume this means you are running another sort after.
The whole algorithm could be improved by using a divide and conquer algorithm. Choose an element in the middle of the array, compare with new element and continue until you find the spot where to insert. It will be slightly faster than your above algorithm, and won't require a sort afterwards.
If you need help working out the algorithm, feel free to ask.
I've created a (simple and incomplete) Set type before like this:
var Set = function (hashCodeGenerator) {
this.hashCode = hashCodeGenerator;
this.set = {};
this.elements = [];
};
Set.prototype = {
add: function (element) {
var hashCode = this.hashCode(element);
if (this.set[hashCode]) return false;
this.set[hashCode] = true;
this.elements.push(element);
return true;
},
get: function (element) {
var hashCode = this.hashCode(element);
return this.set[hashCode];
},
getElements: function () { return this.elements; }
};
You just need to find out a good hashCodeGenerator function for your objects. If your objects are primitives, this function can return the object itself. You can then access the set elements in array form from the getElements accessor. Inserts are O(1). Space requirements are O(2n).
If your array is a binary tree, you can insert in O(log n) by putting the new element on the end and bubbling it up into place. Checks for duplicates would also take O(log n) to perform.
Wikipedia has a great explanation.

In Javascript, how do I check if an array has duplicate values?

Possible Duplicate:
Easiest way to find duplicate values in a javascript array
How do I check if an array has duplicate values?
If some elements in the array are the same, then return true. Otherwise, return false.
['hello','goodbye','hey'] //return false because no duplicates exist
['hello','goodbye','hello'] // return true because duplicates exist
Notice I don't care about finding the duplication, only want Boolean result whether arrays contains duplications.
If you have an ES2015 environment (as of this writing: io.js, IE11, Chrome, Firefox, WebKit nightly), then the following will work, and will be fast (viz. O(n)):
function hasDuplicates(array) {
return (new Set(array)).size !== array.length;
}
If you only need string values in the array, the following will work:
function hasDuplicates(array) {
var valuesSoFar = Object.create(null);
for (var i = 0; i < array.length; ++i) {
var value = array[i];
if (value in valuesSoFar) {
return true;
}
valuesSoFar[value] = true;
}
return false;
}
We use a "hash table" valuesSoFar whose keys are the values we've seen in the array so far. We do a lookup using in to see if that value has been spotted already; if so, we bail out of the loop and return true.
If you need a function that works for more than just string values, the following will work, but isn't as performant; it's O(n2) instead of O(n).
function hasDuplicates(array) {
var valuesSoFar = [];
for (var i = 0; i < array.length; ++i) {
var value = array[i];
if (valuesSoFar.indexOf(value) !== -1) {
return true;
}
valuesSoFar.push(value);
}
return false;
}
The difference is simply that we use an array instead of a hash table for valuesSoFar, since JavaScript "hash tables" (i.e. objects) only have string keys. This means we lose the O(1) lookup time of in, instead getting an O(n) lookup time of indexOf.
You could use SET to remove duplicates and compare, If you copy the array into a set it will remove any duplicates. Then simply compare the length of the array to the size of the set.
function hasDuplicates(a) {
const noDups = new Set(a);
return a.length !== noDups.size;
}
One line solutions with ES6
const arr1 = ['hello','goodbye','hey']
const arr2 = ['hello','goodbye','hello']
const hasDuplicates = (arr) => arr.length !== new Set(arr).size;
console.log(hasDuplicates(arr1)) //return false because no duplicates exist
console.log(hasDuplicates(arr2)) //return true because duplicates exist
const s1 = ['hello','goodbye','hey'].some((e, i, arr) => arr.indexOf(e) !== i)
const s2 = ['hello','goodbye','hello'].some((e, i, arr) => arr.indexOf(e) !== i);
console.log(s1) //return false because no duplicates exist
console.log(s2) //return true because duplicates exist
Another approach (also for object/array elements within the array1) could be2:
function chkDuplicates(arr,justCheck){
var len = arr.length, tmp = {}, arrtmp = arr.slice(), dupes = [];
arrtmp.sort();
while(len--){
var val = arrtmp[len];
if (/nul|nan|infini/i.test(String(val))){
val = String(val);
}
if (tmp[JSON.stringify(val)]){
if (justCheck) {return true;}
dupes.push(val);
}
tmp[JSON.stringify(val)] = true;
}
return justCheck ? false : dupes.length ? dupes : null;
}
//usages
chkDuplicates([1,2,3,4,5],true); //=> false
chkDuplicates([1,2,3,4,5,9,10,5,1,2],true); //=> true
chkDuplicates([{a:1,b:2},1,2,3,4,{a:1,b:2},[1,2,3]],true); //=> true
chkDuplicates([null,1,2,3,4,{a:1,b:2},NaN],true); //=> false
chkDuplicates([1,2,3,4,5,1,2]); //=> [1,2]
chkDuplicates([1,2,3,4,5]); //=> null
See also...
1 needs a browser that supports JSON, or a JSON library if not.
2 edit: function can now be used for simple check or to return an array of duplicate values
You can take benefit of indexOf and lastIndexOf. if both indexes are not same, you have duplicate.
function containsDuplicates(a) {
for (let i = 0; i < a.length; i++) {
if (a.indexOf(a[i]) !== a.lastIndexOf(a[i])) {
return true
}
}
return false
}
If you are dealing with simple values, you can use array.some() and indexOf()
for example let's say vals is ["b", "a", "a", "c"]
const allUnique = !vals.some((v, i) => vals.indexOf(v) < i);
some() will return true if any expression returns true. Here we'll iterate values (from the index 0) and call the indexOf() that will return the index of the first occurrence of given item (or -1 if not in the array). If its id is smaller that the current one, there must be at least one same value before it. thus iteration 3 will return true as "a" (at index 2) is first found at index 1.
is just simple, you can use the Array.prototype.every function
function isUnique(arr) {
const isAllUniqueItems = input.every((value, index, arr) => {
return arr.indexOf(value) === index; //check if any duplicate value is in other index
});
return isAllUniqueItems;
}
One nice thing about solutions that use Set is O(1) performance on looking up existing items in a list, rather than having to loop back over it.
One nice thing about solutions that use Some is short-circuiting when the duplicate is found early, so you don't have to continue evaluating the rest of the array when the condition is already met.
One solution that combines both is to incrementally build a set, early terminate if the current element exists in the set, otherwise add it and move on to the next element.
const hasDuplicates = (arr) => {
let set = new Set()
return arr.some(el => {
if (set.has(el)) return true
set.add(el)
})
}
hasDuplicates(["a","b","b"]) // true
hasDuplicates(["a","b","c"]) // false
According to JSBench.me, should preform pretty well for the varried use cases. The set size approach is fastest with no dupes, and checking some + indexOf is fatest with a very early dupe, but this solution performs well in both scenarios, making it a good all-around implementation.
function hasAllUniqueChars( s ){
for(let c=0; c<s.length; c++){
for(let d=c+1; d<s.length; d++){
if((s[c]==s[d])){
return false;
}
}
}
return true;
}

Categories

Resources