How to remove both instances of duplicated objects in an array - javascript

I have an array of objects. I am trying to find duplicated objects and then remove both instances of that object.
Right now I am using this method:
function checkForDups(data){
for(var i = 0; i < data.length; i++){
for(var j = i+1; j < data.length; j++){
if(data[j].number === data[i].number){
data.splice(j,1);
data.splice(i,1);
}
}
}
return data;
}
I believe problem is that it only checks for duplicates that have an index that greater than the current position it is checking. This means objects that are "behind" it in the array are not checked for duplication. Currently I run the array through this function a few times to get the desired results. However, this is obviously extremely inefficient. How could I achieve my desired results more efficiently?

I believe problem is that it only checks for duplicates that have an index that greater than the current position it is checking. This means objects that are "behind" it in the array are not checked for duplication.
No, that's a simply optimisation, made possible by the symmetry of the equality relation. By searching ahead and removing all duplicates in front of you, any current item can't be a duplicate of a previous one or it would have been already eliminated.
However, there are some things you did not take care of:
When splicing (removing) an item from the array, all subsequent ones are moved, and the array changes its length. To really examine all items in the array, you need decrease (or: not increase) the counter variable when removing, so that the new item which is now in the same spot as the removed one gets visited (the spot from which you just removed needs to get revisited).
You probably want to break the inner loop after having found a duplicate, otherwise you compare and remove totally different items.
You have not made clear what the algorithm should do when there are more than 2 duplicate items of the same sort in the array. Leave one when their number is odd? Thanks for your comment.
To remove all existing duplicates, you will need to continue the search, but must not remove the ith element immediately or you won't have anything to compare to furtheron - or you might even remove the ith item multiple times (see #2).
So this modification should fit:
function removeAllDups(data) {
// leaves only items in the array that appeared a single time
// removes everything whose .number can be found multiple times
for (var i = 0; i < data.length; i++) {
var found = false,
num = data[i].number;
for (var j = i+1; j < data.length; j++) {
if (data[j].number === num) {
found = true;
data.splice(j--, 1);
}
}
if (found) {
data.splice(i--, 1);
}
}
return data;
}

Here's an alternate implementation. This uses only two passes through the array, and removes all dupes in cases > 2: http://jsfiddle.net/nrabinowitz/1pdr780j/
function removeDupes(arr, test) {
test = test || function(a, b) { return a === b; };
function find(cache, element) {
return cache.some(test.bind(null, element));
}
var seen = [];
var dupes = [];
var len = arr.length;
var x;
var current;
// First pass - find dupes
for (x = 0; x < len; x++) {
current = arr[x];
if (find(seen, current)) {
dupes.push(current);
} else {
seen.push(current);
}
}
// Second pass: remove dupes. Reverse iteration saves headaches here
for (x = len - 1; x >= 0; x--) {
current = arr[x];
if (find(dupes, current)) {
arr.splice(x, 1);
}
}
}
This is an updated version that takes an optional test function to determine equality for dupe purposes; for the OP's case, the call would be
removeDupes(arr, function(a, b) {
return a.number == b.number;
});
Note that this assumes support for ES5 methods - Array#some, Function#bind. If you need to support older browsers, an ES5 shim or the Underscore library would fit the bill.

You could use underscore.js:
function checkForDups(data) {
return (
_.map(
_.filter(
_.pairs(
_.countBy(data)
), function(v) {return v[1] == 1}
), function(v) {return ~~v[0]}
)
)
}
Example
>> console.log(checkForDups([0,1,2,3,0,1,0,4]))
[2, 3, 4]
You can try it here.

Related

Function take in array of string return only strings with that have number(s) in them. Or return empty array if none

The function has 1 parameter: an array. It should iterate through the array and return strings that contain a number in them. If none of them have an number it will return an empty array.
The code I have written so far seems too verbose. Sometimes it does not return the correct value. I am looking for any ways to shorten the code or improve it.
function numInStr(arr) {
var stringsWithNum = [];
for(var x = 0; x < arr.length - 1; x++) {
for (var y = 0; y < arr[x].length - 1;y++) {
//console.log(typeof arr[x][y]);
if('0123456789'.indexOf(arr[x][y]) !== -1) {
stringsWithNum.push(arr[x]);
break;
}
}
}
return stringsWithNum;
}
You can shorten your code considerably by using .filter() to filter out the elements containing numbers, and using .match() to test whether the elements contain a number.
This eliminates the need to create and maintain (and possibly incorrectly set) array indexes.
function numInStr(arr) {
return arr.filter(function (elmt) {
return elmt.match(/\d/)
})
}
console.log(numInStr(['foo', 'ab12', '34asdf', 'bar']))
// Array [ "ab12", "34asdf" ]

