Find common objects in multiple arrays by object ID - javascript

I've searched SO for a way to do this but most questions only support two arrays (I need a solution for multiple arrays).
I don't want to compare exact objects, I want to compare objects by their ID, as their other parameters may differ.
So here's the example data:
data1 = [{'id':'13','name':'sophie'},{'id':'22','name':'andrew'}, etc.]
data2 = [{'id':'22','name':'mary'},{'id':'85','name':'bill'}, etc.]
data3 = [{'id':'20','name':'steve'},{'id':'22','name':'john'}, etc.]
...
I'd like to return all objects whose ID appears in all arrays, and I don't mind which of the set of matched objects is returned.
So, from the data above, I'd expect to return any one of the following:
{'id':'22','name':'andrew'}
{'id':'22','name':'mary'}
{'id':'22','name':'john'}
Thanks

First, you really need an array of arrays - using a numeric suffix is not extensible:
let data = [ data1, data2, ... ];
Since you've confirmed that the IDs are unique within each sub array, you can simplify the problem by merging the arrays, and then finding out which elements occur n times, where n is the original number of sub arrays:
let flattened = data.reduce((a, b) => a.concat(b), []);
let counts = flattened.reduce(
(map, { id }) => map.set(id, (map.get(id) || 0) + 1), new Map()
);
and then you can pick out those objects that did appear n times, in this simple version they'll all come from the first sub array:
let found = data[0].filter(({ id }) => counts.get(id) === data.length);
Picking an arbitrary (unique) match from each sub array would be somewhat difficult, although picking just one row of data and picking the items from that would be relatively easy. Either would satisfy the constraint from the question.

If you want the unique object by Name
data1 = [{'id':'13','name':'sophie'},{'id':'22','name':'mary'}]
data2 = [{'id':'26','name':'mary'},{'id':'85','name':'bill'}]
data3 = [{'id':'29','name':'sophie'},{'id':'22','name':'john'}]
flattened = [ ...data1, ...data2, ...data3 ];
counts = flattened.reduce(
(map, { name }) => map.set(name, (map.get(name) || 0) + 1), new Map()
);
names = []
found = flattened.filter(({ name }) => {
if ((counts.get(name) > 1) && (!names.includes(name))) {
names.push(name);
return true
}
return false
});

its too many loops but , if u can find the common id which is present in all the arrays then it would make your finding easier i think .you can have one array value as reference to find the common id
var global = [];
for(var i = 0;i<data1.length;i++){
var presence = true;
for(var j=0;j<arrays.length;j++){
var temp = arrays[j].find(function(value){
return data1[i].id == value.id;
});
if(!temp){
presence = false;
break;
}
}
if(presence){
global.push(data1[i].id)
}
}
console.log(global);
var data1 = [{'id':'13','name':'sophie'},{'id':'22','name':'andrew'}];
var data2 = [{'id':'22','name':'mary'},{'id':'85','name':'bill'}];
var data3 = [{'id':'20','name':'steve'},{'id':'22','name':'john'}];
var arrays = [data1, data2, data3];
var global = [];
for(var i = 0;i<data1.length;i++){
var presence = true;
for(var j=0;j<arrays.length;j++){
var temp = arrays[j].find(function(value){
return data1[i].id == value.id;
});
if(!temp){
presence = false;
break;
}
}
if(presence){
global.push(data1[i].id)
}
}
console.log(global);

