Question has been moved to CodeReview: https://codereview.stackexchange.com/questions/154804/find-a-list-of-objects-in-an-array-with-javascript
Having an array of objects - such as numbers - what would be the most optimal (Memory and CPU efficiency) way if finding a sub group of objects? As an example:
demoArray = [1,2,3,4,5,6,7]
Finding [3,4,5] would return 2, while looking for 60 would return -1.
The function must allow for wrapping, so finding [6,7,1,2] would return 5
I have a current working solution, but I'd like to know if it could be optimized in any way.
var arr = [
1,
5,2,6,8,2,
3,4,3,10,9,
1,5,7,10,3,
5,6,2,3,8,
9,1]
var idx = -1
var group = []
var groupSize = 0
function findIndexOfGroup(g){
group = g
groupSize = g.length
var beginIndex = -2
while(beginIndex === -2){
beginIndex = get()
}
return beginIndex
}
function get(){
idx = arr.indexOf(group[0], idx+1);
if(idx === -1 || groupSize === 1){
return idx;
}
var prevIdx = idx
for(var i = 1; i < groupSize; i++){
idx++
if(arr[getIdx(idx)] !== group[i]){
idx = prevIdx
break
}
if(i === groupSize - 1){
return idx - groupSize + 1
}
}
return -2
}
function getIdx(idx){
if(idx >= arr.length){
return idx - arr.length
}
return idx
}
console.log(findIndexOfGroup([4,3,10])) // Normal
console.log(findIndexOfGroup([9,1,1,5])) // Wrapping
You could use the reminder operator % for keeping the index in the range of the array with a check for each element of the search array with Array#every.
function find(search, array) {
var index = array.indexOf(search[0]);
while (index !== -1) {
if (search.every(function (a, i) { return a === array[(index + i) % array.length]; })) {
return index;
}
index = array.indexOf(search[0], index + 1);
}
return -1;
}
console.log(find([3, 4, 5], [1, 2, 3, 4, 5, 6, 7])); // 2
console.log(find([6, 7, 1, 2], [1, 2, 3, 4, 5, 6, 7])); // 5
console.log(find([60], [1, 2, 3, 4, 5, 6, 7])); // -1
console.log(find([3, 4, 5], [1, 2, 3, 4, 6, 7, 3, 4, 5, 9])); // 6
.as-console-wrapper { max-height: 100% !important; top: 0; }
My take on the problem is to use slice() and compare each subarray of length equal to the group's length to the actual group array. Might take a bit long, but the code is short enough:
// The array to run the tests on
var arr = [
1,
5, 2, 6, 8, 2,
3, 4, 3, 10, 9,
1, 5, 7, 10, 3,
5, 6, 2, 3, 8,
9, 1
];
// Check arrays for equality, provided that both arrays are of the same length
function arraysEqual(array1, array2) {
for (var i = array1.length; i--;) {
if (array1[i] !== array2[i])
return false;
}
return true;
}
// Returns the first index of a subarray matching the given group of objects
function findIndexOfGroup(array, group) {
// Get the length of both arrays
var arrayLength = array.length;
var groupLength = group.length;
// Extend array to check for wrapping
array = array.concat(array);
var i = 0;
// Loop, slice, test, return if found
while (i < arrayLength) {
if (arraysEqual(array.slice(i, i + groupLength), group))
return i;
i++;
}
// No index found
return -1;
}
// Tests
console.log(findIndexOfGroup(arr,[4,3,10])); // Normal
console.log(findIndexOfGroup(arr,[9,1,1,5])); // Wrapping
console.log(findIndexOfGroup(arr,[9,2,1,5])); // Not found
If the group is longer than the array, some errors might occur, but I leave it up to you to extend the method to deal with such situations.
Related
How to find the difference between the min and max indexes of the same value in an array with one loop with complexity exactly O(N)?
For example, given array A:
[4, 6, 2, 2, 6, 6, 1];
the function returns 4.
I'd use reduce to remember the first index of each value, then update the last value and maximum spread as I went along, e.g.
var data = [4, 6, 2, 2, 6, 6, 1];
function getMaxIndexSpread(data) {
return data.reduce(function(acc, value, index) {
if (value in acc) {
acc[value].lastIndex = index
} else {
acc[value] = {firstIndex: index, lastIndex: index};
}
var spread = acc[value].lastIndex - acc[value].firstIndex;
if (acc.maxSpread < spread) acc.maxSpread = spread;
return acc;
}, {maxSpread: 0}).maxSpread;
}
console.log(getMaxIndexSpread(data));
There's likely a funkier way, but this makes sense to me.
var data = [4, 6, 2, 2, 6, 6, 1];
console.log(Math.max(...data.map((v,i) => i - data.indexOf(v))));
var arr = [4, 6, 2, 2, 6, 6, 1];
function test(arr) {
var resultArr = [];
arr.map(function (v, i, a) {
for (var j = arr.length - 1; j >= 0; j--) {
if (v == arr[j]) {
resultArr.push({value: v, result: j - i});
// console.log(v+'的折扣值'+(j-i));
break;
}
}
})
resultArr.sort(function (a, b) {
return b.result - a.result;
})
console.log(resultArr[0])
}
test(arr);
Try with Array#filter .filter the array without max and min value .Then find max value in filtered array .... its spread syntax
var data = [4, 6, 2, 2, 6, 6, 1];
function bet(data) {
return Math.max(...data.filter(a => a != Math.max(...data) && a != Math.min(...data)))
}
console.log(bet(data))
My problem is I can't console.log the repeating after another same numbers in the array. I can find which number how many times appears with this code:
var array = [2, 1, 1, 2, 3, 3, 2, 2, 2, 1],
currentNum = 1,
counter = 0,
item;
for (var i = 0; i < array.length; i++) {
for (var j = i; j < array.length; j++) {
if (array[i] == array[j])
counter++;
if (currentNum < counter) {
currentNum = counter;
item = array[i];
}
}
counter = 0;
}
console.log(item + " ( " + currentNum + " times ) ");
But my real issue is that I want to log the 2, 2, 2 part and I don't know how.
Thank you guys very much in advance!
Following will find the first instance of longest (assuming more than one same length sequence)
The first reduce() creates subarrays for each sequence, the second reduce() checks lengths of those sub arrays returning the first instance of longest sequence
var array = [2, 1, 1, 2, 3, 3, 2, 2, 2, 1];
var res = array.reduce(function(a,c,i){
if(a.length && a[a.length-1][0] ===c){
a[a.length-1].push(c)
}else{
a.push([c]);
}
return a
},[]).reduce(function(a,c){
return c.length > a.length ? c : a;
});
console.log('Sequence length=',res.length);
console.log('Value=', res[0]);
If you expect multiple same length sequences and want to capture all the values that match can modify second reduce something like:
var array = [2, 1, 1, 2, 3, 3, 2, 2, 2, 1, 2, 2, 2, 7, 7, 7];
var res = array.reduce(function(a,c,i){
if(a.length && a[a.length-1][0] ===c){
a[a.length-1].push(c)
}else{
a.push([c]);
}
return a
},[]).reduce(function(a,c){
if(c.length > a.len){
// reset when longer sequence found
a.values = [c[0]];
a.len = c.length;
} else if(c.length === a.len){
// add new value when same length found
a.values.push(c[0]);
}
return a
},{len:0,values:[]});
console.log('Sequence length=', res.len)
console.log('How many times=', res.values.length)
console.log('Values = ', res.values.join() )
Array.prototype.forEach or reduce is better for iteration through an array.
Here is a solution with a single going through array (just 3 additional variables :) )
var array = [2, 1, 1, 2, 3, 3, 2, 2, 2, 1]
var item = array[0], maxTimes = 1, currentTimes = 1
array.reduce(function(prev, current) {
if (prev === current) {
currentTimes = currentTimes + 1
if (currentTimes > maxTimes) {
maxTimes = currentTimes
item = current
}
} else {
currentTimes = 1
}
return current
})
console.log(item, maxTimes)
You could use Array#reduce with an object which holds the temporary result and the actual count of the item.
If an item is equal to the last item, last count is incremented and checked if the count is greater than the temporary count. Then the temporary count is replaced by the actual count.
var array = [2, 1, 1, 2, 3, 3, 2, 2, 2, 1],
count = array.reduce(function (r, a, i, aa) {
if (a === aa[i - 1]) {
r.last.count++;
if (!(r.last.count <= r.result.count)) {
r.result = r.last;
}
} else {
r.last = { value: a, count: 1 };
}
return r;
}, { last: {}, result: {} }).result;
console.log(count);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I need to delete occurrences of an element if it occurs more than n times.
For example, there is this array:
[20,37,20,21]
And the output should be:
[20,37,21]
I thought one way of solving this could be with the splice method
First I sort the array it order to make it like this:
[20,20,37,21]
Then I check if the current element is not equal to the next and split the array into chunks, so it should look like:
[20, 20],[37],[21]
Later I can edit the chunk longer than 1 and join it all again.
This is what the code looks like in my head but didn't work in real life
var array = [20, 37, 20, 21];
var chunk = [];
for(i = 0; i < array.length; i++) {
if(array[i] !== array[i + 1]) {
var index = array.indexOf(array[i]);
chunk.push = array.splice(0, index) // cut from zero to last duplicate element
} else
var index2 = a.indexOf(a[i]);
chunk.push(a.splice(0, index));
}
with this code the output is
[[], [20, 20]]
I think It's something in the 'else' but can't figure it out what to fix.
As the logic you want to achieve is to delete n occurrences of element in an array, your code could be as follow:
var array = [1, 1, 3, 3, 7, 2, 2, 2, 2];
var n = 2;
var removeMultipleOccurences = function(array, n) {
var filteredArray = [];
var counts = {};
for(var i = 0; i < array.length; i++) {
var x = array[i];
counts[x] = counts[x] ? counts[x] + 1 : 1;
if (counts[x] <= n) filteredArray.push(array[i])
}
return filteredArray;
}
console.log(removeMultipleOccurences(array, n));
I came up with this one, based on array filter checking repeated values up to a limit, but I can see #Basim's function does the same.
function removeDuplicatesAbove(arr, max) {
if (max > arr.length) {max = arr.length;}
if (!max) {return arr;}
return arr.filter(function (v, i) {
var under = true, base = -1;
for (var n = 0; n < max; n++) {
base = arr.indexOf(v, base+1); if (base == -1) {break;}
}
if (base != -1 && base < i) {under = false;}
return under;
});
}
var exampleArray = [20, 37, 20, 20, 20, 37, 22, 37, 20, 21, 37];
console.log(removeDuplicatesAbove(exampleArray, 3)); // [20, 37, 20, 20, 37, 22, 37, 21]
Always when you use splice() you truncate the array. Truncate the array with the length of same values from the start with the help of lastIndexOf(). It always starts from 0.
[ 1, 1, 1, 2, 2, 2, 3, 4, 4, 5 ] // splice(0, 3)
[ 2, 2, 2, 3, 4, 4, 5 ] // splice(0, 3)
[ 3, 4, 4, 5 ] // splice(0, 1)
[ 4, 4, 5 ] // splice(0, 2)
[ 5 ] // splice(0, 1)
Do this as long as the array length is greater than 0.
var arr = [1, 1, 1, 2, 2, 2, 3, 4, 4, 5];
var res = [];
while (arr.length > 0) {
var n = arr[0];
var last = arr.lastIndexOf(n) + 1;
res.push(n);
arr.splice(0, last);
}
console.log(res);
You can use Array.prototype.reduce(), Array.prototype.filter() to check if n previous elements are the same as current element
let cull = (arr, n) => arr.reduce((res, curr) => [...res
, res.filter(v => v === curr).length === n
? !1 : curr].filter(Boolean), []);
let arrays = [[20,37,20,21], [1,1,3,3,7,2,2,2,2]];
let cullone = cull(arrays[0], 1);
let cullthree = cull(arrays[1], 3);
console.log(cullone // [20, 37, 21]
, cullthree // [1, 1, 3, 3, 7, 2, 2, 2]
);
I'm not sure if the title of this question is correct or not and also not sure what the appropriate keyword to search on google.
I have an array look like:
var myArray = [1,1,2,2,2,3,4,4,4];
and I want to sort my array into:
var myArray = [1,2,3,4,1,2,4,2,4];
Please in to my expected result. the order is ascending but duplicate value will repeated on last sequence instead of put it together in adjacent keys. So the expected result grouped as 1,2,3,4 1,2,4 and 2,4.
Thank you for your help and sorry for my bad English.
This code works. But it may exist a better solution.
// We assume myArray is already sorted
var myArray = [1,1,2,2,2,3,4,4,4],
result = [];
while (myArray.length) {
var value = myArray.shift();
// Find place
var index = 0;
while(result[index] && result[index][result[index].length - 1] == value) index++;
if(!result[index]) {
result[index] = [];
}
result[index][result[index].length] = value;
}
result.reduce(function(current, sum) {
return current.concat(sum);
});
console.log(result) // Display [1, 2, 3, 4, 1, 2, 4, 2, 4]
Here is my method using JQuery and it does not assume the array is already sorted.
It will iterate through the array and no duplicates to tempResultArray, once finished, it will then add them to the existing result and repeat the process again to find duplicates.
This is not the most efficient method, but it can be handled by one function and does not require the array to be sorted.
var myArray = [1,1,2,2,2,3,4,4,4],result = [];
while (myArray && myArray.length) {
myArray = customSort(myArray);
}
console.log(result);
function customSort(myArray){
var tempResultArray = [], tempMyArray = [];
$.each(myArray, function(i, el){
if($.inArray(el, tempResultArray ) === -1){
tempResultArray.push(el);
}else{
tempMyArray.push(el);
}
});
tempResultArray.sort(function(a, b){return a-b});
$.merge( result,tempResultArray)
return tempMyArray;
}
JSFiddle
This proposal features a straight forward approach with focus on array methods.
function sprout(array) {
return array.reduce(function (r, a) {
!r.some(function (b) {
if (b[b.length - 1] < a) {
b.push(a);
return true;
}
}) && r.push([a]);
return r;
}, []).reduce(function (r, a) {
return r.concat(a);
});
}
document.write('<pre>' + JSON.stringify(sprout([1, 1, 2, 2, 2, 3, 4, 4, 4]), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(sprout([1, 2, 3, 7, 7, 7]), 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(sprout([1, 1, 2, 3, 3, 4, 5, 5, 6, 6, 6, 6, 6, 7]), 0, 4) + '</pre>');
here's another solution:
var myArray = [1, 1, 2, 2, 2, 3, 4, 4, 4];
function mySequelArray(arr) {
var res = arguments[1] || [];
var nextVal;
var min = Math.min.apply(null, arr);
if (res.length > 0) {
nextVal = arr.filter(function (x) {
return x > res[res.length - 1]
}).sort()[0] || min;
} else {
nextVal = min;
}
res.push(nextVal);
arr.splice(arr.indexOf(nextVal), 1);
return (arr.length > 0) ? mySequelArray(arr, res) : res;
}
console.log(mySequelArray(myArray))
fiddle
My best approach will be to split your array into separate arrays for each repeated value, then arrange each separate array and join altogether.
UPDATED:
I wrote a quick code sample that should work if the same number in inputArray is not given more than twice. You could improve it by making it recursive thus creating new arrays for each new number and removing the limitation. Had some free time so i re-wrote a recursive function to sort any given array in sequence groups like you wanted. Works like a charm, inputArray does not need to be sorted and doesn't require any libraries. Jsfiddle here.
var inputArray = [3, 4, 1, 2, 3, 1, 2, 4, 1, 2, 5, 1];
var result = sortInSequence(inputArray);
console.log(result); //output: [1, 2, 3, 4, 5, 1, 2, 3, 4, 1, 2, 1]
function sortInSequence(inputArray){
var inputArraySize = inputArray.length,
tempArray = [], //holds new array we are populating
sameValuesArray = [], //holds same values that we will pass as param in recursive call
rSorted = []; //init sorted array in case we have no same values
for(var i = inputArraySize; i > 0; i--){
var value = inputArray.pop();
tempArray.push(value);
var counter = 0,
tempArraySize = tempArray.length;
for(var j = 0; j < tempArraySize; j++){
if(tempArray[j] == value){
counter++;
}
}
if(counter == 2){
//value found twice, so remove it from tempArray and add it in sameValuesArray
var sameValue = tempArray.pop();
sameValuesArray.push(sameValue);
}
}
if(sameValuesArray.length > 0){
rSorted = sortInSequence(sameValuesArray);
}
tempArray.sort();
return tempArray.concat(rSorted);
}
I have an array here:
var myArr = [1, 1, 2, 5, 5, 7, 8, 9, 9];
Now I want to remove both appearances of a duplicate. So the desired result is not:
var myArr = [1, 2, 5, 7, 8 ,9];
but
var myArr = [2, 7, 8];
Basically I know how to remove duplicates, but not in that that special way. Thats why any help would be really appreciated!
Please note: My array is filled with strings. The numbers here were only used as an example.
jsfiddle for this code:
var myArr = [1, 1, 2, 5, 5, 7, 8, 9, 9];
var newArr = myArr;
var h,i,j;
for(h = 0; h < myArr.length; h++) {
var curItem = myArr[h];
var foundCount = 0;
// search array for item
for(i = 0; i < myArr.length; i++) {
if (myArr[i] == myArr[h])
foundCount++;
}
if(foundCount > 1) {
// remove repeated item from new array
for(j = 0; j < newArr.length; j++) {
if(newArr[j] == curItem) {
newArr.splice(j, 1);
j--;
}
}
}
}
Here's my version
var a = [1, 1, 2, 5, 5, 7, 8, 9, 9];
function removeIfduplicate( arr ) {
var discarded = [];
var good = [];
var test;
while( test = arr.pop() ) {
if( arr.indexOf( test ) > -1 ) {
discarded.push( test );
continue;
} else if( discarded.indexOf( test ) == -1 ) {
good.push( test );
}
}
return good.reverse();
}
x = removeIfduplicate( a );
console.log( x ); //[2, 7, 8]
EDITED with better answer:
var myArr = [1, 1, 2, 5, 5, 7, 8, 9, 9];
function removeDuplicates(arr) {
var i, tmp;
for(i=0; i<arr.length; i++) {
tmp = arr.lastIndexOf(arr[i]);
if(tmp === i) {
//Only one of this number
} else {
//More than one
arr.splice(tmp, 1);
arr.splice(i, 1);
}
}
}
Using Hashmap
create hashmap and count occurencies
filter where hashmap.get(value) === 1 (only unique values)
const myArray = [1, 1, 2, 5, 5, 7, 8, 9, 9];
const map = new Map();
myArray.forEach(v => map.set(v, map.has(v) ? map.get(v)+1 : 1));
myArray.filter(v => map.get(v) === 1);
Old version (slower but valid too)
Heres a short version using Array.filter(). The trick is to first find all values that are NOT uniqe, and then use this array to reject all unique items in the original array.
let myArr = [1, 1, 2, 5, 5, 7, 8, 9, 9];
let duplicateValues = myArr.filter((item, indx, s) => s.indexOf(item) !== indx);
myArr.filter(item => !duplicateValues.includes(item));
// => [2, 7, 8]
Wherever removing duplicates is involved, it's not a bad idea to use a set data structure.
JavaScript doesn't have a native set implementation, but the keys of an object work just as well - and in this case help because then the values can be used to keep track of how often an item appeared in the array:
function removeDuplicates(arr) {
var counts = arr.reduce(function(counts, item) {
counts[item] = (counts[item] || 0) + 1;
return counts;
}, {});
return Object.keys(counts).reduce(function(arr, item) {
if (counts[item] === 1) {
arr.push(item);
}
return arr;
}, []);
}
var myArr = [1, 1, 2, 5, 5, 7, 8, 9, 9];
console.log(removeDuplicates(myArr), myArr);
Check out the example on jsfiddle.
Alternately, you could not use calls to reduce(), and instead use for and for(item in counts) loops:
function removeDuplicates(arr) {
var counts = {};
for(var i=0; i<arr.length; i++) {
var item = arr[i];
counts[item] = (counts[item]||0)+1;
}
var arr = [];
for(item in counts) {
if(counts[item] === 1) {
arr.push(item);
}
}
return arr;
}
Check out the example on jsfiddle.
If it's just alphanumeric, duplicates are case-sensitive, and there can be no more than two of any element, then something like this can work:
var a = [2, 1, "a", 3, 2, "A", "b", 5, 6, 6, "B", "a"],
clean_array = $.map(a.sort(), function (v,i) {
a[i] === a[i+1] && (a[i] = a[i+1] = null);
return a[i];
});
// clean_array = [1,3,5,"A","B","b"]
In this example,we are taking two arrays as function arguments, from this we are going to print only unique values of both arrays hence deleting the values that are present in both arrays.
first i am concatenating both the arrays into one. Then I taking each array value at a time and looping over the array itself searching for its no of occurrence. if no of occurrence(i.e.,count) equal to 1 then we are pushing that element into the result array. Then we can return the result array.
function diffArray(arr1, arr2) {
var newArr = [];
var myArr=arr1.concat(arr2);
var count=0;
for(i=0;i<myArr.length;i++){
for(j=0;j<myArr.length;j++){
if(myArr[j]==myArr[i]){
count++;
}
}
if(count==1){
newArr.push(myArr[i]);
}
count=0;
}
return newArr;
}
EDIT: Here is the jspref http://jsperf.com/deleting-both-values-from-array
http://jsfiddle.net/3u7FK/1/
This is the fastest way to do it in two passes without using any fancy tricks and keeping it flexible. You first spin through and find the count of every occurance and put it into and keyvalue pair. Then spin through it again and filter out the ones where the count was greater than 1. This also has the advanatage of being able to apply other filters than just "greater than 1"; as well as the having the count of occurances if you needed that as well for something else.
This should work with strings as well instead of numbers.
http://jsfiddle.net/mvBY4/1/
var myArr = [1, 1, 2, 5, 5, 7, 8, 9, 9];
var map = new Object();
for(var i = 0; i < myArr.length; i++)
{
if(map[myArr[i]] === undefined)
{
map[myArr[i]] = 1;
}
else
{
map[myArr[i]]++;
}
}
var result = new Array();
for(var i = 0; i < myArr.length; i++)
{
if(map[myArr[i]] > 1)
{
//do nothing
}
else
{
result.push(myArr[i]);
}
}
alert(result);
You can use Set (available in IE 11+) as below
const sourceArray = [1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8];
const duplicatesRemoved = new Set();
sourceArray.forEach(element => {
if (duplicatesRemoved.has(element)) {
duplicatesRemoved.delete(element)
} else {
duplicatesRemoved.add(element)
}
})
console.log(Array.from(duplicatesRemoved))
N.B. Arrow functions are not supported in older browsers. Use normal function syntax for that instead. However, Array.from can easily be polyfilled for older browsers.
Try it here.