Goal: given an array of mixed types determine the number of elements at each level. If there are two sub-arrays at the same level, each of their elements count towards to the total number of elements at that level.
Approach:
Array.prototype.elementsAtLevels = function( level, levelData ) {
if ( level == undefined ) { level = 0; } else { level += 1 }
if ( levelData == undefined ) { levelData = {}; }
if ( levelData[level] == undefined ) { levelData[level] = this.length} else { levelData[level] += this.length }
this.map(function(e, i) {if (Array.isArray(e)){ e.elementsAtLevels(level, levelData) }})
return levelData
}
Test case:
[
1, // 0: 1
1, // 0: 2
1, // 0: 3
1, // 0: 4
[ // 0: 5
2, // 1: 1
2, // 1: 2
2 // 1: 3
],
[ // 0: 6
[ // 1: 4
3, // 2: 1
3 // 2: 2
],
[ // 1: 5
[ // 2: 3
4 // 3: 1
]
]
]
].elementsAtLevels()
// Object [ 6, 5, 3, 1 ]
Question:
Is there a more efficient way to calculate this?
I wrote something very similar to what you have, and in a very rudimentary benchmark, it ran in a little under half the time.
let a = [1,1,1,1,[2,2,2],[[3,3],[[4]]]];
Array.prototype.elementsAtLevels2 = function (level, lData) {
if (!level || !lData) {
level = 0;
lData = {};
}
if (!(level in lData)) {
lData[level] = this.length;
} else {
lData[level] += this.length;
}
this.forEach(function (v) {
if (Array.isArray(v))
v.elementsAtLevels2(level + 1, lData);
});
return lData;
}
console.log(a.elementsAtLevels2());
I'm guessing the main performance increase might be from the forEach vs map, map creates a new array, where forEach does not.
Edit
Here it is in JSBin
Here is my take on it. It resembles yours but it doesn't change the prototype and it uses an array instead of an object for return.
function arrayCounter(arr, level, levelData) {
if (level === void 0) {
level = 0;
}
if (levelData === void 0) {
levelData = [];
}
//Set default value for level
if (levelData[level] === void 0) {
levelData[level] = 0;
}
//Count length
levelData[level] += arr.length;
//Loop through list items
for (var i = 0; i < arr.length; i++) {
var value = arr[i];
//If array, perform a subcount
if (Array.isArray(value)) {
levelData = arrayCounter(value, level + 1, levelData);
}
}
return levelData;
}
//TEST
var data = [1, 1, 1, 1, [2, 2, 2],
[
[3, 3],
[
[4]
]
]
];
console.log(arrayCounter(data));
This thing performs about the same as yours, but at least it gives correct results:
function elementsAtLevel( array, result = [], level = 0 ){
result[level] = (result[level] || 0) + array.length
level++
for( const el of array ){
if( Array.isArray(el) )
elementsAtLevel(el, result, level)
}
return result
}
console.log( elementsAtLevel([1,1,1,1,[2,2,2],[[3,3],[[4]]]]) )
By correct I mean consistent: you counted subarrays as elements on the first level, but not any other.
Here's a prototype version:
Array.prototype.elementsAtLevel = function( result = [], level = 0 ){
result[level] = (result[level] || 0) + this.length
level++
for( const el of this ){
if( Array.isArray(el) )
el.elementsAtLevel(result, level)
}
return result
}
console.log( [1,1,1,1,[2,2,2],[[3,3],[[4]]]].elementsAtLevel() )
this recursive function should do the work
let arr = [1,1,1,1,[2,2,2],[[3,3],[[4]]]];
Array.prototype.elementsAtLevels = function(){
return this.reduce((acc, el, index, currentArray) => {
if(Array.isArray(el)){
return acc.concat(el.elementsAtLevels());
}
return [currentArray.length];
}, [])
}
console.log(arr.elementsAtLevels());
Related
I am trying to find 3 or more matching items in array but it is only matching the first 3 and none matching for the rest of the array. If anyone could help would be great :)
var grid = [2,2,2,5,5,5,3,3,3,3];
checkResults();
function checkResults(){
var list_matches = []; // store all matches found
var listcurrent = []; // store current
var maxitems = 3;
var last = -1; // last cell
for(let j =0; j < grid.length; ++j){
let item = grid[j];
// check if last is null
if(last == -1){
// add first item
listcurrent.push(item);
last = item;
console.log("Added: "+item);
continue;
}
let wasMatch = false;
// check match
if(item == last){
wasMatch = true;
listcurrent.push(item);
last = item;
console.log("Added Match: "+item);
}
if(!wasMatch){
console.log("Not matched: " + item);
if(listcurrent.length >= maxitems){
list_matches.push(listcurrent);
}
// reset to null
last = -1;
listcurrent = [];
}
}
console.log(list_matches);
console.log("Cols: " + grid.length);
}
Expected Results: from [2,2,2,5,5,5,3,3,3,3];
0: 222
1: 555
2: 3333
Current output is:
0: 222 and thats it
You could take a temporary array for collecting the same values and push this array if the length has the wanted minimum length.
function getMore(array, min) {
var result = [],
temp;
array.forEach((v, i, a) => {
if (v !== a[i - 1]) return temp = [v];
temp.push(v);
if (temp.length === min) result.push(temp);
});
return result;
}
console.log(getMore([2, 2, 2, 5, 5, 5, 3, 3, 3, 3], 3));
you can do something like this:
var grid = [ 1, 1, 2, 3, 4, 5 ];
var hashMap = {};
for( var i = 0; i < grid.length; i++ ) {
if( hashMap.hasOwnProperty( grid[i] ) ) {
hashMap[ grid[i] ]++;
} else {
hashMap[ grid[i] ] = 1;
}
}
it will helps you.
//toLowerCase for get unique on words, not on character. if i ignore this, it will return two words=> developers. and developers
//first Split and join for remove '.' character form text and finally last split is for convert string to an Array of words that splited by Space character
let uniqWords = Array.from(new Set(text));
//using Set to get unique words and convert it to Array to do more on Array.
let count = {};
// declare varriable for store counts of words.
uniqWords.map(item => {
count[item] = text.filter(elem => {
//create property with the name of words and filter common words to an Array
return elem == item
}).length
//get Array length for get words repeated count.
})
console.table(count)
//log Result into Console
Another solution using Array.prototype[reduce/map/filter]
const someArray = [2, 2, 2, 5, 5, 5, 3, 3, 3, 3, 9, 9];
console.log(aggregate(someArray));
function aggregate(arr) {
return arr
// retrieve unique values
.reduce((acc, val) => !acc.includes(val) && acc.concat(val) || acc, [])
// use unique values to map arr values to strings
// if number of matches >= 3
.map(val => {
const filtered = arr.filter(v => v == val);
return filtered.length > 2 ? filtered.join("") : false
})
// filter non falsy values
.filter(val => val);
}
How can I determine the of an array (in this case, 'duplicate' or 'unique') to make it return either the duplicated or unique number. The array arr contains positive integers. It may be one of the following:
There are numbers 1 to n, only one number is duplicate (repeated two times), the other numbers are unique.
There are numbers 1 to n, only one number is unique, the other numbers are repeated two times.
I have attached my current code below, but since it's way too slow; I am wondering if it is possible to solve the problem in another way.
function duplicateOrUnique(arr) {
var duplicate = 0,
output = 0,
n = 0,
num = {}
arr.forEach(function(item) { // Inserts every item in 'arr' to list 'num'
num[item] = 0;
})
arr.forEach(function(item) { // Applies value (count) to every item list 'num'
num[item] += 1;
})
arr.forEach(function(item) { // Check how many times an item have been duplicated
if (num[item] > 1) {
duplicate += 1;
}
})
// Detertime wether 'arr' includes duplicated or unique numbers
if (duplicate > 2) { // unique
arr.forEach(function(item) {
if (num[item] == 1) {
output = item;
}
})
} else { // duplicated
arr.forEach(function(item) {
if (num[item] >= 2) {
output = item;
}
})
}
return output;
}
Note
All numbers are positive integers that from 1 to n.
The length of the array will always be more than 5.
Examples
[1,2,3,6,5,4,1] should return 1
[1,2,3,1,2,3,4] should return 4
[3,6,9,2,5,8,1,4,8,7] should return 8
[9,8,7,1,2,3,9,7,1,2,3,4,4,5,5,6,6] should return 8
If ES6 is not a problem, you can use a couple of sets to see how many duplicates are found (pre-ES6, a hash object could be used):
function duplicateOrUnique(arr) {
const set= new Set(), dupes= new Set();
for(let i of arr)
(set.has(i) ? dupes : set).add(i); //if set contains i -> add to dupes
if(dupes.size === 1) //one duplicate
return [...dupes][0]; //return first entry of duplicates
return [...set].find(i => !dupes.has(i)); //one unique-> filter out all dupes from the set
}
console.log(duplicateOrUnique([1,2,3,6,5,4,1]));
console.log(duplicateOrUnique([1,2,3,1,2,3,4]));
You can do it in the following way
function getVal(arr){
arr.sort();
let counUniq = 0, countDup = 0;
let uniq = -1, dup = -1;
for(let i=1; i<arr.length; i++){
if(arr[i] == arr[i-1]){
countDup++;
dup = arr[i];
}
else if(arr[i] != arr[i+1]) {
counUniq++;
uniq = arr[i];
}
}
//console.log(counUniq, countDup, uniq, dup);
if(counUniq == 1){
return uniq;
}
else {
return dup;
}
}
console.log(getVal([1,2,3,6,5,4,1]))
console.log(getVal([1,2,3,1,2,3,4]))
console.log(getVal([3,6,9,2,5,8,1,4,8,7]))
console.log(getVal([9,8,7,1,2,3,9,7,1,2,3,4,4,5,5,6,6]))
it uses sorting, and just checks which array type is it
Solution
function duplicateOrUnique(array) {
const unique = [...new Set(array)]
const lookup = unique.map(function (value) {
return this.indexOf(value) === this.lastIndexOf(value)
}, array)
return unique[
lookup.indexOf(
lookup.reduce((a, b) => a + b, 0) === 1
)
]
}
console.log(
duplicateOrUnique([1,2,3,6,5,4,1])
) // should return 1
console.log(
duplicateOrUnique([1,2,3,1,2,3,4])
) // should return 4
console.log(
duplicateOrUnique([3,6,9,2,5,8,1,4,8,7])
) // should return 8
console.log(
duplicateOrUnique([9,8,7,1,2,3,9,7,1,2,3,4,4,5,5,6,6])
) // should return 8
Explanation
const unique = [...new Set(array)]
Generates an array containing every unique value.
const lookup = unique.map(function (value) {
return this.indexOf(value) === this.lastIndexOf(value)
}, array)
Creates a lookup table to determine if each value is unique in the original array (true if unique, false if duplicate), by testing whether its first index is equivalent to its last index.
lookup.reduce((a, b) => a + b, 0) === 1
Determines which type of array by summing the lookup table of booleans. If only one value in the array is unique, then the result is true, otherwise false.
return unique[lookup.indexOf(...)]
Performs a reverse-lookup of the number to return by getting the index of the desired type (unique or duplicate) based on the type of array.
You could just check the set where the value belongs and then render the result for only one item.
function fn(array) {
var one = new Set,
two = new Set;
array.forEach(a => two.has(a) || one.has(a) && (one.delete(a), two.add(a)) || one.add(a));
return (one.size === 1 ? one : two).values().next().value;
}
console.log(fn([1, 2, 3, 6, 5, 4, 1])); // 1
console.log(fn([1, 2, 3, 1, 2, 3, 4])); // 4
console.log(fn([3, 6, 9, 2, 5, 8, 1, 4, 8, 7])); // 8
console.log(fn([9, 8, 7, 1, 2, 3, 9, 7, 1, 2, 3, 4, 4, 5, 5, 6, 6])); // 8
.as-console-wrapper { max-height: 100% !important; top: 0; }
A readable but not very performant solution:
var arr1 = [1,2,3,6,5,4,1];
var arr2 = [1,2,3,1,2,3,4];
var duplicatedNumber = arr1.find((it, itIndex) => arr1.some((some, someIndex) => it === some && itIndex !== someIndex));
var uniqueNumber = arr2.find((it, itIndex) => !arr2.some((other, otherIndex) => it === other && itIndex !== otherIndex));
console.log("Duplicated: ", duplicatedNumber);
console.log("Unique: ", uniqueNumber);
// To make a function that returns either the duplicated or unique number in an array:
function findUniqueOrDuplicated(arr) {
return arr.find((it, itIndex) => arr.some((other, othersIndex) => it === other && itIndex !== othersIndex)) ||
arr.find((it, itIndex) => !arr.some((other, othersIndex) => it === other && itIndex !== othersIndex));
}
I have one nested array for example
var arr = [
[0,1,2,3,4],
[0,1,2,3],
[0,1,2,3,4],
[0,1]
];
How to remove N items from end or from beginning using lodash?
For example if I remove 6 elements from beginning, I want result to be:
var arr = [
[1,2,3],
[0,1,2,3,4],
[0,1]
];
and if I remove 1 from end, I need result to be:
var arr = [
[0,1,2,3,4],
[0,1,2,3],
[0,1,2,3,4],
[0]
];
I hope i was clear. Lodash is not necessary.
This is my code:
function removeFromTop(group, count) {
for (var i = 0; i < group.length; i++) {
for (var x = 0; x < group[i].chatItems.length; x++) {
if(count) {
group[i].chatItems.splice(x, 1);
if(!group[i].chatItems.length) {
group.splice(i, 1);
};
count--;
} else {
break;
}
};
};
return group;
}
function removeFromBottom(group, count) {
for (var i = group.length - 1; i >= 0; i--) {
for (var x = group[i].chatItems.length - 1; x >= 0; x--) {
if(count) {
group[i].chatItems.splice(x, 1);
if(!group[i].chatItems.length) {
group.splice(i, 1);
};
count--;
} else {
break;
}
};
};
return group;
}
You could shift the inner array for each item count from the beginning and pop the values from the end. For the first you could use Array#reduce and for the other Array#reduceRight
function removeFromStart(array, n) {
var copy = JSON.parse(JSON.stringify(array));
return copy.reduce(function (r, a) {
while (n && a.length) {
a.shift();
n--;
}
a.length && r.push(a);
return r;
}, []);
}
function removeFromEnd(array, n) {
var copy = JSON.parse(JSON.stringify(array));
return copy.reduceRight(function (r, a) {
while (n && a.length) {
a.pop();
n--;
}
a.length && r.push(a);
return r;
}, []).reverse();
}
var array = [[0, 1, 2, 3, 4], [0, 1, 2, 3], [0, 1, 2, 3, 4], [0, 1]];
console.log(JSON.stringify(removeFromStart(array, 6)));
console.log(JSON.stringify(removeFromEnd(array, 6)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
using Lodash function .drop you can drop very first element(s) of an array or else can specify n element(s) default value is 1. same way .dropRight for the end element(s).
var arr = [
[0,1,2,3,4],
[0,1,2,3],
[0,1,2,3,4],
[0,1]
];
// remove 1 element front of 2D Array
var resultFront= arr.map(function(value,index) { return _.drop(value); });
console.log(resultFront);
// remove 1 element from End of 2D Array
var resultEnd= arr.map(function(value,index) { return _.dropRight(value); });
console.log(resultEnd);
// remove 1 element front and end of 2D Array
var resultFrontEnd = arr.map(function(value,index) { return _.dropRight(_.drop(value)); });
console.log(resultFrontEnd);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
var arr = [
[0,1,2,3,4],
[0,1,2,3],
[0,1,2,3,4],
[0,1]
];
console.log(arr);
console.log('------------------------');
// remove 1 element front of 2D Array
var resultFront= arr.map(function(value,index) { return _.drop(value); });
console.log('Remove 1 element from front') ;
console.log(resultFront);
// remove 1 element from End of 2D Array
var resultEnd= arr.map(function(value,index) { return _.dropRight(value); });
console.log('Remove 1 element from end') ;
console.log(resultEnd);
// remove 1 element front and end of 2D Array
var resultFrontEnd = arr.map(function(value,index) { return _.dropRight(_.drop(value)); });
console.log('Remove 1 element from front & End') ;
console.log(resultFrontEnd);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
You can simply do as follows;
var arr = [[0,1,2,3,4],[0,1,2,3],[0,1,2,3,4],[0,1]],
r = [],
n = 6,
res = arr.reduce((r,sa) => r.n > sa.length ? (r.n -= sa.length, r)
: (r.push(sa.slice(r.n)), r.n = 0, r), (r.n = n, r));
console.log(res);
I use a state variable r.n within the initial array in the reduce operation. You may or may not chose to delete it afterwards.
Implemented the merge sort algorithm in my javascript code.
I'm wonder how I can target specific attributes like date, title, name etc for sorting in an array when calling merge sort like mergeSort(array);.
function mergeSort(arr){
var len = arr.length;
if(len <2)
return arr;
var mid = Math.floor(len/2),
left = arr.slice(0,mid),
right =arr.slice(mid);
return merge(mergeSort(left),mergeSort(right));
}
function merge(left, right){
var result = [],
lLen = left.length,
rLen = right.length,
l = 0,
r = 0;
while(l < lLen && r < rLen){
if(left[l] < right[r]){
result.push(left[l++]);
}
else{
result.push(right[r++]);
}
}
return result.concat(left.slice(l)).concat(right.slice(r));
}
Using it in a sort options method. What I want is to print a sorted list. The way the list is sorted will be defined by the users chosen sort option.
function sortConfig(array, sortOption){
if(sortOption == 'title') mergeSort(array.Title);
//..etc
}
To implement the behavior with an optional argument, you could do it in the following way:
function mergeSort(arr, compare = (item => item))
This would set compare function to be the item itself when running the merge
and then we update the calling of the merge and mergeSort itself, where they now all get the compare argument
return merge(mergeSort(left, compare), mergeSort(right, compare), compare);
and ofcourse the declaration for your merge function itself
function merge(left, right, compare)
Which then calls the compare function upon comparison, like here:
if (compare(left[l]) < compare(right[r]))
This lets you choose wether you wish to give an argument or not wen you call your mergeSort function, like:
console.log(mergeSort(nrs).join(','));
console.log(mergeSort(nrs, n => -n).join(','));
console.log(mergeSort(arr, i => i.id));
console.log(mergeSort(arr, i => i.title));
function mergeSort(arr, compare = (item => item)) {
var len = arr.length;
if (len < 2)
return arr;
var mid = Math.floor(len / 2),
left = arr.slice(0, mid),
right = arr.slice(mid);
return merge(mergeSort(left, compare), mergeSort(right, compare), compare);
}
function merge(left, right, compare) {
var result = [],
lLen = left.length,
rLen = right.length,
l = 0,
r = 0;
while (l < lLen && r < rLen) {
if (compare(left[l]) < compare(right[r])) {
result.push(left[l++]);
} else {
result.push(right[r++]);
}
}
return result.concat(left.slice(l)).concat(right.slice(r));
}
var arr = [{
title: 'test 5',
id: 4
}, {
title: 'test',
id: 0
}, {
title: 'test 3',
id: 2
}, {
title: 'test 4',
id: 3
}];
var nrs = [5, 3, 7, 156, 15, 6, 17, 9];
// and call like
console.log(mergeSort(nrs).join(','));
console.log(mergeSort(nrs, n => -n).join(','));
// or like
console.log(mergeSort(arr, i => i.id));
console.log(mergeSort(arr, i => i.title));
For the sake of brevity, these examples show how to sort an array of objects based on a property with a string value. You would most likely need to create some additional logic to handle different types of properties.
1. Array.sort()
You can do this with the Array.sort() method
Fiddle Example
myThings = [
{ alpha: 'a' },
{ alpha: 'x' },
{ alpha: 'p' },
{ alpha: 'orange' },
{ alpha: 'c' },
{ alpha: 'w' }
];
myThings.sort(function(a, b) {
var alphaA = a.alpha.toUpperCase();
var alphaB = b.alpha.toUpperCase();
if (alphaA < alphaB) return -1;
if (alphaA > alphaB) return 1;
return 0;
});
console.log(myThings);
2. Or, compare array item property value instead of array item value
Fiddle Example
function mergeSort(arr, prop) {
if (arr.length < 2)
return arr;
var middle = parseInt(arr.length / 2);
var left = arr.slice(0, middle);
var right = arr.slice(middle, arr.length);
return merge(mergeSort(left, prop), mergeSort(right, prop), prop);
}
function merge(left, right, prop) {
var result = [];
while (left.length && right.length) {
if (left[0][prop] <= right[0][prop]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
myThings = [
{ alpha: 'a' },
{ alpha: 'x' },
{ alpha: 'p' },
{ alpha: 'orange' },
{ alpha: 'c' },
{ alpha: 'w' }
];
console.log(mergeSort(myThings, 'alpha'));
I populate a 2d array with a while loop, but I need to stop the push when the 1st column contains 3 different unique value.
the start is something like this
var maxunique;
var i = 0;
while (countunique(arr) != maxunique) {
// my code that push data into array
arr[i].push(RandomNumber(1,8));
arr[i].push(i+1);
i++;
}
function countunique(arr)
{
// function here
}
function RandomNumber(min,max)
{
return Math.floor(Math.random()*(max-min+1)+min);
}
This return that value
arr: [ [ 4, 1 ],
[ 7, 2 ],
[ 5, 3 ],
[ 5, 4 ],
[ 3, 5 ],
[ 1, 6 ],
[ 7, 7 ],
[ 8, 8 ],
[ 5, 9 ],
[ 5, 10 ] ]
Whell the idea about the expected result is
arr: [ [ 4, 1 ],
[ 7, 2 ],
[ 5, 3 ] ]
you can see that the push is interrupted after the first 5, that is the 3 unique value in array
I don't know how to do it, and I don't know if is better do to with a while or a for loop.
Any Idea?
In the loop where you are populating the 2D array, have the array sent to a check function that determines if 3 unique elements are present as the first elements in the individual array. Here is the code for check function, it returns true if 3 unique elements are not yet present, once it finds 3 unique elements it returns false.
var a = []; //;
var i = 0;
while (check(a)) {
a[i]=[];
a[i].push(RandomNumber(1, 42));
a[i].push(i + 1);
i++;
}
function RandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function check(arr) {
var length = arr.length;
var tmp = [];
for (var j = 0; j < length; j++) {
if (tmp.indexOf(arr[j][0]) === -1) {
tmp.push(arr[j][0]);
}
if (tmp.length === 3) {
return false;
}
}
return true;
}
console.log('test: ', a);
console.log('check: ', check(a));
You could use one object to store each first number as property and check in each iteration of while loop if any value in that object is equal 3. If it is you can break from loop. I used random number for first number.
var ar = [];
var obj = {}
while (true) {
var firstNum = parseInt(Math.random() * 5) + 1;
ar.push([firstNum, 1]);
obj[firstNum] = (obj[firstNum] || 0) + 1;
var stop = Object.keys(obj).find(function(e) {
return obj[e] == 3;
});
if (stop) break;
}
console.log(obj);
console.log(ar);
You could implement a function which stores the number of the first column as a property in a object and increments a counter if that number does not exist in that object.If the counter is equal to 3 then there are 3 unique elements,then stop the array push and break out of the loop.
Here is an example code.
var arr = ['-15, 1',
'-15, 2' ,
'2,3' ,
'2, 4' ,
'2, 5',
'77, 6','22,3' ];
function unique(array) {
var arr = [],x,y,obj = {},count = 0,arr1;
for (i = 0; i < array.length ; i++) {
arr1 = array[i].split(',');
x = arr1[0] * 1;
y = arr1[1] * 1;
if (count === 3) {
break;
}
array.push([x,y]);
if (!obj[x]) {
obj[x] = 1;
count++;
}
}
}
This implements a real push:
var yourarray={
arr:[],
push:function(obj){
for(a=0;a<this.obj.length){
let count=0;
for(i=0;i<this.arr.length;i++){
if(obj[a]!=this.arr[i][a]){
count++;
if(count==3){
break;
}
}
}
}
this.arr.push(obj);
};
};
Now you can do:
yourarray.push([0,1,2]);
And access it like:
alert(yourarray.arr);
However, accessing yourarray directly isnt possible anymore