counting duplicate arrays within an array in javascript - javascript

I have an array of arrays as follows:
[[3, 4], [1, 2], [3, 4]]
I wish to create a new array of arrays that has no duplicates, and has a count of the number of occurrences of each element in the first array:
[[3,4,2], [1,2,1]]
here is what I have so far:
var alreadyAdded = 0;
dataset.forEach(function(data) {
From = data[0];
To = data[1];
index = 0;
newDataSet.forEach(function(newdata) {
newFrom = newData[0];
newTo = newData[1];
// check if the point we are looking for is already added to the new array
if ((From == newFrom) && (To == newTo)) {
// if it is, increment the count for that pair
var count = newData[2];
var newCount = count + 1;
newDataSet[index] = [newFrom, newTo, newCount];
test = "reached here";
alreadyAdded = 1;
}
index++;
});
// the pair was not already added to the new dataset, add it
if (alreadyAdded == 0) {
newDataSet.push([From, To, 1]);
}
// reset alreadyAdded variable
alreadyAdded = 0;
});
I am very new to Javascript, can someone help explain to me what I'm doing wrong? I'm sure there is a more concise way of doing this, however I wasn't able to find an example in javascript that dealt with duplicate array of arrays.

Depending on how large the dataset is that you're iterating over I'd be cautious of looping over it so many times. You can avoid having to do that by creating an 'index' for each element in the original dataset and then using it to reference the elements in your grouping. This is the approach that I took when I solved the problem. You can see it here on jsfiddle. I used Array.prototype.reduce to create an object literal which contained the grouping of elements from the original dataset. Then I iterated over it's keys to create the final grouping.
var dataSet = [[3,4], [1,2], [3,4]],
grouping = [],
counts,
keys,
current;
counts = dataSet.reduce(function(acc, elem) {
var key = elem[0] + ':' + elem[1];
if (!acc.hasOwnProperty(key)) {
acc[key] = {elem: elem, count: 0}
}
acc[key].count += 1;
return acc;
}, {});
keys = Object.keys(counts);
for (var i = 0, l = keys.length; i < l; i++) {
current = counts[keys[i]];
current.elem.push(current.count);
grouping.push(current.elem);
}
console.log(grouping);

Assuming order of sub array items matters, assuming that your sub arrays could be of variable length and could contain items other than numbers, here is a fairly generic way to approach the problem. Requires ECMA5 compatibility as it stands, but would not be hard to make it work on ECMA3.
Javascript
// Create shortcuts for prototype methods
var toClass = Object.prototype.toString.call.bind(Object.prototype.toString),
aSlice = Array.prototype.slice.call.bind(Array.prototype.slice);
// A generic deepEqual defined by commonjs
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
function deepEqual(a, b) {
if (a === b) {
return true;
}
if (toClass(a) === '[object Date]' && toClass(b) === '[object Date]') {
return a.getTime() === b.getTime();
}
if (toClass(a) === '[object RegExp]' && toClass(b) === '[object RegExp]') {
return a.toString() === b.toString();
}
if (a && typeof a !== 'object' && b && typeof b !== 'object') {
return a == b;
}
if (a.prototype !== b.prototype) {
return false;
}
if (toClass(a) === '[object Arguments]') {
if (toClass(b) !== '[object Arguments]') {
return false;
}
return deepEqual(aSlice(a), aSlice(b));
}
var ka,
kb,
length,
index,
it;
try {
ka = Object.keys(a);
kb = Object.keys(b);
} catch (eDE) {
return false;
}
length = ka.length;
if (length !== kb.length) {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
} else {
return false;
}
} else {
ka.sort();
kb.sort();
for (index = 0; index < length; index += 1) {
if (ka[index] !== kb[index]) {
return false;
}
}
}
for (index = 0; index < length; index += 1) {
it = ka[index];
if (!deepEqual(a[it], b[it])) {
return false;
}
}
return true;
};
// Recursive function for counting arrays as specified
// a must be an array of arrays
// dupsArray is used to keep count when recursing
function countDups(a, dupsArray) {
dupsArray = Array.isArray(dupsArray) ? dupsArray : [];
var copy,
current,
count;
if (a.length) {
copy = a.slice();
current = copy.pop();
count = 1;
copy = copy.filter(function (item) {
var isEqual = deepEqual(current, item);
if (isEqual) {
count += 1;
}
return !isEqual;
});
current.push(count);
dupsArray.push(current);
if (copy.length) {
countDups(copy, dupsArray);
}
}
return dupsArray;
}
var x = [
[3, 4],
[1, 2],
[3, 4]
];
console.log(JSON.stringify(countDups(x)));
Output
[[3,4,2],[1,2,1]]
on jsFiddle

After fixing a typo I tried your solution in the debugger; it works!
Fixed the inner forEach-loop variable name to match case. Also some var-keywords added.
var alreadyAdded = 0;
dataset.forEach(function (data) {
var From = data[0];
var To = data[1];
var index = 0;
newDataSet.forEach(function (newData) {
var newFrom = newData[0];
var newTo = newData[1];
// check if the point we are looking for is already added to the new array
if ((From == newFrom) && (To == newTo)) {
// if it is, increment the count for that pair
var count = newData[2];
var newCount = count + 1;
newDataSet[index] = [newFrom, newTo, newCount];
test = "reached here";
alreadyAdded = 1;
}
index++;
});
// the pair was not already added to the new dataset, add it
if (alreadyAdded == 0) {
newDataSet.push([From, To, 1]);
}
// reset alreadyAdded variable
alreadyAdded = 0;
});

