Given an array of arrays, what would be the efficient way of identifying the duplicate item?
var array = [
[
11.31866455078125,
44.53836644772605
],
[ // <-- Here's the duplicate
11.31866455078125,
44.53836644772605
],
[
11.371536254882812,
44.53836644772605
],
[
11.371536254882812,
44.50140292110874
]
]
I've been working on this with lodash as an accepted dependency, and I get how to just return the "unique" list using _.uniqWith and _.isEqual:
_.uniqWith(array,_.isEqual)
With would give the "unique" version of the list:
[
[ 11.31866455078125, 44.53836644772605 ],
[ 11.371536254882812, 44.53836644772605 ],
[ 11.371536254882812, 44.50140292110874 ]
]
But rather than just reporting the unique elements, I need just the element that is duplicated, and ideally the index of the first occurrence.
Is this actually covered in the lodash library by some combination of methods that I'm missing? Or am I just going to have to live with writing loops to compare elements.
Probably just overtired on this, so fresh eyes on the problem would be welcome.
Trying not to rewrite functions if there are library methods that suit, so I basically am stuck with:
Returning just the duplicate or at least the comparison difference with the "unique list".
Basically identifying the "index of" an array within an array. Though I suppose that can be a filter reduction with _.isEqual once the duplicate item is identified.
Trying also to avoid creating an object Hash/Map and counting the occurrences of keys here as well, or at least not as a separate object, and as something that can be done functionally "in-line".
Lodash gives a lot of useful functions to achieve finding the first duplicate index.
Using the _.findIndex() and _.isEqual() the following code will find the first duplicate index:
var duplicateIndex = _.findIndex(array, function(value, index, collection) {
var equal = _.isEqual.bind(undefined, value);
return _.findIndex(collection.slice(0, index), equal) !== -1;
});
or a bit faster but more verbose:
var duplicateIndex = _.findIndex(array, function(value, index, collection) {
var equal = _.isEqual.bind(undefined, value);
return _.findIndex(collection, function(val, ind) {
return ind < index && equal(val);
}) !== -1;
});
Notice that if no duplicate exists, -1 will be returned.
In a few words the algorithm iterates through array and looks back if the current element does not exist already. If it does, just return the current iteration index.
Please check the working demo.
Here's an approach that uses uniqWith(), and difference():
_.indexOf(array, _.head(_.difference(array, _.uniqWith(array, _.isEqual))));
The basic idea is:
Use uniqWith() to remove the duplicates from array.
Use difference() to compare array against the duplicate-free version. This gets us an array of the duplicates.
Use head() to get the first item of the array. This is the duplicate that we're interested in.
Use indexOf() to find the index of the duplicate, in this case it's 1.
However, if you need the index of the original, and not it's duplicate, we have to make some adjustments:
var duplicate = _.head(_.difference(array, _.uniqWith(array, _.isEqual)));
_.findIndex(array, _.unary(_.partial(_.isEqual, duplicate)));
We're still using uniqWith(), and difference() to find the duplicate. But now, we're using findIndex() to get the index. The reason is that we need to use isEqual() to find the first position of the duplicate, not the second. We construct the predicate using partial() and unary(). The result this time is 0.
You can just use plain javascript to do that, it's not that hard, here is my implementation
for (let i = 0; i < array.length; i++) {
for (let j = i + 1; j < array.length; j++) {
// quick elimination by comparing sub-array lengths
if (array[i].length !== array[j].length) {
continue;
}
// look for dupes
var dupe = true;
for (var k = 0; k < array[i].length; k++) {
if (array[i][k] !== array[j][k]) {
dupe = false;
break;
}
}
// if a dupe then print
if (dupe) {
console.debug("%d is a dupe", j);
}
}
}
The nice part about this implementation is that it will print you multiple times that an array at an index is a dupe for multiple dupes, you can use that fact to count your dupes in each index!
This is actually a very efficient way to do this because the inner for loop (j) always runs from the next position of the outer loop (i). so you half your check count.
And here is a plunk
I don't know how to do this other than to just write the algorithm yourself. Both this answer and the other posted ones aren't very efficient but should be fine:
function findIndex(array, startingIndex, value) {
var predicate = _.partial(_.isEqual, value);
var arraySubset = array.slice(startingIndex+1);
var index = arraySubset.findIndex(predicate);
return index === -1 ? index : index+startingIndex+1;
}
function findDuplicates(array) {
return array.map((value, index) => {
return {
value,
index: findIndex(array, index, value)
};
}).filter(info => info.index !== -1);
}
findDuplicates([1, 2, 3, 4, 1, [ 3 ], [ 4 ], [ 3 ] ]);
// [ { value: 1, index: 4 }, { value: [ 3 ], index: 7 } ] // [ { value: 1, index: 4 }, { value: [ 3 ], index: 7 } ]
This basically creates a map of the array, calling .findIndex() on the remainder of the array, noting down the index of any duplicates, returning information about every item that has a duplicate and what the index of the duplicate is.
One nice thing about this is that it will work for triplicates or any amount of occurrences of a value.
I believe constructing a LUT is one of the most efficient ways when it comes to making comparisons. The following method constructs a LUT by utilizing Array.prototype.reduce() and eventually mutates the original array by removing not only one but all duplicate elements regardless of how many there are.
var arr = [
[
11.31866455078125,
44.53836644772605
],
[
11.31866455078125,
44.53836644772605
],
[
11.371536254882812,
44.53836644772605
],
[
11.371536254882812,
44.50140292110874
]
];
arr.reduce((p,c,i)=> { var prop = c[0]+"" + c[1]+"";
p[prop] === void 0 ? p[prop] = i : p.dups.push(i);
return p;
},{dups:[]}).dups.reverse().forEach( i => arr.splice(i,1))
document.write('<pre>' + JSON.stringify(arr, 0, 2) + '</pre>');
However if you would like to have a new array by keeping the original then obviously it would be much faster procedure.
Related
I am trying to make a function that removes strings from an array if they include a character in a certain other list
Here is the code:
var possible = ["salutations", "goodbye", "thanks", "welcome"];
var incorrect = ["o"];
console.log(possible);
function narrowdown(possible, incorrect)
{
var templist = possible;
for (i in possible)
{
console.log(i + " " + possible[i]);
var array1 = possible[i].split("");
var common = array1.filter(value => incorrect.includes(value));
console.log(common)
if (common.length)
{
templist.splice(i, 1);
}
}
possible = templist;
}
narrowdown(possible, incorrect);
console.log(possible);
Here I am trying to remove all the words that include the letter o. I created a temporary array in the function because it has happened to me before that a for loop skips items altogether.
The code first logs the index of the item in the list and then the item itself.
Then it turns the word into an array and checks for overlap between it and the "incorrect" array. It does that correctly and logs the overlapping characters.
The issue seems to be that it skips over the "goodbye" item for some reason. It doesn't even process it.
Here is the output I am getting:
[ 'salutations', 'goodbye', 'thanks', 'welcome' ]
0 salutations
[ 'o' ]
1 thanks
[]
2 welcome
[ 'o' ]
[ 'goodbye', 'thanks' ]
First of all, for (i in possible) is a bad way of looping through an array since it retrieves the keys before the loop begins and it never updates that list of keys. Also, if someone assigns an attribute to the array, like possible.foo = 17, then your loop will also go through that. The issue you're having is that when you splice the array, everything else is shifted one to the left, changing their indices to be one less, so your new index actually skips over the next element. The fix is to use a conventional for loop and decrement i after splicing:
for (let i = 0; i < possible.length; i ++) {
// more code...
if (common.length) {
templist.splice(i, 1);
i --;
}
}
The problem is that the array is re-indexed when you splice. One solution is to iterate in reverse:
var possible = ["salutations", "goodbye", "thanks", "welcome"];
var incorrect = ["o"];
console.log(possible);
function narrowdown(possible, incorrect)
{
var templist = possible;
var i = possible.length;
while (i--)
{
console.log(i + " " + possible[i]);
var array1 = possible[i].split("");
var common = array1.filter(value => incorrect.includes(value));
console.log(common)
if (common.length)
{
templist.splice(i, 1);
}
}
possible = templist;
}
narrowdown(possible, incorrect);
console.log(possible);
The issue comes from the line tempList = possible which is an assignment by reference, meaning that when you do the splice operation you do it on both arrays at the same time.
Generally it is considered bad form to manipulate data like that anyway, you should have narrowDown return a value that you re-assign to possible instead of filtering them in place. If you do that you can also leverage some of the newer array methods:
var possible = ["salutations", "goodbye", "thanks", "welcome"];
var incorrect = ["o"];
function narrowdown(possible, incorrect)
{
return possible.filter(item => !incorrect.some(test => item.includes(test)))
}
possible = narrowdown(possible, incorrect);
console.log(possible);
using the .some will also exit early as soon as there is a match, instead of looping over all incorrect values and the entire string, boosting performance slightly
If you're looking for a less "imperative" version of this function, you can make use of Array.filter(), Array.some(), and String.includes() to make a one-liner function that does the trick (for formatting purposes, I split it in the multiple lines in the snippet below).
const possible = ['salutations', 'goodbye', 'thanks', 'welcome'];
function narrowDown(possible, incorrect) {
return possible.filter((value) => (
!incorrect.some((exclude) => (value.includes(exclude)))
));
}
console.log('["o"]', JSON.stringify(narrowDown(possible, ['o'])));
console.log('["a"]', JSON.stringify(narrowDown(possible, ['a'])));
console.log('["a", "o"]', JSON.stringify(narrowDown(possible, ['a', 'o'])));
let's say i have this code :
let variants = []
let variant = {
variantName: 'Size',
variantItems: ['XL','MD','SM']
}
variants.push(variant)
variant = {
variantName: 'Color',
variantItems: ['Red','Blue']
}
variants.push(variant)
okay, now how do i output it to something like this :
and the variants array can contain many variant object
and if there is one more object inside variants array :
variant = {
variantName: 'Material',
variantItems: ['Plastic','Wood', 'Ceramic']
}
variants.push(variant)
and it will be outputted like this :
Please help, i very appreciate your help...
You can use a recursive function with Array#map, and Array#concat to flatten the results:
const variants = [{"variantName":"Size","variantItems":["XL","MD","SM"]},{"variantName":"Color","variantItems":["Red","Blue"]},{"variantName":"Material","variantItems":["Plastic","Wood","Ceramic"]}];
const addVariants = (variants) => {
// destructure the 1st item array, and the rest of the variants
const add = ([{ variantName, variantItems }, ...variants], row = []) =>
// iterate the variants and flatten
[].concat(...variantItems.map((variantItem) => {
// create a new array for the current row, and the variant string
const currRow = [...row, `${variantName}: ${variantItem}`];
// if there are more variants, invoke add with the remaining variants and the current row, or join the array to a string (the end result)
return variants.length ? add(variants, currRow) : currRow.join(', '); // instead of currRow.join(', ') replace with [currRow] if you want an array
}));
return add(variants);
}
const result = addVariants(variants);
console.log(result);
This is a functional ES6 approach.
let variants = [{
variantName: "Size",
variantItems: [
"XL",
"MD",
"SM"
]
},
{
variantName: "Color",
variantItems: [
"Red",
"Blue"
]
}];
let crossJoined = new Array(variants.reduce((product, variant) => (product * variant.variantItems.length), 1))
.fill(0)
.reduce(crossJoin => {
crossJoin.data.push(crossJoin.currentIndexes.map((itemIndex, variantIndex) => `${variants[variantIndex].variantName}: ${variants[variantIndex].variantItems[itemIndex]}`).join(", "));
let incrementableIndex = variants.length - crossJoin.currentIndexes
.slice()
.reverse()
.findIndex((itemIndex, variantIndex) => variants[variants.length - variantIndex - 1].variantItems.length > itemIndex + 1) - 1;
crossJoin.currentIndexes[incrementableIndex]++;
crossJoin.currentIndexes = crossJoin.currentIndexes.map((value, index) => (index > incrementableIndex
? 0
: value));
return (crossJoin.currentIndexes.length == variants.length
? crossJoin
: crossJoin.data);
}, {
data: [],
currentIndexes: new Array(variants.length).fill(0)
}).join("\n");
console.log(crossJoined);
First, an array is created with a length of all variantItems array lengths multiplied, then it is zero-filled. Next, we reduce it to another array.
crossJoin is the aggregator that holds an object of this structure most of the time:
{
data: [ … ],
currentIndexes: [ 1, 3 ] // Corresponds to variants[0].variantItems[1]
// and variants[1].variantItems[3]
}
That is “most of the time”, until the end, where currentIndexes won’t be used anymore and only its data property is returned.
So in each reduce iteration, we first push a new entry to crossJoin.data, using crossJoin.currentIndexes and the indexes of that array. Removing .join(", ") will result in an entry of the structure
[ "Size", "XL" ]
Next, we need to increment the numbers in crossJoin.currentIndexes:
[ 0, 0 ] should be incremented to [ 0, 1 ], because there is a second color at the index 1. [ 0, 1 ] should be incremented to [ 1, 0 ], because there is no third color, but a second size, but then we need the first color again, and so on. The last valid index array is [ 2, 1 ] which corresponds to the third size and the second color, which is the last combination.
incrementableIndex is the last possible index that can still be incremented. Once incremented, all subsequent indexes have to be 0.
You see those variants.length - something - 1 twice, because you need to find the first index from the end, so you have to reverse (a copy of — hence the slice) the array, then re-interpret the found index as an index from the start again.
The return returns this crossJoin object for the next iteration. The condition crossJoin.currentIndexes.length == variants.length applies to the very end, where no index can be incremented anymore. It adds a NaN to the currentIndexes array, so the lengths don’t match, and instead of filtering out or preventing the NaN, I just ignored and discarded it altogether.
The output will be an array of all combinations. You can .join("\n") it to make a string where each combination is separated by a line-break, or you can use .forEach to append each combination to a list, for example.
Use .each loop on variants array. Take another array and add object using size, color and material. So it will become combined array of your requirement. Then print new array data as per requirement using .each loop .
i have two arrays like this. first array is customFields and length is 2
var customFields = [
{
"$$hashKey":"object:259",
"fields":[
],
"id":0.84177744416334,
"inputType":"number",
"labelShown":"item",
"type":"textBox",
"value":"222222"
},
{
"$$hashKey":"object:260",
"fields":[
"as",
"dd",
"asd"
],
"id":0.51091342118417,
"inputType":"",
"labelShown":"labels",
"type":"selectBox",
"value":"dd"
}
]
second one is field and length is 3
var field = [
{
"fields":[
],
"id":0.84177744416334,
"inputType":"number",
"labelShown":"item",
"type":"textBox"
},
{
"fields":[
"as",
"dd",
"asd"
],
"id":0.51091342118417,
"inputType":"",
"labelShown":"labels",
"type":"selectBox"
},
{
"fields":[
],
"id":0.32625015743856,
"inputType":"text",
"labelShown":"sample",
"type":"textBox"
}
]
both arrays are dynamic and i need to compare these arrays by id fields and add missing objects to customFields array from field array. how can i do this without 2 for loops looping inside one another. what is the most efficient way. thank you !!!!
You can use reduce() and find() to get desired result.
var customFields = [{"$$hashKey":"object:259","fields":[],"id":0.84177744416334,"inputType":"number","labelShown":"item","type":"textBox","value":"222222"},{"$$hashKey":"object:260","fields":["as","dd","asd"],"id":0.51091342118417,"inputType":"","labelShown":"labels","type":"selectBox","value":"dd"}];
var field = [{"fields":[],"id":0.84177744416334,"inputType":"number","labelShown":"item","type":"textBox"},{"fields":["as","dd","asd"],"id":0.51091342118417,"inputType":"","labelShown":"labels","type":"selectBox"},{"fields":[],"id":0.32625015743856,"inputType":"text","labelShown":"sample","type":"textBox"}]
var result = field.reduce(function(r, e) {
var f = customFields.find(el => e.id == el.id)
r.push(f ? f : e)
return r;
}, [])
console.log(result)
You can use native higher order functions such as map and reduce.
Sample implementation using lodash is here. https://github.com/rbs392/object-deep-diff/blob/master/index.js
Here's a solution without nested loops. First a lookup table is produced containing ID's of the customFields array. Next the field array is traversed and each missing object is appended to customFields array. Lookup table is also updated to take care of possible duplicates.
var lut = customFields.map(function(obj) {
return obj.id;
});
field.forEach(function(obj) {
if (lut.indexOf(obj.id) == -1) {
customFields.push(obj);
lut.push(obj.id);
}
});
As noted in comments, my first proposition hid complexity in indexOf.
Here's an alternative approach that relies on object properties for ID lookup, which is likely better than linear search. lut maintains an association from ID to customFields array index.
var lut = customFields.reduce(function(t, obj, i) {
t[obj.id] = i;
return t;
}, {});
field.forEach(function(obj) {
if (undefined === lut[obj.id]) {
lut[obj.id] = customFields.push(obj) - 1;
}
});
There are two arrays:
itemKeys: [
{
name: "REFOBJTYPE"
},
{
name: "REFOBJKEY"
}
...
]
itemValues: [
{
value: ""
},
{
value: ""
}
]
and an object
ref: {
REFOBJTYPE: 1,
REFOBJKEY: 2,
}
They are fixed and the structure itself cannot be changed.
values of itemValues should be filled with values from ref object,
to get index we have to look up the itemKeys array.
The point of this question: I don't want to use 2 "for" loops to check for each key if it exists in ref. I WANT use JAVASCRIPT specific features like maybe "indexOf", so:
is the ANY OTHER way, rather than TWO FOR-LOOPs to complete this task?
Please don't question why I need this, why don't I like 2 loops. Obviously under any implementation "behind" it will be 2 loops.
I'm not sure if this is what you meant, but if you loop over the itemKeys array, you can easily look up the value associated with the key in the ref object, and then write that out to the itemValues array in one loop.
for (var i = 0; i < itemKeys.length; i++) {
var key = itemKeys[i].name;
var value = ref[key];
itemValues[i].value = value;
}
I'm trying to make something similar to an array.
I need to be able to "release" an Index (set its value to undefined) but I don't want to lose the Index.
The "released" Indexes should be re-used whenever a new item is put into the array.
I'd like to be able to do something like:
example = new MyArray();
a = example.leaseIndex(); // returns 0
example[a] = "example value";
b = example.leaseIndex(); // returns 1
example[b] = "another value";
example.releaseIndex(0);
c = example.leaseIndex(); // returns 0
example[c] = "yet another value";
In my example leaseIndex finds an available index or if none are available pushes a new item onto the array and returns the index of that item.
I want to do this so that the array doesn't just grow bigger over time needlessly.
I can't remove the "released" items as each item in the array contains a reference to another item in the same array.
I've had minor successes with functions and arrays outside of the main one to keep track of the available indexes and assign and release them, but ideally I'd like the functionality to be part of the main array.
Would I have to add my functions to the Array (or its prototype) or is there another way? As not all my arrays need this functionality.
Hope this makes sense :/
update
I am trying to store wiring loom layouts, which are essentially a net diagram (points and information as to how the points are connected).
The picture shows an example loom. It has 3 Connectors; Red (0) with 2 Lines, Yellow (1) with 3 Lines and Green (2) with 2 Lines.
One of the lines on the Red Connector is Spliced (allowing multiple Lines to connect to a Single Line, the blue square)
This is how that loom would be stored.
loom = {
points = [
{ self: 0, list: [ 2 ] },
{ self: 1, list: [ 7 ] },
{ self: 2, list: [ 0 ] },
{ self: 3, list: [ 7 ] },
{ self: 4, list: [ 6 ] },
{ self: 5, list: [ 7 ] },
{ self: 6, list: [ 4 ] },
{ self: 7, owner: 1, list: [ 1, 3, 5 ] }
],
connectors = [
[ 0, 1 ],
[ 2, 3, 4 ],
[ 5, 6 ]
]
}
the elements in the connectors array contain the indexes of points in the points array.
the list array inside each points object contains the index of its destination(s) which are points too.
Im trying to make functions to help make managing the indexes easier, just wanted to know if there is a way to extend the array, or make something similar that incorporates the functionality. Using static functions would be OK and is what I've been using. I just wanted to see if i could extend the array, or use something like one so I didnt need to use the static functions.
I would add the methods to the Array's prototype, like this:
Array.prototype.leaseIndex = function () {
for (var i = 0; i < this.length; i++) {
if(typeof this[i] === "undefined") {
return i;
}
}
return this.length;
};
Array.prototype.releaseIndex = function (index) {
delete this[index];
};
So, your code would look like this:
example = [];
a = example.leaseIndex(); // returns 0
example[a] = "example value";
b = example.leaseIndex(); // returns 1
example[b] = "another value";
example.releaseIndex(0);
c = example.leaseIndex(); // returns 0
example[c] = "yet another value";
I hope it helps.
Here is a simple implementation using some static functions (without needing to fuss with methods):
var hop = function(obj, prop){
return Object.prototype.hasOwnProperty.call(obj, prop);
};
var leaseIndex = function(arr, value){
var i;
for(i=0; i<arr.length; i++){
if(!hop(arr, i)){
break;
}
}
arr[i] = value;
return i;
};
var releaseIndex = function(arr, i){
delete arr[i];
};
Of course, I have no idea if this is what you really want, since my algorithm is potentially O(N) and I am not sure you need all this complication.