There's mention you you need n arrays, but also, given that you can:
put all the arrays into an array called data
you can:
combine your arrays
get a list of duplicated IDs (via sort by ID)
make that list unique (unique list of IDs)
find entries in the combined list that match the unique IDs
where the count of those items match the original number of arrays
Sample code:
// Original data
var data1 = [{'id':'13','name':'sophie'},{'id':'22','name':'andrew'}]
var data2 = [{'id':'22','name':'mary'},{'id':'85','name':'bill'}]
var data3 = [{'id':'13','name':'steve'},{'id':'22','name':'john'}]
var arraycount = 3;
// Combine data into a single array
// This might be done by .pushing to an array of arrays and then using .length
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?v=control
var data = [].concat(data1).concat(data2).concat(data3);
//console.log(data)
// Sort array by ID
// http://stackoverflow.com/questions/840781/easiest-way-to-find-duplicate-values-in-a-javascript-array
var sorted_arr = data.slice().sort(function(a, b) {
return a.id - b.id;
});
//console.log(sorted_arr)
// Find duplicate IDs
var duplicate_arr = [];
for (var i = 0; i < data.length - 1; i++) {
if (sorted_arr[i + 1].id == sorted_arr[i].id) {
duplicate_arr.push(sorted_arr[i].id);
}
}
// Find unique IDs
// http://stackoverflow.com/questions/1960473/unique-values-in-an-array
var unique = duplicate_arr.filter(function(value, index, self) {
return self.indexOf(value) === index;
});
//console.log(unique);
// Get values back from data
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter?v=control
var matches = [];
for (var i = 0; i < unique.length; ++i) {
var id = unique[i];
matches.push(data.filter(function(e) {
return e.id == id;
}))
}
//console.log(matches)
// for data set this will be 13 and 22
// Where they match all the arrays
var result = matches.filter(function(value, index, self) {
return value.length == arraycount;
})
//console.log("Result:")
console.log(result)
Note: There's very likely to be more efficient methods.. I've left this in the hope part of it might help someone

var arr1 = ["558", "s1", "10"];
var arr2 = ["55", "s1", "103"];
var arr3 = ["55", "s1", "104"];
var arr = [arr1, arr2, arr3];
console.log(arr.reduce((p, c) => p.filter(e => c.includes(e))));
// output ["s1"]

Related

what's the most efficient way to split an array of millions of data based on condition?

It goes something like this where I have a london array containing more than 10 million data
london = ['dwig7xmW','gIzbnHNI' ...]
And now I have a userTraveled which also contains millions of data
userTraveled = ['ntuJV09a' ...]
Now what's the most efficient way to split userTraveled into inLondon and notInLondon.
My attempt.
inLondon = []
notInLondon = []
userTraveled.forEach((p) => london.includes(p) ? inLondon.push(p) : notInLondon.push(p))
london.includes(p) will do a linear search over the array. Doing that for every userTraveled is horribly inefficient. Use a Set instead:
const usersInLondon = [], usersNotInLondon = [];
const lookup = new Set(london);
for (const p of usersTraveled) {
(lookup.has(p) ? usersInLondon : usersNotInLondon).push(p);
}
I can offer a O(n*log(n)) solution instead of your O(n^2), first order the passwords and later use the binary search on it instead of the include to search for an item
Hope it helps =)
const london = ['dwig7xmW','gIzbnHNI']
const userTraveled = ['ntuJV09a', 'dwig7xmW']
let inLondon = []
let notInLondon = []
const sortedlondon=london.sort();
userTraveled.forEach((p) => (binarySearch(sortedlondon,p)!=-1 ? inLondon.push(p) : notInLondon.push(p)))
//https://www.htmlgoodies.com/javascript/how-to-search-a-javascript-string-array-using-a-binary-search/
function binarySearch(items, value){
var startIndex = 0,
stopIndex = items.length - 1,
middle = Math.floor((stopIndex + startIndex)/2);
while(items[middle] != value && startIndex < stopIndex){
//adjust search area
if (value < items[middle]){
stopIndex = middle - 1;
} else if (value > items[middle]){
startIndex = middle + 1;
}
//recalculate middle
middle = Math.floor((stopIndex + startIndex)/2);
}
//make sure it's the right value
return (items[middle] != value) ? -1 : middle;
}
I hope you are not using these data in a wrong way.
const passwords = ['a', 'b']
const rawPasswords = ['c', 'b'];
const setPasswords = new Set(passwords)
const uniquePassword = [];
const usedPassword = [];
rawPasswords.forEach(rp => {
if (setPasswords.has(rp)) {
usedPassword.push(rp)
} else {
uniquePassword.push(rp)
}
})
console.log(uniquePassword, usedPassword)
Referring to this answer for performance tests: Get all unique values in a JavaScript array (remove duplicates) the best solution in your case would be to use an Object. Since you require to know about the duplicates and not just remove them.
function uniqueArray( ar ) {
var j = {};
var k = [];
var unique;
ar.forEach( function(v) {
if(j.hasOwnProperty(v)){
k.push(v);
} else {
j[v] = v;
}
});
unique = Object.keys(j).map(function(v){
return j[v];
});
return [unique, k];
}
var arr = [1, 1, 2, 3, 4, 5, 4, 3];
console.log(uniqueArray(arr));
First it loops through the input array and checks if the value is already existing as a key on the object. If that's not the case, it adds it. If it is, it pushes the value to another array. Since objects use a hash, the Javascript engine can work faster with it.
Secondly it goes through the object's keys to turn it back into an array and finally returns both. I didn't add this explanation because the provided reference already explained it.
The result will be an array containing 2 arrays. First the array with unique values, second the array with duplicates.