const x = [[3, 4], [1, 2], [3, 4]];
const with_duplicate_count = [
...x
.map(JSON.stringify)
.reduce( (acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map() )
.entries()
].map(([k, v]) => JSON.parse(k).concat(v));
console.log(with_duplicate_count);

Related

Javascript includes and map together [duplicate]

I am supposed to write a program in JavaScript to find all the anagrams within a series of words provided. e.g.:
monk, konm, nkom, bbc, cbb, dell, ledl, llde
The output should be categorised into rows:
1. monk konm, nkom;
2. bbc cbb;
3. dell ledl, llde;
I already sorted them into alphabetical order and put them into an array. i.e.:
kmno kmno bbc bbc dell dell
However I am stuck in comparing and finding the matching anagram within the array.
Any help will be greatly appreciated.
Javascript objects are excellent for this purpose, since they are essentially key/value stores:
// Words to match
var words = ["dell", "ledl", "abc", "cba"];
// The output object
var anagrams = {};
for (var i in words) {
var word = words[i];
// sort the word like you've already described
var sorted = sortWord(word);
// If the key already exists, we just push
// the new word on the the array
if (anagrams[sorted] != null) {
anagrams[sorted].push(word);
}
// Otherwise we create an array with the word
// and insert it into the object
else {
anagrams[sorted] = [ word ];
}
}
// Output result
for (var sorted in anagrams) {
var words = anagrams[sorted];
var sep = ",";
var out = "";
for (var n in words) {
out += sep + words[n];
sep = "";
}
document.writeln(sorted + ": " + out + "<br />");
}
Here is my take:
var input = "monk, konm, bbc, cbb, dell, ledl";
var words = input.split(", ");
for (var i = 0; i < words.length; i++) {
var word = words[i];
var alphabetical = word.split("").sort().join("");
for (var j = 0; j < words.length; j++) {
if (i === j) {
continue;
}
var other = words[j];
if (alphabetical === other.split("").sort().join("")) {
console.log(word + " - " + other + " (" + i + ", " + j + ")");
}
}
}
where the output would be (the word, the match and the index of both):
monk - konm (0, 1)
konm - monk (1, 0)
bbc - cbb (2, 3)
cbb - bbc (3, 2)
dell - ledl (4, 5)
ledl - dell (5, 4)
To get the characters in the in alphabetical order, I used split("") ot get an array, called sort() and used join("") to get a string from the array.
Simple Solution
function anagrams(stringA, stringB) {
return cleanString(stringA) === cleanString(stringB);
}
function cleanString(str) {
return str.replace(/[^\w]/g).toLowerCase().split('').sort().join()
}
anagrams('monk','konm')
If it is anagrams function will return true otherwise false
I worked through a similar question to this today and wanted to share the results of my work. I was focused on just detecting the anagram so processing the list of words was not part of my exercise but this algorithm should provide a highly performant way to detect an anagram between two words.
function anagram(s1, s2){
if (s1.length !== s2.length) {
// not the same length, can't be anagram
return false;
}
if (s1 === s2) {
// same string must be anagram
return true;
}
var c = '',
i = 0,
limit = s1.length,
match = 0,
idx;
while(i < s1.length){
// chomp the next character
c = s1.substr(i++, 1);
// find it in the second string
idx = s2.indexOf(c);
if (idx > -1) {
// found it, add to the match
match++;
// assign the second string to remove the character we just matched
s2 = s2.substr(0, idx) + s2.substr(idx + 1);
} else {
// not found, not the same
return false;
}
}
return match === s1.length;
}
I think technically is can be solved like this:
function anagram(s1, s2){
return s1.split("").sort().join("") === s2.split("").sort().join("");
}
The reason I chose the earlier approach is that it is more performant for larger strings since you don't need to sort either string, convert to an array or loop through the entire string if any possible failure case is detected.
Probably not the most efficient way, but a clear way around using es6
function sortStrChars(str) {
if (!str) {
return;
}
str = str.split('');
str = str.sort();
str = str.join('');
return str;
}
const words = ["dell", "ledl", "abc", "cba", 'boo'];
function getGroupedAnagrams(words) {
const anagrams = {}; // {abc:[abc,cba], dell:[dell, ledl]}
words.forEach((word) => {
const sortedWord = sortStrChars(word);
if (anagrams[sortedWord]) {
return anagrams[sortedWord].push(word);
}
anagrams[sortedWord] = [word];
});
return anagrams;
}
const groupedAnagrams = getGroupedAnagrams(words);
for (const sortedWord in groupedAnagrams) {
console.log(groupedAnagrams[sortedWord].toString());
}
I had this question in an interview. Given an array of words ['cat', 'dog', 'tac', 'god', 'act'], return an array with all the anagrams grouped together. Makes sure the anagrams are unique.
var arr = ['cat', 'dog', 'tac', 'god', 'act'];
var allAnagrams = function(arr) {
var anagrams = {};
arr.forEach(function(str) {
var recurse = function(ana, str) {
if (str === '')
anagrams[ana] = 1;
for (var i = 0; i < str.length; i++)
recurse(ana + str[i], str.slice(0, i) + str.slice(i + 1));
};
recurse('', str);
});
return Object.keys(anagrams);
}
console.log(allAnagrams(arr));
//["cat", "cta", "act", "atc", "tca", "tac", "dog", "dgo", "odg", "ogd", "gdo", "god"]
Best and simple way to solve is using for loops and traversing it to each string and then store their result in object.
Here is the solution :-
function anagram(str1, str2) {
if (str1.length !== str2.length) {
return false;
}
const result = {};
for (let i=0;i<str1.length;i++) {
let char = str1[i];
result[char] = result[char] ? result[char] += 1 : result[char] = 1;
}
for (let i=0;i<str2.length;i++) {
let char = str2[i];
if (!result[char]) {
return false;
}
else {
result[char] = -1;
}
}
return true;
}
console.log(anagram('ronak','konar'));
I know this is an ancient post...but I just recently got nailed during an interview on this one. So, here is my 'new & improved' answer:
var AnagramStringMiningExample = function () {
/* Author: Dennis Baughn
* This has also been posted at:
* http://stackoverflow.com/questions/909449/anagrams-finder-in-javascript/5642437#5642437
* Free, private members of the closure and anonymous, innner function
* We will be building a hashtable for anagrams found, with the key
* being the alphabetical char sort (see sortCharArray())
* that the anagrams all have in common.
*/
var dHash = {};
var sortCharArray = function(word) {
return word.split("").sort().join("");
};
/* End free, private members for the closure and anonymous, innner function */
/* This goes through the dictionary entries.
* finds the anagrams (if any) for each word,
* and then populates them in the hashtable.
* Everything strictly local gets de-allocated
* so as not to pollute the closure with 'junk DNA'.
*/
(function() {
/* 'dictionary' referring to English dictionary entries. For a real
* English language dictionary, we could be looking at 20,000+ words, so
* an array instead of a string would be needed.
*/
var dictionaryEntries = "buddy,pan,nap,toot,toto,anestri,asterin,eranist,nastier,ratines,resiant,restain,retains,retinas,retsina,sainter,stainer,starnie,stearin";
/* This could probably be refactored better.
* It creates the actual hashtable entries. */
var populateDictionaryHash = function(keyword, newWord) {
var anagrams = dHash[keyword];
if (anagrams && anagrams.indexOf(newWord) < 0)
dHash[keyword] = (anagrams+','+newWord);
else dHash[keyword] = newWord;
};
var words = dictionaryEntries.split(",");
/* Old School answer, brute force
for (var i = words.length - 1; i >= 0; i--) {
var firstWord = words[i];
var sortedFirst = sortCharArray(firstWord);
for (var k = words.length - 1; k >= 0; k--) {
var secondWord = words[k];
if (i === k) continue;
var sortedSecond = sortCharArray(secondWord);
if (sortedFirst === sortedSecond)
populateDictionaryHash(sortedFirst, secondWord);
}
}/*
/*Better Method for JS, using JS Array.reduce(callback) with scope binding on callback function */
words.reduce(function (prev, cur, index, array) {
var sortedFirst = this.sortCharArray(prev);
var sortedSecond = this.sortCharArray(cur);
if (sortedFirst === sortedSecond) {
var anagrams = this.dHash[sortedFirst];
if (anagrams && anagrams.indexOf(cur) < 0)
this.dHash[sortedFirst] = (anagrams + ',' + cur);
else
this.dHash[sortedFirst] = prev + ','+ cur;
}
return cur;
}.bind(this));
}());
/* return in a nice, tightly-scoped closure the actual function
* to search for any anagrams for searchword provided in args and render results.
*/
return function(searchWord) {
var keyToSearch = sortCharArray(searchWord);
document.writeln('<p>');
if (dHash.hasOwnProperty(keyToSearch)) {
var anagrams = dHash[keyToSearch];
document.writeln(searchWord + ' is part of a collection of '+anagrams.split(',').length+' anagrams: ' + anagrams+'.');
} else document.writeln(searchWord + ' does not have anagrams.');
document.writeln('<\/p>');
};
};
Here is how it executes:
var checkForAnagrams = new AnagramStringMiningExample();
checkForAnagrams('toot');
checkForAnagrams('pan');
checkForAnagrams('retinas');
checkForAnagrams('buddy');
Here is the output of the above:
toot is part of a collection of 2
anagrams: toto,toot.
pan is part of a collection of 2
anagrams: nap,pan.
retinas is part of a collection of 14
anagrams:
stearin,anestri,asterin,eranist,nastier,ratines,resiant,restain,retains,retinas,retsina,sainter,stainer,starnie.
buddy does not have anagrams.
My solution to this old post:
// Words to match
var words = ["dell", "ledl", "abc", "cba"],
map = {};
//Normalize all the words
var normalizedWords = words.map( function( word ){
return word.split('').sort().join('');
});
//Create a map: normalizedWord -> real word(s)
normalizedWords.forEach( function ( normalizedWord, index){
map[normalizedWord] = map[normalizedWord] || [];
map[normalizedWord].push( words[index] );
});
//All entries in the map with an array with size > 1 are anagrams
Object.keys( map ).forEach( function( normalizedWord , index ){
var combinations = map[normalizedWord];
if( combinations.length > 1 ){
console.log( index + ". " + combinations.join(' ') );
}
});
Basically I normalize every word by sorting its characters so stackoverflow would be acefkloorstvw, build a map between normalized words and the original words, determine which normalized word has more than 1 word attached to it -> That's an anagram.
Maybe this?
function anagram (array) {
var organized = {};
for (var i = 0; i < array.length; i++) {
var word = array[i].split('').sort().join('');
if (!organized.hasOwnProperty(word)) {
organized[word] = [];
}
organized[word].push(array[i]);
}
return organized;
}
anagram(['kmno', 'okmn', 'omkn', 'dell', 'ledl', 'ok', 'ko']) // Example
It'd return something like
{
dell: ['dell', 'ledl'],
kmno: ['kmno', okmn', 'omkn'],
ko: ['ok', ko']
}
It's a simple version of what you wanted and certainly it could be improved avoiding duplicates for example.
My two cents.
This approach uses XOR on each character in both words. If the result is 0, then you have an anagram. This solution assumes case sensitivity.
let first = ['Sower', 'dad', 'drown', 'elbow']
let second = ['Swore', 'add', 'down', 'below']
// XOR all characters in both words
function isAnagram(first, second) {
// Word lengths must be equal for anagram to exist
if (first.length !== second.length) {
return false
}
let a = first.charCodeAt(0) ^ second.charCodeAt(0)
for (let i = 1; i < first.length; i++) {
a ^= first.charCodeAt(i) ^ second.charCodeAt(i)
}
// If a is 0 then both words have exact matching characters
return a ? false : true
}
// Check each pair of words for anagram match
for (let i = 0; i < first.length; i++) {
if (isAnagram(first[i], second[i])) {
console.log(`'${first[i]}' and '${second[i]}' are anagrams`)
} else {
console.log(`'${first[i]}' and '${second[i]}' are NOT anagrams`)
}
}
function isAnagram(str1, str2) {
var str1 = str1.toLowerCase();
var str2 = str2.toLowerCase();
if (str1 === str2)
return true;
var dict = {};
for(var i = 0; i < str1.length; i++) {
if (dict[str1[i]])
dict[str1[i]] = dict[str1[i]] + 1;
else
dict[str1[i]] = 1;
}
for(var j = 0; j < str2.length; j++) {
if (dict[str2[j]])
dict[str2[j]] = dict[str2[j]] - 1;
else
dict[str2[j]] = 1;
}
for (var key in dict) {
if (dict[key] !== 0)
return false;
}
return true;
}
console.log(isAnagram("hello", "olleh"));
I have an easy example
function isAnagram(strFirst, strSecond) {
if(strFirst.length != strSecond.length)
return false;
var tempString1 = strFirst.toLowerCase();
var tempString2 = strSecond.toLowerCase();
var matched = true ;
var cnt = 0;
while(tempString1.length){
if(tempString2.length < 1)
break;
if(tempString2.indexOf(tempString1[cnt]) > -1 )
tempString2 = tempString2.replace(tempString1[cnt],'');
else
return false;
cnt++;
}
return matched ;
}
Calling function will be isAnagram("Army",Mary);
Function will return true or false
let words = ["dell", "ledl","del", "abc", "cba", 'boo'];
//sort each item
function sortArray(data){
var r=data.split('').sort().join().replace(/,/g,'');
return r;
}
var groupObject={};
words.forEach((item)=>{
let sorteditem=sortArray(item);
//Check current item is in the groupObject or not.
//If not then add it as an array
//else push it to the object property
if(groupObject[sorteditem])
return groupObject[sorteditem].push(item);
groupObject[sorteditem]=[sorteditem];
});
//to print the result
for(i=0;i<Object.keys(groupObject).length;i++)
document.write(groupObject[Object.keys(groupObject)[i]] + "<br>");
/* groupObject value:
abc: (2) ["abc", "cba"]
boo: ["boo"]
del: ["del"]
dell: (2) ["dell", "ledl"]
OUTPUT:
------
dell,ledl
del
abc,cba
boo
*/
Compare string length, if not equal, return false
Create character Hashmap which stores count of character in strA e.g. Hello --> {H: 1, e: 1, l: 2, o: 1}
Loop over the second string and lookup the current character in Hashmap. If not exist, return false, else decrement the value by 1
If none of the above return falsy, it must be an anagram
Time complexity: O(n)
function isAnagram(strA: string, strB: string): boolean {
const strALength = strA.length;
const strBLength = strB.length;
const charMap = new Map<string, number>();
if (strALength !== strBLength) {
return false;
}
for (let i = 0; i < strALength; i += 1) {
const current = strA[i];
charMap.set(current, (charMap.get(current) || 0) + 1);
}
for (let i = 0; i < strBLength; i += 1) {
const current = strB[i];
if (!charMap.get(current)) {
return false;
}
charMap.set(current, charMap.get(current) - 1);
}
return true;
}
function findAnagram(str1, str2) {
let mappedstr1 = {}, mappedstr2 = {};
for (let item of str1) {
mappedstr1[item] = (mappedstr1[item] || 0) + 1;
}
for (let item2 of str2) {
mappedstr2[item2] = (mappedstr2[item2] || 0) + 1;
}
for (let key in mappedstr1) {
if (!mappedstr2[key]) {
return false;
}
if (mappedstr1[key] !== mappedstr2[key]) {
return false;
}
}
return true;
}
console.log(findAnagram("hello", "hlleo"));
Another example only for comparing 2 strings for an anagram.
function anagram(str1, str2) {
if (str1.length !== str2.length) {
return false;
} else {
if (
str1.toLowerCase().split("").sort().join("") ===
str2.toLowerCase().split("").sort().join("")
) {
return "Anagram";
} else {
return "Not Anagram";
}
}
}
console.log(anagram("hello", "olleh"));
console.log(anagram("ronak", "konar"));
const str1 ="1123451"
const str2 = "2341151"
function anagram(str1,str2) {
let count = 0;
if (str1.length!==str2.length) { return false;}
for(i1=0;i1<str1.length; i1++) {
for (i2=0;i2<str2.length; i2++) {
if (str1[i1]===str2[i2]){
count++;
break;
}
}
}
if (count===str1.length) { return true}
}
anagram(str1,str2)
Another solution for isAnagram using reduce
const checkAnagram = (orig, test) => {
return orig.length === test.length
&& orig.split('').reduce(
(acc, item) => {
let index = acc.indexOf(item);
if (index >= 0) {
acc.splice(index, 1);
return acc;
}
throw new Error('Not an anagram');
},
test.split('')
).length === 0;
};
const isAnagram = (tester, orig, test) => {
try {
return tester(orig, test);
} catch (e) {
return false;
}
}
console.log(isAnagram(checkAnagram, '867443', '473846'));
console.log(isAnagram(checkAnagram, '867443', '473846'));
console.log(isAnagram(checkAnagram, '867443', '475846'));
var check=true;
var str="cleartrip";
var str1="tripclear";
if(str.length!=str1.length){
console.log("Not an anagram");
check=false;
}
console.log(str.split("").sort());
console.log("----------"+str.split("").sort().join(''));
if(check){
if((str.split("").sort().join(''))===((str1.split("").sort().join('')))){
console.log("Anagram")
}
else{
console.log("not a anagram");
}
}
Here is my solution which addresses a test case where the input strings which are not anagrams, can be removed from the output. Hence the output contains only the anagram strings. Hope this is helpful.
/**
* Anagram Finder
* #params {array} wordArray
* #return {object}
*/
function filterAnagram(wordArray) {
let outHash = {};
for ([index, word] of wordArray.entries()) {
let w = word.split("").sort().join("");
outHash[w] = !outHash[w] ? [word] : outHash[w].concat(word);
}
let filteredObject = Object.keys(outHash).reduce(function(r, e) {
if (Object.values(outHash).filter(v => v.length > 1).includes(outHash[e])) r[e] = outHash[e]
return r;
}, {});
return filteredObject;
}
console.log(filterAnagram(['monk', 'yzx','konm', 'aaa', 'ledl', 'bbc', 'cbb', 'dell', 'onkm']));
i have recently faced this in the coding interview, here is my solution.
function group_anagrams(arr) {
let sortedArr = arr.map(item => item.split('').sort().join(''));
let setArr = new Set(sortedArr);
let reducedObj = {};
for (let setItem of setArr) {
let indexArr = sortedArr.reduce((acc, cur, index) => {
if (setItem === cur) {
acc.push(index);
}
return acc;
}, []);
reducedObj[setItem] = indexArr;
}
let finalArr = [];
for (let reduceItem in reducedObj) {
finalArr.push(reducedObj[reduceItem].map(item => arr[item]));
}
return finalArr;
}
group_anagrams(['car','cra','rca', 'cheese','ab','ba']);
output will be like
[
["car", "cra", "rca"],
["cheese"],
["ab", "ba"]
]
My solution has more code, but it avoids using .sort(), so I think this solution has less time complexity. Instead it makes a hash out of every word and compares the hashes:
const wordToHash = word => {
const hash = {};
// Make all lower case and remove spaces
[...word.toLowerCase().replace(/ /g, '')].forEach(letter => hash[letter] ? hash[letter] += 1 : hash[letter] = 1);
return hash;
}
const hashesEqual = (obj1, obj2) => {
const keys1 = Object.keys(obj1), keys2 = Object.keys(obj2);
let match = true;
if(keys1.length !== keys2.length) return false;
for(const key in keys1) { if(obj1[key] !== obj2[key]) match = false; break; }
return match;
}
const checkAnagrams = (word1, word2) => {
const hash1 = wordToHash(word1), hash2 = wordToHash(word2);
return hashesEqual(hash1, hash2);
}
console.log( checkAnagrams("Dormitory", "Dirty room") );
/*This is good option since
logic is easy,
deals with duplicate data,
Code to check anagram in an array,
shows results in appropriate manner,
function check can be separately used for comparing string in this regards with all benefits mentioned above.
*/
var words = ["deuoll", "ellduo", "abc","dcr","frt", "bu","cba","aadl","bca","elduo","bac","acb","ub","eldou","ellduo","ert","tre"];
var counter=1;
var ele=[];
function check(str1,str2)
{
if(str2=="")
return false;
if(str1.length!=str2.length)
return false;
var r1=[...(new Set (str1.split('').sort()))];
var r2=[...(new Set (str2.split('').sort()))];
var flag=true;
r1.forEach((item,index)=>
{
if(r2.indexOf(item)!=index)
{ flag=false;}
});
return flag;
}
var anagram=function ()
{
for(var i=0;i<words.length && counter!=words.length ;i++)
{
if(words[i]!="")
{
document.write("<br>"+words[i]+":");
counter++;
}
for(var j=i+1;j<words.length && counter !=words.length+1;j++)
{
if(check(words[i],words[j]))
{
ele=words[j];
document.write(words[j]+"&nbsp");
words[j]="";
counter++;
}
}
}
}
anagram();
If you just need count of anagrams
const removeDuplicatesAndSort = [...new Set(yourString.split(', '))].map(word => word.split('').sort().join())
const numberOfAnagrams = removeDuplicatesAndSort.length - [...new Set(removeDuplicatesAndSort)].length
function isAnagram(str1, str2){
let count = 0;
if (str1.length !== str2.length) {
return false;
} else {
let val1 = str1.toLowerCase().split("").sort();
let val2 = str2.toLowerCase().split("").sort();
for (let i = 0; i < val2.length; i++) {
if (val1[i] === val2[i]) {
count++;
}
}
if (count == str1.length) {
return true;
}
}
return false;
}
console.log(isAnagram("cristian", "Cristina"))
function findAnagrams (str, arr){
let newStr = "";
let output = [];
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr[i].length; j++) {
for (let k = 0; k < str.length; k++) {
if (str[k] === arr[i][j] && str.length === arr[i].length) {
newStr += arr[i][j];
}
}
} if(newStr.length === str.length){
output.push(newStr);
newStr = "";
}
}
return output;
}
const getAnagrams = (...args) => {
const anagrams = {};
args.forEach((arg) => {
const letters = arg.split("").sort().join("");
if (anagrams[letters]) {
anagrams[letters].push(arg);
} else {
anagrams[letters] = [arg];
}
});
return Object.values(anagrams);
}
function isAnagaram(str1, str2){
if(str1.length!== str2.length){
return false;
}
var obj1 = {};
var obj2 = {};
for(var arg of str1){
obj1[arg] = (obj1[arg] || 0 ) + 1 ;
}
for(var arg of str2){
obj2[arg] = (obj2[arg] || 0 ) + 1 ;
}
for( var key in obj1){
if(obj1[key] !== obj2[key]){
return false;
}
}
return true;
}
console.log(isAnagaram('texttwisttime' , 'timetwisttext'));
let validAnagram = (firstString, secondString) => {
if (firstString.length !== secondString.length) {
return false;
}
let secondStringArr = secondString.split('');
for (var char of firstString) {
charIndexInSecondString = secondString.indexOf(char);
if (charIndexInSecondString === -1) {
return false;
}
secondString = secondString.replace(char, '');
}
return true;
}

How to detect if an array contains any value that is a specific length

I have an array as follows:
var array = ['Bob','F', 'Nichols'];
I want to detect whether this array contains any values that are a single character long. In other words, I want to know whether there are any initials in this person's name.
var array = ['Bob','F', 'Nichols']; //true
var array = ['B','Freddy', 'Nichols']; //true
var array = ['Bob','Freddy', 'N']; //true
var array = ['B','F', 'N']; //true
var array = ['B','F', 'N']; //true
var array = ['Bob','Freddy', 'Nichols']; //false
if (anyInitials(array)) {
console.log("there are initials");
} else {
console.log("there are no initials");
}
function anyInitials(a) {
var arrayLength = a.length;
var initial = 'no';
for (var i = 0; i < arrayLength; i++) {
if (a[i].length == 1){
initial = 'yes';
}
}
return initial;
}
You can use the function some
let array = ['Bob','F', 'Nichols'];
console.log(array.some(({length}) => length === 1));
let anyInitials = a => a.some(({length}) => length === 1) ? "yes" : "no";
You can use a simple .forEach() loop like below. This loops through the array, and sets isInitial to true if the length is 1.
var array = ['Bob', 'F', 'Nichols'];
function anyInitials(a) {
var isInitial = false;
a.forEach(e => {
if (e.length == 1) {
isInitial = true;
}
});
return isInitial;
}
console.log(anyInitials(array));
You can also use .some() like below. This will return true if any element in the array has a length of 1.
var array = ['Bob', 'F', 'Nichols'];
function anyInitials(a) {
return array.some(e => e.length == 1);
}
console.log(anyInitials(array));

im unable to remove adjacent vaules from array

im trying to remove adjacent duplicates but instead of desired result (3 results) i'm getting only 2 results
my Expected Output:
[{"mw://HOME_BIN":{"position":0}},{"mw://diagnosis_HOME":{"position":3}},{"mw://HOME_BIN":{"position":3}}]
here is what i have tried:
var arr = [{"mw://HOME_BIN":{"position":0}},{"mw://diagnosis_HOME":{"position":3}},{"mw://HOME_BIN":{"position":3}},{"mw://HOME_BIN":{"position":3}}];
var nArr = [];
for(var i = 0; i < arr.length;++i){
var key1 = Object.keys(arr[i]).join('');
var nLength = ((i + 1) > arr.length - 1 ) ? 0 : i + 1;
var key2 = Object.keys(arr[nLength]).join('');
if(key1 == key2) continue;
nArr.push(arr[i]);
}
console.log(nArr);
from the above result you can see one more element is missing
Easier to use filter:
var arr = [{"mw://HOME_BIN":{"position":0}},{"mw://diagnosis_HOME":{"position":3}},{"mw://HOME_BIN":{"position":3}},{"mw://HOME_BIN":{"position":3}}];
let lastKey;
const filtered = arr.filter((item) => {
const key = Object.keys(item)[0];
if (key === lastKey) return false;
lastKey = key;
return true;
});
console.log(filtered);
If you're looking for adjacent keys, you should just track the latest key, and not compare all of them.
var arr = [{"mw://HOME_BIN":{"position":0}},{"mw://diagnosis_HOME":{"position":3}},{"mw://HOME_BIN":{"position":3}},{"mw://HOME_BIN":{"position":3}}];
let result = [], prev_key = '', key;
for(let item of arr) {
key = Object.keys(item).sort().join();
if(key == prev_key) continue;
prev_key = key;
result.push(item);
}
console.log(result);
var arr =
[
{"mw://HOME_BIN":{"position":0}},
{"mw://diagnosis_HOME":{"position":3}},
{"mw://HOME_BIN":{"position":3}},
{"mw://HOME_BIN":{"position":3}}
];
var nArr = [];
arr.forEach((ar,index) => {
if(index === arr.length - 1) {
nArr.push(ar);
return;
}
if(JSON.stringify(ar) !== JSON.stringify(arr[index+1])){
nArr.push(ar);
}
})
console.log(nArr);
You can use JSON.stringify to compare whole objects instead of using single key, for your scalability

Count the number of unique occurrences in an array that contain a specific string with Javascript

Here is my javascript array:
arr = ['blue-dots', 'blue', 'red-dots', 'orange-dots', 'blue-dots'];
With Javascript, how can I count the total number of all unique values in the array that contain the string “dots”. So, for the above array the answer would be 3 (blue-dots, orange-dots, and red-dots).
var count = 0,
arr1 = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i].indexOf('dots') !== -1) {
if (arr1.indexOf(arr[i]) === -1) {
count++;
arr1.push(arr[i]);
}
}
}
you check if a certain element contains 'dots', and if it does, you check if it is already in arr1, if not increment count and add element to arr1.
One way is to store element as key of an object, then get the count of the keys:
var arr = ["blue-dots", "blue", "red-dots", "orange-dots", "blue-dots"];
console.log(Object.keys(arr.reduce(function(o, x) {
if (x.indexOf('dots') != -1) {
o[x] = true;
}
return o
}, {})).length)
Try this something like this:
// Create a custom function
function countDots(array) {
var count = 0;
// Get and store each value, so they are not repeated if present.
var uniq_array = [];
array.forEach(function(value) {
if(uniq_array.indexOf(value) == -1) {
uniq_array.push(value);
// Add one to count if 'dots' word is present.
if(value.indexOf('dots') != -1) {
count += 1;
}
}
});
return count;
}
// This will print '3' on console
console.log( countDots(['blue-dots', 'blue', 'red-dots', 'orange-dots', 'blue-dots']) );
From this question, I got the getUnique function.
Array.prototype.getUnique = function(){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
}
then you can add a function that counts ocurrences of a string inside an array of strings:
function getOcurrencesInStrings(targetString, arrayOfStrings){
var ocurrencesCount = 0;
for(var i = 0, arrayOfStrings.length; i++){
if(arrayOfStrings[i].indexOf(targetString) > -1){
ocurrencesCount++;
}
}
return ocurrencesCount;
}
then you just:
getOcurrencesInStrings('dots', initialArray.getUnique())
This will return the number you want.
It's not the smallest piece of code, but It's highly reusable.
var uniqueHolder = {};
var arr = ["blue-dots", "blue", "red-dots", "orange-dots", "blue-dots"];
arr.filter(function(item) {
return item.indexOf('dots') > -1;
})
.forEach(function(item) {
uniqueHolder[item] ? void(0) : uniqueHolder[item] = true;
});
console.log('Count: ' + Object.keys(uniqueHolder).length);
console.log('Values: ' + Object.keys(uniqueHolder));
Try this code,
arr = ["blue-dots", "blue", "red-dots", "orange-dots", "blue-dots"];
sample = [];
for (var i = 0; i < arr.length; i++) {
if ((arr[i].indexOf('dots') !== -1) && (sample.indexOf(arr[i]) === -1)){
sample.push(arr[i]);
}
}
alert(sample.length);
var arr = [ "blue-dots", "blue", "red-dots", "orange-dots", "blue-dots" ];
var fArr = []; // Empty array, which could replace arr after the filtering is done.
arr.forEach( function( v ) {
v.indexOf( "dots" ) > -1 && fArr.indexOf( v ) === -1 ? fArr.push( v ) : null;
// Filter if "dots" is in the string, and not already in the other array.
});
// Code for displaying result on page, not necessary to filter arr
document.querySelector( ".before" ).innerHTML = arr.join( ", " );
document.querySelector( ".after" ).innerHTML = fArr.join( ", " );
Before:
<pre class="before">
</pre>
After:
<pre class="after">
</pre>
To put this simply, it will loop through the array, and if dots is in the string, AND it doesn't already exist in fArr, it'll push it into fArr, otherwise it'll do nothing.
I'd separate the operations of string comparison and returning unique items, to make your code easier to test, read, and reuse.
var unique = function(a){
return a.length === 0 ? [] : [a[0]].concat(unique(a.filter(function(x){
return x !== a[0];
})));
};
var has = function(x){
return function(y){
return y.indexOf(x) !== -1;
};
};
var arr = ["blue-dots", "blue", "red-dots", "orange-dots", "blue-dots"];
var uniquedots = unique(arr.filter(has('dots')));
console.log(uniquedots);
console.log(uniquedots.length);