Unusual characters when defining where to replace / alter string

I have to see if the array given (arr) is in ascending order. (only positive integers given)
I created an array for something to compare it against. Then looped through the input arr to see if each iteration matches. Or at least that was my goal.
Have also tried a counter variable which adds one to the count only every time sorted[i] == arr[i] is true. Then if the count is the same as sorted.length it is true. However, the fact this didn't work made me think I have made a more fundamental error somewhere.
function inAscOrder(arr) {
let sorted = arr.sort((a, b) => a - b);
for (let i = 0; i < arr.length; i++) {
if (arr[i] === sorted[i]) {
return true;
} else {
return false;
}
}
}
sort modifies array in place, so you're comparing two identical arrays. You need to use .slice() on the array first to create a copy.
Also, you need to move return true to the end of the function, otherwise you will return after first match.
function inAscOrder(arr) {
return JSON.stringify(arr) === JSON.stringify(arr.concat().sort()));
}
You don't need to sort your array to check if it's sorted. Loop over each consecutive pair of elements and check if the first is less than the second; if you find a pair for which this isn't true, the array is not sorted.
function inAscOrder(arr) {
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i+1]) {
return false;
}
}
return true;
}

Recursion to find n numbers in a range that add up to a target value

I've written a function that finds all sets of two numbers that sum a target value, given a range of numbers from 0 to x. I'm trying to rewrite it in a way so that you can get a result of a given length n (not only 2), but n numbers that will equal a target when added together.
For example, if the range is 1 to 5 and you want to find all sets of three numbers that add up to 7, the answer would be:
[[1,1,5], [1,2,4], [1,3,3], [2,2,3]]
It looks like the solution is to use a recursive function, but I can't quite figure out how to do this. I've looked at several subset-sum examples on StackOverflow, but none seem to match this particular scenario.
This is not a homework problem. Any help would be appreciated. Here is my findPairs function:
function findPairs(arr, target) {
var pairs = [];
var first = 0;
var last = arr.length-1;
while (first <= last) {
var sum = arr[first] + arr[last];
if (sum === target) {
pairs.push([arr[first], arr[last]]);
first ++;
last--;
}
else if (sum < target) {
first++;
}
else {
last--;
}
}
return pairs;
}
var sample = _.range(11);
console.log(JSON.stringify(findPairs(sample,12)));
// Returns
// [[2,10],[3,9],[4,8],[5,7],[6,6]]
This example uses the lodash _.range function. Fiddle here: https://jsfiddle.net/tbarmann/muoms1vL/10/
You could indeed use recursion. Define the third argument as the length of the sub-arrays you are looking for, and define a recursion function inside that function as follows:
function findSums(arr, target, count) {
var result = [];
function recurse(start, leftOver, selection) {
if (leftOver < 0) return; // failure
if (leftOver === 0 && selection.length == count) {
result.push(selection); // add solution
return;
}
for (var i = start; i < arr.length; i++) {
recurse(i, leftOver-arr[i], selection.concat(arr[i]));
}
}
recurse(0, target, []);
return result;
}
// Demo
var result = findSums([1,2,3,4,5], 7, 3);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Remarks
The solution does not require that the input array has consecutive numbers (range): you might pass it [3,6,4,2,1]. The sub-arrays that are returned will keep the selected elements in order, for example: [3,3,3], [3,4,2] and [6,2,1] could be solutions for targetting 9 with 3 values for that example input.
The numbers must all be non-negative. If negative values are to be allowed, then the optimisation if (leftOver < 0) return; must be removed from the code.
Well, what you are probably looking for is Dynamic Programming. It is an approach where you define mathematically your problem and a recursive solution. Then you try to find a solution, using memoization.
I would make a helper function, where the arguments are (range, target, lengthOfResult). Then do something like:
func helper(arr, target, lengthOfResult){
if(taget == 0) continue;
for(int i=1; i<arr.length-1; i++){
if(taget - arr[i] < 0) return [];
var sub_results = helper(arr, taget - arr[i], lengthOfResult - 1)
foreach(var res in sub_results){
result.concat([arr[i]] + res);
}
}
return result;
}
So you are changing the question for the helper function to "Give me all lists of length-1 which sums up to taget-arr[i]", append to that arr[i]. Then use that to construct the result, for each arr[i].
Basically, every iteration the length will decrease, so the function will terminate at some point. You subtract from the taget whatever number you have now. So you're end result will add up to the desired target.
Note that this algorithm works only with positive numbers. If you can allow negatives, you should remove the if inside the first for-loop.
About the memoization, if you want to allow using each number only once, you could get away with a single array. If you allow reoccurring numbers in the result (like in your example), you probably need a grid; horizontally the target, vertically the lengthOfResult. Then in the beginning of each invocation of the helper method, you check if you already calculated that value. This will save you some recursive calls and make the algorithm not exponential.
function sumPermutations(target, range, number) {
var result = [];
function combo(left, group, sum) {
if(sum > target) return null;
if (left == 0) {
if(sum == target) return group;
return null;
}
for (var i = range.min; i <= range.max; i++) {
var r = combo(left - 1, group.concat(i), sum + i);
if (r)
result.push(r);
}
}
combo(number, [], 0);
return result;
}
console.log(sumPermutations(7, {min: 1, max: 5}, 3));
Note: This gives results with duplicates (all permutaions including those with different orders). You can remove duplicates by sorting the arrays and join thier items and hash them into a hash object.