Compare 2 different Arrays by ID and calculate difference

I got 2 arrays
ArrayA = {"data":{"PlayerList":[{"Platform":1,"PlayerExternalId":205288,"Price":250,"RemainingTime":22},{"Platform":1,"PlayerExternalId":205753,"Price":10000,"RemainingTime":22}]}}
ArrayB = {"datafut": [{"currentPricePs4": "4149000","currentPriceXbox": "3328000","PlayerExternalId": "151152967"},{"currentPricePs4": "3315000","currentPriceXbox": "2720000","PlayerExternalId": "151198320"}]}
ArrayB is like a small database to compare prices. ArrayA needs theoretically an Interception with ArrayB. But this creates a new ArrayC which is complicated for me because I need the index of the results from ArrayA.
Moreover when comparing both array IDs, I need to compare both prices and calculate a difference into a variable so I can work with it later. How can I achieve this?
This is my pseudo code. Idk if this is even the right way..
Filter ArrayB by ArrayA //by playerID
for(
NewPrice = ArrayA.price / ArrayB.price + Index of ArrayA.price
index = Index of ArrayA.price)
Edit: or could I append the price from arrayB to arrayA and can calculate then somehow?
You can pass both arrays to following function: I have stored index, now if you only need index, you don't need to sort it otherwise I am sorting it on the base of index to keep the original order.
function mergeArrays(arrayA, arrayB) {
var players = arrayA.data.PlayerList;
var data = arrayB.data;
var arrayC = [];
for(let i=0; i<data.length; i++) {
var playerId = data[i].PlayerExternalId;
for(let j=0; j<players.length; j++) {
if(players[j].PlayerExternalId != playerId) {
continue;
}
var obj = {};
obj.playerId = playerId;
obj.index = j;
obj.price = players[j].price;
obj.xboxprice = data[i].currentPriceXbox;
obj.ps4price = data[i].currentPricePs4;
arrayC.push(obj);
}
}
arrayC.sort((a,b) => (a.index < b.index)?-1:(a.index>b.index?1:0));
return arrayC;
}

Checking multiple variables with the same "rules"