Compare arrays as (multi-) sets

I'm looking for an efficient way to find out whether two arrays contain same amounts of equal elements (in the == sense), in any order:
foo = {/*some object*/}
bar = {/*some other object*/}
a = [1,2,foo,2,bar,2]
b = [bar,2,2,2,foo,1]
sameElements(a, b) --> true
PS. Note that pretty much every solution in the thread uses === and not == for comparison. This is fine for my needs though.
Update 5
I posted a new answer with a different approach.
Update
I extended the code to have the possibility of either checking by reference or equality
just pass true as second parameter to do a reference check.
Also I added the example to Brunos JSPerf
It runs at about 11 ops/s doing a reference check
I will comment the code as soon(!) as I get some spare time to explain it a bit more, but at the moment don't have the time for that, sry. Done
Update 2.
Like Bruno pointed out in the comments sameElements([NaN],[NaN]) yields false
In my opinion this is the correct behaviour as NaN is ambigious and should always lead to a false result,at least when comparing NaN.equals(NaN). But he had quite a good point.
Whether
[1,2,foo,bar,NaN,3] should be equal to [1,3,foo,NaN,bar,2] or not.
Ok.. honestly I'm a bit torn whether it should or not, so i added two flags.
Number.prototype.equal.NaN
If true
NaN.equals(NaN) //true
Array.prototype.equal.NaN
If true
[NaN].equals([NaN],true) //true
note this is only for reference checks. As a deep check would invoke Number.prototype.equals anyway
Update 3:
Dang i totally missed 2 lines in the sort function.
Added
r[0] = a._srt; //DANG i totally missed this line
r[1] = b._srt; //And this.
Line 105 in the Fiddle
Which is kind of important as it determines the consistent order of the Elements.
Update 4
I tried to optimize the sort function a bit, and managed to get it up to about 20 ops/s.
Below is the updated code, as well as the updated fiddle =)
Also i chose to mark the objects outside the sort function, it doesn't seem to make a performance difference anymore, and its more readable
Here is an approach using Object.defineProperty to add equals functions to
Array,Object,Number,String,Boolean's prototype to avoid typechecking in one function for
performance reasons. As we can recursively call .equals on any element.
But of course checking Objects for equality may cause performance issues in big Objects.
So if anyone feels unpleasant manipulating native prototypes, just do a type check and put it into one function
Object.defineProperty(Boolean.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c) {
return this == c; //For booleans simply return the equality
}
});
Object.defineProperty(Number.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c) {
if (Number.prototype.equals.NaN == true && isNaN(this) && c != c) return true; //let NaN equals NaN if flag set
return this == c; // else do a normal compare
}
});
Number.prototype.equals.NaN = false; //Set to true to return true for NaN == NaN
Object.defineProperty(String.prototype, "equals", {
enumerable: false,
configurable: true,
value: Boolean.prototype.equals //the same (now we covered the primitives)
});
Object.defineProperty(Object.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c, reference) {
if (true === reference) //If its a check by reference
return this === c; //return the result of comparing the reference
if (typeof this != typeof c) {
return false; //if the types don't match (Object equals primitive) immediately return
}
var d = [Object.keys(this), Object.keys(c)],//create an array with the keys of the objects, which get compared
f = d[0].length; //store length of keys of the first obj (we need it later)
if (f !== d[1].length) {//If the Objects differ in the length of their keys
return false; //immediately return
}
for (var e = 0; e < f; e++) { //iterate over the keys of the first object
if (d[0][e] != d[1][e] || !this[d[0][e]].equals(c[d[1][e]])) {
return false; //if either the key name does not match or the value does not match, return false. a call of .equal on 2 primitives simply compares them as e.g Number.prototype.equal gets called
}
}
return true; //everything is equal, return true
}
});
Object.defineProperty(Array.prototype, "equals", {
enumerable: false,
configurable: true,
value: function (c,reference) {
var d = this.length;
if (d != c.length) {
return false;
}
var f = Array.prototype.equals.sort(this.concat());
c = Array.prototype.equals.sort(c.concat(),f)
if (reference){
for (var e = 0; e < d; e++) {
if (f[e] != c[e] && !(Array.prototype.equals.NaN && f[e] != f[e] && c[e] != c[e])) {
return false;
}
}
} else {
for (var e = 0; e < d; e++) {
if (!f[e].equals(c[e])) {
return false;
}
}
}
return true;
}
});
Array.prototype.equals.NaN = false; //Set to true to allow [NaN].equals([NaN]) //true
Object.defineProperty(Array.prototype.equals,"sort",{
enumerable:false,
value:function sort (curr,prev) {
var weight = {
"[object Undefined]":6,
"[object Object]":5,
"[object Null]":4,
"[object String]":3,
"[object Number]":2,
"[object Boolean]":1
}
if (prev) { //mark the objects
for (var i = prev.length,j,t;i>0;i--) {
t = typeof (j = prev[i]);
if (j != null && t === "object") {
j._pos = i;
} else if (t !== "object" && t != "undefined" ) break;
}
}
curr.sort (sorter);
if (prev) {
for (var k = prev.length,l,t;k>0;k--) {
t = typeof (l = prev[k]);
if (t === "object" && l != null) {
delete l._pos;
} else if (t !== "object" && t != "undefined" ) break;
}
}
return curr;
function sorter (a,b) {
var tStr = Object.prototype.toString
var types = [tStr.call(a),tStr.call(b)]
var ret = [0,0];
if (types[0] === types[1] && types[0] === "[object Object]") {
if (prev) return a._pos - b._pos
else {
return a === b ? 0 : 1;
}
} else if (types [0] !== types [1]){
return weight[types[0]] - weight[types[1]]
}
return a>b?1:a<b?-1:0;
}
}
});
With this we can reduce the sameElements function to
function sameElements(c, d,referenceCheck) {
return c.equals(d,referenceCheck); //call .equals of Array.prototype.
}
Note. of course you could put all equal functions into the sameElements function, for the cost of the typechecking.
Now here are 3 examples: 1 with deep checking, 2 with reference checking.
var foo = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3]
};
var bar = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3]
};
var foobar = {
a: 1,
obj: {
number: 2,
bool: true,
string: "asd"
},
arr: [1, 2, 3, 4]
};
var a = [1, 2, foo, 2, bar, 2];
var b = [foo, 2, 2, 2, bar, 1];
var c = [bar, 2, 2, 2, bar, 1];
So these are the Arrays we compare. And the output is
Check a and b with references only.
console.log (sameElements ( a,b,true)) //true As they contain the same elements
Check b and c with references only
console.log (sameElements (b,c,true)) //false as c contains bar twice.
Check b and c deeply
console.log (sameElements (b,c,false)) //true as bar and foo are equal but not the same
Check for 2 Arrays containing NaN
Array.prototype.equals.NaN = true;
console.log(sameElements([NaN],[NaN],true)); //true.
Array.prototype.equals.NaN = false;
Demo on JSFiddle
You can implement the following algorithm:
If a and b do not have the same length:
Return false.
Otherwise:
Clone b,
For each item in a:
If the item exists in our clone of b:
Remove the item from our clone of b,
Otherwise:
Return false.
Return true.
With Javascript 1.6, you can use every() and indexOf() to write:
function sameElements(a, b)
{
if (a.length != b.length) {
return false;
}
var ourB = b.concat();
return a.every(function(item) {
var index = ourB.indexOf(item);
if (index < 0) {
return false;
} else {
ourB.splice(index, 1);
return true;
}
});
}
Note this implementation does not completely fulfill your requirements because indexOf() uses strict equality (===) internally. If you really want non-strict equality (==), you will have to write an inner loop instead.
Like this perhaps?
var foo = {}; var bar=[];
var a = [3,2,1,foo]; var b = [foo,1,2,3];
function comp(a,b)
{
// immediately discard if they are of different sizes
if (a.length != b.length) return false;
b = b.slice(0); // clone to keep original values after the function
a.forEach(function(e) {
var i;
if ((i = b.indexOf(e)) != -1)
b.splice(i, 1);
});
return !b.length;
}
comp(a,b);
UPDATE
As #Bergi and #thg435 point out my previous implementation was flawed so here is another implementation:
function sameElements(a, b) {
var objs = [];
// if length is not the same then must not be equal
if (a.length != b.length) return false;
// do an initial sort which will group types
a.sort();
b.sort();
for ( var i = 0; i < a.length; i++ ) {
var aIsPrimitive = isPrimitive(a[i]);
var bIsPrimitive = isPrimitive(b[i]);
// NaN will not equal itself
if( a[i] !== a[i] ) {
if( b[i] === b[i] ) {
return false;
}
}
else if (aIsPrimitive && bIsPrimitive) {
if( a[i] != b[i] ) return false;
}
// if not primitive increment the __count property
else if (!aIsPrimitive && !bIsPrimitive) {
incrementCountA(a[i]);
incrementCountB(b[i]);
// keep track on non-primitive objects
objs.push(i);
}
// if both types are not the same then this array
// contains different number of primitives
else {
return false;
}
}
var result = true;
for (var i = 0; i < objs.length; i++) {
var ind = objs[i];
// if __aCount and __bCount match then object exists same
// number of times in both arrays
if( a[ind].__aCount !== a[ind].__bCount ) result = false;
if( b[ind].__aCount !== b[ind].__bCount ) result = false;
// revert object to what it was
// before entering this function
delete a[ind].__aCount;
delete a[ind].__bCount;
delete b[ind].__aCount;
delete b[ind].__bCount;
}
return result;
}
// inspired by #Bergi's code
function isPrimitive(arg) {
return Object(arg) !== arg;
}
function incrementCountA(arg) {
if (arg.hasOwnProperty("__aCount")) {
arg.__aCount = arg.__aCount + 1;
} else {
Object.defineProperty(arg, "__aCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}
function incrementCountB(arg) {
if (arg.hasOwnProperty("__bCount")) {
arg.__bCount = arg.__bCount + 1;
} else {
Object.defineProperty(arg, "__bCount", {
enumerable: false,
value: 1,
writable: true,
configurable: true
});
}
}
Then just call the function
sameElements( ["NaN"], [NaN] ); // false
// As "1" == 1 returns true
sameElements( [1],["1"] ); // true
sameElements( [1,2], [1,2,3] ); //false
The above implement actually defines a new property called "__count" that is used to keep track of non-primitive elements in both arrays. These are deleted before the function returns so as to leave the array elements as before.
Fiddle here
jsperf here.
The reason I changed the jsperf test case was that as #Bergi states the test arrays, especially the fact there were only 2 unique objects in the whole array is not representative of what we are testing for.
One other advantage of this implementation is that if you need to make it compatible with pre IE9 browsers instead of using the defineProperty to create a non-enumerable property you could just use a normal property.
Thanks everyone for sharing ideas! I've came up with the following
function sameElements(a, b) {
var hash = function(x) {
return typeof x + (typeof x == "object" ? a.indexOf(x) : x);
}
return a.map(hash).sort().join() == b.map(hash).sort().join();
}
This isn't the fastest solution, but IMO, most readable one so far.
i wasn't sure if "===" is ok, the question is a bit vauge...
if so, this is quite a bit faster and simpler than some other possible ways of doing it:
function isSame(a,b){
return a.length==b.length &&
a.filter(function(a){ return b.indexOf(a)!==-1 }).length == b.length;
}
Edit 2
1) Thanks to user2357112 for pointing out the Object.prototype.toString.call issue
this also showed, the reason it was that fast, that it didn't consider Arrays ...
I fixed the code,it should be working now :), unfortunately its now at about 59ops/s on chrome and 45ops/s on ff.
Fiddle and JSPerf is updated.
Edit
1)
I fixed the code, it supports mutliple variables referencing the same Object now.
A little bit slower than before, but still over 100ops/s on chrome.
2)
I tried using a bitmask instead of an array to keep multiple positions of the objects, but its nearly 15ops/s slow
3) As pointed ot in the comments i forgot to reset tmp after [[get]] is called fixed the code, the fiddle, and the perf.
So thanks to user2357112 with his Answer heres another approach using counting
var sameElements = (function () {
var f, of, objectFlagName;
of = objectFlagName = "__pos";
var tstr = function (o) {
var t = typeof o;
if (o === null)
t = "null";
return t
};
var types = {};
(function () {
var tmp = {};
Object.defineProperty(types, tstr(1), {
set: function (v) {
if (f)
tmp[v] = -~tmp[v];
else
tmp[v] = ~-tmp[v];
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = {};
Object.defineProperty(types, tstr(""), {
set: function (v) {
if (f) {
tmp[v] = -~tmp[v];
} else {
tmp[v] = ~-tmp[v];
}
},
get: function () {
var ret = 1;
for (var k in tmp) {
ret &= !tmp[k];
}
tmp = {};
return ret;
}
});
})();
(function () {
var tmp = [];
function add (v) {
tmp.push(v);
if (v[of]===undefined) {
v[of] = [tmp.length -1];
} else {
v[of].push(tmp.length -1)
}
}
Object.defineProperty(types, tstr({}), {
get: function () {
var ret = true;
for (var i = tmp.length - 1; i >= 0; i--) {
var c = tmp[i]
if (typeof c !== "undefined") {
ret = false
delete c[of]
}
}
tmp = [];
return ret;
},
set: function (v) {
var pos;
if (f) {
add (v);
} else if (!f && (pos = v[of]) !== void 0) {
tmp[pos.pop()] = undefined;
if (pos.length === 0)
delete v[of];
} else {
add (v);
}
}
});
}());
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(undefined), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
(function () {
var tmp = 0;
Object.defineProperty(types, tstr(null), {
get: function () {
var ret = !tmp;
tmp = 0;
return ret;
},
set: function () {
tmp += f ? 1 : -1;
}
});
})();
var tIt = [tstr(1), tstr(""), tstr({}), tstr(undefined), tstr(null)];
return function eq(a, b) {
f = true;
for (var i = a.length - 1; i >= 0; i--) {
var v = a[i];
types[tstr(v)] = v;
}
f = false;
for (var k = b.length - 1; k >= 0; k--) {
var w = b[k];
types[tstr(w)] = w;
}
var r = 1;
for (var l = 0, j; j = tIt[l]; l++) {
r &= types [j]
}
return !!r;
}
})()
Here is a JSFiddle and a JSPerf (it uses the same Arrays a and b as in the previous answers perf) with this code vs the Closure compiled
Heres the output. note: it doesn't support a deep comparison anymore, as is
var foo = {a:2}
var bar = {a:1};
var a = [1, 2, foo, 2, bar, 2];
var b = [foo, 2, 2, 2, bar, 1];
var c = [bar, 2, 2, 2, bar, 1];
console.log(sameElements([NaN],[NaN])); //true
console.log (sameElements ( a,b)) //true
console.log (sameElements (b,c)) //false
Using efficient lookup tables for the counts of the elements:
function sameElements(a) { // can compare any number of arrays
var map, maps = [], // counting booleans, numbers and strings
nulls = [], // counting undefined and null
nans = [], // counting nans
objs, counts, objects = [],
al = arguments.length;
// quick escapes:
if (al < 2)
return true;
var l0 = a.length;
if ([].slice.call(arguments).some(function(s) { return s.length != l0; }))
return false;
for (var i=0; i<al; i++) {
var multiset = arguments[i];
maps.push(map = {}); // better: Object.create(null);
objects.push({vals: objs=[], count: counts=[]});
nulls[i] = 0;
nans[i] = 0;
for (var j=0; j<l0; j++) {
var val = multiset[j];
if (val !== val)
nans[i]++;
else if (val === null)
nulls[i]++;
else if (Object(val) === val) { // non-primitive
var ind = objs.indexOf(val);
if (ind > -1)
counts[ind]++;
else
objs.push(val), counts.push(1);
} else { // booleans, strings and numbers do compare together
if (typeof val == "boolean")
val = +val;
if (val in map)
map[val]++;
else
map[val] = 1;
}
}
}
// testing if nulls and nans are the same everywhere
for (var i=1; i<al; i++)
if (nulls[i] != nulls[0] || nans[i] != nans[0])
return false;
// testing if primitives were the same everywhere
var map0 = maps[0];
for (var el in map0)
for (var i=1; i<al; i++) {
if (map0[el] !== maps[i][el])
return false;
delete maps[i][el];
}
for (var i=1; i<al; i++)
for (var el in maps[i])
return false;
// testing if objects were the same everywhere
var objs0 = objects[0].vals,
ol = objs0.length;
counts0 = objects[0].count;
for (var i=1; i<al; i++)
if (objects[i].count.length != ol)
return false;
for (var i=0; i<ol; i++)
for (var j=1; j<al; j++)
if (objects[j].count[ objects[j].vals.indexOf(objs0[i]) ] != counts0[i])
return false;
// else, the multisets are equal:
return true;
}
It still uses indexOf search amongst all objects, so if you have multisets with many different objects you might want to optimize that part as well. Have a look at Unique ID or object signature (and it's duplicate questions) for how to get lookup table keys for them. And if you don't have many primitive values in the multisets, you might just store them in arrays and sort those before comparing each item-by-item (like #Bruno did).
Disclaimer: This solution doesn't try to get the [[PrimitiveValue]] of objects, they will never be counted as equal to primitives (while == would do).
Here is the update on #Bruno's jsperf test of the answers, yet I guess only two objects (each of them present 500 times in the 10k array) and no duplicate primitive values are not representative.

Categories