Using 2 arrays of objects, iterate over them using something similar to IndexOf (or other option)

I have an array of objects, i was trying to iterate over. The array is pretty simple in format.
{a:5, b:"key", c:19}
i was trying to compare an array with a subset, say: [{a:5},...]
for (var i = 0; i < subset.length; i++) {
var searchTerm = subset[i].a;
var index = objs.indexOf(searchTerm, function (el) {
return el.a;
});
if (index > -1) {
objs[index].Found = true;
}
}
So that way ultimately objs, could have a new key in it, 'Found'
This way, it will set the main array objs item.Found = true, if it existed in subset.
There are 2 issues though. Accounting for multiple instances of the same item, and the fact that this current implementation doesnt seem to work.
This is a slight expansion of (indexOf method in an object array? )but with an array of search terms.
ideally, i dont want to change the arrays at all, so im trying not to slice, etc.
In a lot of the defintions, indexOf is defined as:
function indexOf (key, start);
instead of the ideas i am trying to work with.
Edit
Here is some code that i have to get this working, but i was thinking there is a more effecient way to do it than written.
for (var j = 0; j < compare.length; j++){
var searchTerm = compare[j]["a"];
for (var k = 0; k < objs.length; k++){
if (!objs[k].Found && objs[k]["a"] == searchTerm){
objs[k].Found = true;
break;
}
}
}

Delete from array in javascript

3 hours ago, I asked a question in SO , about deleting a part of an object, so I linked this question to it:
delete a part of object in javascript
but now another problem occurred when I deleted from that array.
I use that object to populate a FlexiGrid. but when I delete an item from that object by following code, instead of delete that item , it sets to undefined :( and flexigrid did not accept it for input data.
for (var i = 0; i < Roomdata.length; i++) {
if(Roomdata[i].id = X) {
delete Roomdata[i];
break;
}
}
For example, imagine I have 3 items in Roomdata like this :
{item1, item2, item3}
When I call this code to delete item2 , Roomdata object looks like this :
{item1, undefined, item3}
and this is a bad format to be accepted by flexigrid as input data
Is there any solution ?
Thanks every body and sorry about my bad syntax (I am new in English)
regards , Foroughi
Walk through the array in reverse order, and use .splice to remove the element.
You have to walk in the reverse order, because otherwise you end up skipping elements See below.
for (var i = Roomdata.length-1; i >= 0; i--) {
if (Roomdata[i].id == X) {
Roomdata.splice(i, 1);
break;
}
}
What happens if you don't walk in the reverse order:
// This happens in a for(;;) loop:
// Variable init:
var array = [1, 2, 3];
var i = 0;
array.splice(i, 1); // array = [2, 3] array.length = 2
// i < 2, so continue
i++; // i = 1
array.splice(i, 1); // i=1, so removes item at place 1: array = [2]
// i < 1 is false, so stop.
// array = [2]. You have skipped one element.
What you have is an Array. You should use the splice() method to remove an element from an array, not by deleteing the element.
for (var i = 0; i < Roomdata.length; i++) {
if(Roomdata[i].id = X) {
Roomdata.splice(i, 1);
break;
}
}
Using splice in spite of delete.
Roomdata.splice(i, 0);
splice attribute removes blank string elements, undefined references, NULLs and FALSEs.
it will solve your problem
To remove all of the elements of an array matching a particular value, you can do this:
// Remove all elements in arr[] matching val
for (let i = 0; i < arr.length; i++) {
if (arr[i] === val) {
arr.splice(i--, 1); // Remove arr[i] and adjust the loop index
}
}
Note that this is a forward scan of the array. The decrement of the loop index is necessary so that the loop does not skip the next element after an element is removed.

Categories

Resources