I have to construct an array of objects. I can do it "long hand," but I'm hoping to find a way to iterate through some variables and check each at "push" them into the right spot in the array.
I have this:
//this is the starting array...I'm going to update these objects
operationTime = [
{"isActive":false,"timeFrom":null,"timeTill":null},//Monday which is operationTime[0]
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null}
];
//I get the below via an API call
var monHours = placeHours.mon_open_close;
var tueHours = placeHours.tue_open_close;
var wedHours = placeHours.wed_open_close;
var thuHours = placeHours.thu_open_close;
var friHours = placeHours.fri_open_close;
var satHours = placeHours.sat_open_close;
var sunHours = placeHours.sun_open_close;
var sunHours = placeHours.sun_open_close;
//here's where I'm stuck.
if (monHours.length>0){
var arr = monHours[0].split("-");
operationTime[0].isActive= true;
operationTime[0].timeFrom= arr[0];
operationTime[0].timeTill= arr[1];
}
else {
operationTime[0].isActive= false;
}
My if/else works perfectly in the above example using Monday, but I don't want to write this for seven days of the week making it unnecessarily complicated. How could I condense this into a single "function" that'll test each variable and push it into the array object in the correct position?
I guess you can put the keys in an array, and then use forEach loop through operationTime and update the object based on the index:
operationTime = [
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null},
{"isActive":false,"timeFrom":null,"timeTill":null}
];
// make an array of keys that has the same order of the operationTime
var keys = ['mon_open_close', 'tue_open_close', 'wed_open_close', 'thu_open_close', 'fri_open_close', 'sat_open_close', 'sun_open_close'];
var placeHours = {'mon_open_close': ['08:00-17:00'], 'tue_open_close':[], 'wed_open_close':[], 'thu_open_close':[], 'fri_open_close':[], 'sat_open_close':[], 'sun_open_close':['10:20-15:30']}
operationTime.forEach( (obj, index) => {
var dayHours = placeHours[keys[index]];
if(dayHours.length > 0) {
var arr = dayHours[0].split("-");
obj.isActive= true;
obj.timeFrom= arr[0];
obj.timeTill= arr[1];
}
})
console.log(operationTime);
You can try this way with foreach all days's hour,
$all_hours = [monHours, tueHours , wedHours , thuHours , friHours , satHours ,sunHours];
foreach($all_hours as $k=>$hours){
if ($hours.length>0){
$arr = $hours[k].split("-");
operationTime[$k].isActive= true;
operationTime[$k].timeFrom= $arr[0];
operationTime[$k].timeTill= $arr[1];
}
else {
operationTime[$k].isActive = false;
}
}
You can use Object.entries() to iterate properties and values of an object as an array, .map() to define and include index of iteration in block of for..of or other loop. The index is utilized to reference object at index of operationTime array
for (let
[key, prop, index]
of
Object.entries(placeHours)
.map(([key, prop], index) => [key, prop, index]))) {
if (prop.length > 0 ) {
let [arr] = prop.split("-");
operationTime[index].isActive = true;
operationTime[index].timeFrom = arr[0];
operationTime[index].timeTill = arr[1];
}
}

Get full value from array using partial value

I have an array like this:
var array = ["xs-1", "sm-10", "md-4"];
Now I want to get the number at the end of a particular value. For example I want to search the array for "md-" and see what number is at the end of that string (should return 4).
I can't do array.indexOf("xs-") because that isn't the whole value. Is there a way to do this?
Using a for loop:
var array = ["xs-1", "sm-10", "md-4"];
var search = "md-";
var found = null;
for (var i = 0; i < array.length; i++) {
if (array[i].indexOf(search) === 0) {
found = array[i];
break; // Note: this is assuming only one match exists - or at least you are
// only interested in the first match
}
}
if (found) {
alert(found);
} else {
alert("Not found");
}
Using .filter:
var array = ["xs-1", "sm-10", "md-4"];
var search = "md-";
var filtered = array.filter(function(item) {
return item.indexOf(search) === 0;
});
// note that here filtered will contain all matched elements, so it might be more than
// one match.
alert(filtered);
Building from #János Weisz's suggestion, you can easily transform your array into an object using .reduce:
var array = ["xs-1", "sm-10", "md-4"];
var search = "md";
var obj = array.reduce(function(prev, item) {
var cells = item.split("-");
prev[cells[0]] = cells[1];
return prev;
}, {});
// note: at this point we have an object that looks like this:
// { xs:1, sm:10, md: 4 }
// if we save this object, we can do lookups much faster than looping
// through an array
// now to find "md", we simply do:
alert(obj[search]);
If you need to do multiple look ups from the same source array, then transforming it into an object may be the most efficient approach overall. You pay the initial price of the transformation, but after than lookups are O(1) versus O(n) for each time you have to search your array. Of course, if you only ever need one item, then probably don't bother.
I recommend using objects for this:
var array = [{'type': 'xs', 'value': 1}, {'type' : 'sm', 'value': '10'}, {'type' : 'md', 'value': '4'}];
This way you can search the array as:
function searchMyArrayByType(array, type) {
var items[];
for (var i = 0; i < array.length; i++)
{
if (array[i].type == type) items.push(array[i].value);
}
return items;
}
var valuesWithMd = searchMyArrayByType(array, 'md');
For more information regarding the structure and use of objects, please refer to https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects
You can create a method that takes the prefix you're looking for, the array, and the split character and returns all the numbers in an array:
function findNumberFromPrefix(prefix, arr, splitChar) {
var values = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i].indexOf(prefix) === 0) {
values.push(arr[i].split(splitChar)[1]);
}
}
return values;
}
And call it:
var array = ["xs-1", "sm-10", "md-4"];
var values = findNumberFromPrefix("md-", array, "-");
console.log(values); //["4"]
Demo: http://jsfiddle.net/rn4h9msh/
A more functional approach and assuming you can have have more than one element with the same prefix:
function findPrefix(array, prefix) {
return array.filter(function (a) { return a.indexOf(prefix) === 0; })
.map(function (e) { return e.slice(prefix.length); })
}
If you have only one matching element, do a loop like this:
var array = ["xs-1", "sm-10", "md-4"];
var needle = "md-";
for(i=0;i<array.length;i++) {
if(array[i].indexOf(needle) == 0)
alert(array[i].substr(needle.length, array[i].length));
}
Demo: http://jsfiddle.net/kg0c43ov/
You can do it like this...
var array = ["xs-1", "sm-10", "md-4"];
getValue("md-");
function getValue(search) {
for(var key in array) {
if(array[key].indexOf(search) > -1) {
alert("Array key is: " + key);
alert("Array value is: " + array[key].replace(search, ""));
}
}
}
JSFiddle here.

Find duplicates without going through the list twice?

I need to know if one or more duplicates exist in a list. Is there a way to do this without travelling through the list more than once?
Thanks guys for the suggestions. I ended up using this because it was the simplest to implement:
var names = [];
var namesLen = names.length;
for (i=0; i<namesLen; i++) {
for (x=0; x<namesLen; x++) {
if (names[i] === names[x] && (i !== x)) {alert('dupe')}
}
}
Well the usual way to do that would be to put each item in a hashmap dictionary and you could check if it was already inserted. If your list is of objects they you would have to create your own hash function on the object as you would know what makes each one unique. Check out the answer to this question.
JavaScript Hashmap Equivalent
This method uses an object as a lookup table to keep track of how many and which dups were found. It then returns an object with each dup and the dup count.
function findDups(list) {
var uniques = {}, val;
var dups = {};
for (var i = 0, len = list.length; i < len; i++) {
val = list[i];
if (val in uniques) {
uniques[val]++;
dups[val] = uniques[val];
} else {
uniques[val] = 1;
}
}
return(dups);
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
findDups(data); // returns {2: 3, 3: 2, 9: 2}
var data2 = [1,2,3,4,5,6,7,8,9];
findDups(data2); // returns {}
var data3 = [1,1,1,1,1,2,3,4];
findDups(data3); // returns {1: 5}
Since we now have ES6 available with the built-in Map object, here's a version of findDups() that uses the Map object:
function findDups(list) {
const uniques = new Set(); // set of items found
const dups = new Map(); // count of items that have dups
for (let val of list) {
if (uniques.has(val)) {
let cnt = dups.get(val) || 1;
dups.set(val, ++cnt);
} else {
uniques.add(val);
}
}
return dups;
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
log(findDups(data)); // returns {2 => 3, 3 => 2, 9 => 2}
var data2 = [1,2,3,4,5,6,7,8,9];
log(findDups(data2)); // returns empty map
var data3 = [1,1,1,1,1,2,3,4];
log(findDups(data3)); // returns {1 => 5}
// display resulting Map object (only used for debugging display in snippet)
function log(map) {
let output = [];
for (let [key, value] of map) {
output.push(key + " => " + value);
}
let div = document.createElement("div");
div.innerHTML = "{" + output.join(", ") + "}";
document.body.appendChild(div);
}
If your strings are in an array (A) you can use A.some-
it will return true and quit as soon as it finds a duplicate,
or return false if it has checked them all without any duplicates.
has_duplicates= A.some(function(itm){
return A.indexOf(itm)===A.lastIndexOf(itm);
});
If your list was just words or phrases, you could put them into an associative array.
var list=new Array("foo", "bar", "foobar", "foo", "bar");
var newlist= new Array();
for(i in list){
if(newlist[list[i]])
newlist[list[i]]++;
else
newlist[list[i]]=1;
}
Your final array should look like this:
"foo"=>2, "bar"=>2, "foobar"=>1

Categories

Resources