Let's say we have a List of ranges expressed in arrays with two elements [from, to].
When we add a new array range like [5,8], it should check in List if there is a closest range and then replace it with the new range value. An example is provided below:
Example 1
var List = [[1,2], [3,4], [6,7], [9,10]]
var newData = [5,8]
Expected Output:
[[1,2], [3,4], [5,8], [9,10]]
The [6,7] range is already included in [5,8]
Example 2
var List = [[1,3], [4,6], [8,10]]
var newData = [5,9]
Expected Output:
[[1,3], [4,10]]
Assuming the initial list is well-formed, with its pairs sorted and non-overlapping, you could use binary search to find the end points of a new pair in the array and so determine any overlap. If overlap, splice the array accordingly:
function addSegments(segments, ...pairs) {
for (let pair of pairs) {
let [start, end] = pair.map(function (x, i) { // Binary search
let low = 0,
high = segments.length;
side = 1 - i;
while (low < high) {
let mid = (low + high) >> 1;
if (x < segments[mid][side]) high = mid;
else low = mid + 1;
}
return low - (side && segments[low-1]?.[side] === x);
});
if (start < end) {
pair = [
Math.min(segments[start][0], pair[0]),
Math.max(segments[end-1][1], pair[1])
];
}
segments.splice(start, end - start, pair);
}
}
// Demo
let list = [[1, 2], [3, 4], [6, 7], [9, 10]];
addSegments(list, [5, 8]);
console.log(JSON.stringify(list));
list = [[1, 3], [4, 6], [8, 10]];
addSegments(list, [5, 9]);
console.log(JSON.stringify(list));
If we have two arrays containing all the integers included in the ranges, then we can intersect and see if they overlap. If they do, we create a new range from the union of the two ranges, and use that in the output instead.
So to that end, we define three helper functions, range(), intersect() and union(), and then we use those to generate the output array. If the intersection exists, we overwrite any overlapped range with the new union of the two. I assumed that if two ranges just touched they weren't meant to be combined, so the overwrite is only triggered if the intersection contains more than one element. Also, I added an initial sort.
function add(list, data) {
let buffer = [], idx;
const range=(a,b)=>Array.from({length:b-a+1}, (_, i)=>(a+i));
const intersect=(a,b)=>a.filter(x=>b.includes(x));
const union=(a,b)=>[...new Set([...a, ...b])].sort((a,b)=>a-b);
list.sort((a,b)=>a[0]-b[0]);
list.forEach(el=>{
let x = range(el[0], el[1]);
let y = range(data[0], data[1]);
let i = intersect(x, y);
if(i.length>1) {
let d = union(x,y);
data = [d[0], d[d.length-1]];
if(idx) { buffer[idx] = data; }
else { idx = buffer.push(data)-1; }
}
else { buffer.push(el); };
});
return buffer;
}
// DEMO
let List = [[1,2], [3,4], [6,7], [9,10]];
let newData = [5,8];
console.log(JSON.stringify(List));
console.log(JSON.stringify(newData));
console.log(JSON.stringify(add(List, newData)));
console.log('');
List = [[1,3], [4,6], [8,10]];
newData = [5,9];
console.log(JSON.stringify(List));
console.log(JSON.stringify(newData));
console.log(JSON.stringify(add(List, newData)));
console.log('');
// DEMO WITH UNORDERED ELEMENTS
List = [[3,4], [9,10], [6,7], [1,2]];
newData = [5,8];
console.log(JSON.stringify(List));
console.log(JSON.stringify(newData));
console.log(JSON.stringify(add(List, newData)));
console.log('');
Related
I'm learning Javascript and I'm wondering what the most elegant way to convert this: [1,8]
into this:[1,2,3,4,5,6,7,8]?
Thanks a lot!
const argarray = [1, 8]
const countToN = (array) => {
// init results array
let res = []
// start at the first value array[0] go *up to* the second array[1]
for (let i = array[0]; i <= array[1]; i++) {
res.push(i)
}
// return the result
return res
}
console.log(countToN([1, 10]))
This would accommodate what you're trying to do, but it's fairly brittle. You'd have to check that it's an array and that it has only 2 values. If you had other requirements, I could amend this to account for it.
Here's a solution without loops. Note that this only works with positive numbers. It supports arrays of any length, but will always base the result off of the first and last values.
const case1 = [1, 8];
const case2 = [5, 20];
const startToEnd = (array) => {
const last = array[array.length - 1];
const newArray = [...Array(last + 1).keys()];
return newArray.slice(array[0], last + 1);
};
console.log(startToEnd(case1));
console.log(startToEnd(case2));
Here's a solution that works for negative values as well:
const case1 = [-5, 30];
const case2 = [-20, -10];
const case3 = [9, 14];
const startToEndSolid = (array) => {
const length = array[array.length - 1] - array[0] + 1;
if (length < 0) throw new Error('Last value must be greater than the first value.');
return Array.from(Array(length)).map((_, i) => array[0] + i);
};
console.log(startToEndSolid(case1));
console.log(startToEndSolid(case2));
console.log(startToEndSolid(case3));
A simple for loop will do it. Here's an example that has error checking and allows you to range both backwards and forwards (ie [1, 8], and also [1, -8]).
function range(arr) {
// Check if the argument (if there is one) is
// an array, and if it's an array it has a length of
// of two. If not return an error message.
if (!Array.isArray(arr) || arr.length !== 2) {
return 'Not possible';
}
// Deconstruct the first and last elements
// from the array
const [ first, last ] = arr;
// Create a new array to capture the range
const out = [];
// If the last integer is greater than the first
// integer walk the loop forwards
if (last > first) {
for (let i = first; i <= last; i++) {
out.push(i);
}
// Otherwise walk the loop backwards
} else {
for (let i = first; i >= last; i--) {
out.push(i);
}
}
// Finally return the array
return out;
}
console.log(range([1, 8]));
console.log(range('18'));
console.log(range());
console.log(range([1]));
console.log(range([-3, 6]));
console.log(range([9, 16, 23]));
console.log(range([4, -4]));
console.log(range([1, -8, 12]));
console.log(range(null));
console.log(range(undefined));
console.log(range([4, 4]));
Additional documentation
Destructuring assignment
Use Array#map as follows:
const input = [1,8],
output = [...Array(input[1] - input[0] + 1)]
.map((_,i) => input[0] + i);
console.log( output );
I am trying to create a little project where I can count the number of elements in an array. This part I have already done. My question is how can I fix a counting problem? I'm using a mapping method to get my element occurrence counter, but I want to put the data into an array. The only way I know how is to take the data from the mapping with .get. They way I'm doing it is by using this first part here:
let winners = [1, 2, 3, 2];
let nullArray = [];
let mode = new Map([...new Set(winners)].map(
k => [k, winners.filter(t => t === k).length]
));
for (let n in winners) {
nullArray.push(mode.get(winners[n]));
console.log(nullArray);
}
However, this will *n push matches. Like, if you have l=[1,2,2], because l[1]=2, and l[2]=2, they will be pushed into the nullArray as 2, however, it will also push l[2] and l[1] as the values are different. If you have three matches, it would duplicate 3 times, and so on. To counter this, I tried making a detector that would calculate when the same numbers in the nullArray are from the same copy but in a different order. To do this, I used the code I have below (combined with the original code)
let winners = [1, 2, 3, 2];
let nullArray = [];
let mode = new Map([...new Set(winners)].map(
k => [k, winners.filter(t => t === k).length]
));
for (let n in winners) {
nullArray.push(mode.get(winners[n]));
console.log(nullArray);
}
for (let num in nullArray) {
for (let c in nullArray) {
if (nullArray[num] === nullArray[c] && winners[num] === winners[c]) {
nullArray.splice(num, 1);
}
console.log(nullArray);
}
}
However, whenever I try this, the specific output on this array is [2,2]. How could I make a general solution that will eliminate all duplicate copies, only leaving a single copy of the number count (which in the case of [1,2,3,2], I would want nullArray=[1,2,1] as an output)
You can do something like this.
If you don't care about the order, you can just do the following.
const remove_dup_and_count = (winners = [1, 2, 3, 2]) =>{
let map = {}
//count elements
for (let n in winners) {
const curr_val = winners[n]
//duplicate, so we increment count
if(curr_val in map) map[curr_val] += 1
//first seen, so we start at 1
else map[curr_val] = 1
}
//lets grab the array of all keys in map
const keys_arr = Object.keys(map)
let count_arr = []
for(let i of keys_arr){
count_arr.push(map[i])
}
return count_arr
}
console.log(remove_dup_and_count())
If you care about the order, this is your best bet:
const remove_dup_and_count = (winners = [1, 2, 3, 2]) =>{
let map = new Map()
//count elements
for (let n in winners) {
const curr_val = winners[n]
//duplicate, so we increment count
if(map.get(curr_val)) map.set(curr_val, map.get(curr_val) + 1)
//first seen, so we start at 1
else map.set(curr_val,1)
}
let count_arr = []
//lets grab the array of all keys in map
for (const [key, value] of map) {
count_arr.push(value)
}
return count_arr
}
console.log(remove_dup_and_count())
I think you can use .reduce() method and then retrieve how many times the value is repeated in array using map.values(); something like the following snippet:
const winners = [1, 2, 3, 2];
const mapWinners = winners.reduce((winnersAccumulator, singleWinner) => winnersAccumulator.set(singleWinner, (winnersAccumulator.get(singleWinner) || 0) + 1), new Map())
console.log([...mapWinners.values()])
/*1 2 3
2 1 3
2 3 1 */
var arr = [
[1,2,3],
[2,1,3],
[2,3,1]
]
function commonElementInColumn(arr){
var a = arr[0],
b=false,
commItem =[];
for(var i=0 ; i<a.length ;i ++){
var item = a[i];
for(j=1; j< arr.length ; j++){
if(arr[j].indexOf(item) !==-1){
b =true
}else {
b= false
}
}
if(b){
commItem.push(item)
}
}
return commItem
}
console.log(commonElementInColumn(arr))
I am trying to find common column item in matrix .I tried like this. But not getting expected output .I am getting [1,2,3] but
can we add any logic which find common element in columns
Expected output is [1]
Let take I have m x n matrix .I want to find common element which is present in all coloum.
See input
/*1 2 3
2 1 3
2 3 1 */
1 is present in all three column.
2 is present only first and second not in third
3 is present on second and third but not in first
Try following
var arr = [
[1,2,3], [2,1,3], [2,3,1]
];
// Create the map of items with value to be an array of indexes
var map = {};
var result = [];
// iterate over the array
arr.forEach(function(subArray){
subArray.forEach(function(item, index){
map[item] = map[item] ? map[item] : [];
// if the map has the item and the index already exists in the array, skip
if(!map[item].includes(index)) {
map[item].push(index);
}
// if the length is same as the array item length (assuming same for all subArray's) then it means all indexes have been covered
if(map[item].length === arr[0].length) {
result.push(item);
}
});
});
console.log(result);
A slightly different approach with a Map for the values and a Set for each value for collecting the indices. Later check the size of the sets and take the keys of the map as result arrray.
function commonCol(array) {
var map = new Map;
array.forEach(a => a.forEach((v, i) => {
if (!map.has(v)) {
map.set(v, new Set);
}
map.get(v).add(i);
}));
return Array
.from(map.entries())
.filter(({ 1: v }) => v.size === array[0].length)
.map(([k]) => k);
}
var array = [[1, 2, 3], [2, 1, 3], [2, 3, 1]];
console.log(commonCol(array));
Take a look at that:
var arr = [
[1,2,3],
[2,1,3],
[2,3,1]
]
// Creates another array from arr organized by column
var byColumn = arr.reduce((accumulator, item) => {
item.forEach((element, index) => {
if(!accumulator[index]) accumulator[index] = [];
accumulator[index].push(element);
});
return accumulator;
}, [])
// Gets the first array of arr to iterate
var firstArray = arr[0];
// Gets an array of elements that appears in all columns
var filteredNumbers = firstArray.filter(element => {
return byColumn.every(item => item.includes(element));
});
console.log(filteredNumbers); // [1]
Use Array#filter() on first subarray since it must contain any common values.
Within that filter use a Set to pass all indices for each number to. Since a Set must have unique values you can then check it's size and if it matches subarray length then all indices are unique
var arr = [
[1, 2, 3],
[2, 1, 3],
[2, 3, 1]
]
function commonElementInColumn(arr) {
// return filtered results from first subarray since it has to contain any common values `
return arr[0].filter((num) => {
// get all indices for this number as array
let indices = arr.map(subArr => subArr.indexOf(num));
//create a set from this array which will void duplicates
let uniqueIndices = new Set(indices);
// if size of set is same as subarray length then all indices for this number are unique
return uniqueIndices.size === arr[0].length;
});
}
console.log(commonElementInColumn(arr))
I want to split an Array of numbers into N groups, which must be ordered from larger to smaller groups.
For example, in the below code, split an Array of 12 numbers into 5 Arrays, and the result should be evenly split, from large (group) to small:
source: [1,2,3,4,5,6,7,8,9,10,11,12]
⬇
output: [1,2,3] [4,5,6] [7,8] [9,10] [11,12]
Playground
// set up known variables
var arr = [1,2,3,4,5,6,7,8,9,10,11,12],
numberOfGroups = 5,
groups = [];
// split array into groups of arrays
for(i=0; i<arr.length; i++) {
var groupIdx = Math.floor( i/(arr.length/numberOfGroups) );
// if group array isn't defined, create it
if( !groups[groupIdx] )
groups[groupIdx] = [];
// add arr value to group
groups[groupIdx].push( arr[i] )
}
// Print result
console.log( "data: ", arr );
console.log( "groups: ", groups )
Update:
Thanks to SimpleJ's answer, I could finish my work.
The use case for this is an algorithm which splits HTML lists into "chunked" lists, a think which cannot be easily achieved by using CSS Columns.
Demo page
I'm not 100% sure how this should work on different sized arrays with different group counts, but this works for your 12 digit example:
function chunkArray(arr, chunkCount) {
const chunks = [];
while(arr.length) {
const chunkSize = Math.ceil(arr.length / chunkCount--);
const chunk = arr.slice(0, chunkSize);
chunks.push(chunk);
arr = arr.slice(chunkSize);
}
return chunks;
}
var arr = [1,2,3,4,5,6,7,8,9,10,11,12];
console.log( chunkArray(arr, 5) )
A shorter version of #SimpleJ answer and without using slice two times.
function splitArrayEvenly(array, n) {
array = array.slice();
let result = [];
while (array.length) {
result.push(array.splice(0, Math.ceil(array.length / n--)));
}
return result;
}
console.log(splitArrayEvenly([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 5))
I think this is a more of a mathematical problem than a Javascript.
const getGroups = (arr, noOfGroups) => {
const division = Math.floor(arr.length / numberOfGroups);
const groups = [[]];
let remainder = arr.length % numberOfGroups;
let arrIndex = 0;
for (let i = 0; i < noOfGroups; i++) {
for (let j = division + (!!remainder * 1); j >= 0; j--) {
groups[i].push(arr[arrIndex]);
arrIndex += 1;
}
remainder -= 1;
}
return groups;
};
const myGroups = getGroups([1,2,3,4,5,6,7,8,9,10,11,12], 5);
myGroups will be [[1, 2, 3], [4, 5, 6], [7, 8], [9, 10], [11, 12]]
This will work for any number of groups and players
I want to 'reduce' the array to only max values for each x (or index 0) value in a JavaScript multidimensional array.
My Array is as follows:
var mulitple = [["1/2013", 1],
["1/2013", 5],
["1/2013", 7],
["1/2013", 6],
["1/2013", 5],
["2/2013", 7],
["2/2013", 10],
["2/2013", 10],
["3/2013", 7],
["3/2013", 10],
["3/2013", 10],
["4/2013", 1],
["4/2013", 5],
["4/2013", 7],
["4/2013", 6],
["4/2013", 5],
["5/2013", 7]];
So the final result should be as follows:
[["1/2013", 7],
["2/2013", 10],
["3/2013", 10],
["4/2013", 7],
["5/2013", 7]];
How can I achieve this in JavaScript.
EDIT:
Aww man who voted my question down.
Anyhow, this is what I have come up with.
var max = 0;
var newarray = [];
for (var i = 1; i < mulitple.length; i++) {
if (mulitple[i - 1][0] == mulitple[i][0]) {
if (mulitple[i - 1][1] > max) {
max = mulitple[i - 1][1];
}
}
else {
if (mulitple[i - 1][1] > max) {
max = mulitple[i - 1][1];
}
newarray.push([mulitple[i - 1][0], max]);
max = 0;
}
}
newarray.push([mulitple[mulitple.length - 1][0], max]);
The problem that I am having is that I can't get that last value (for the lone record) to get in the array. This was my result after I ran the code above.
[["1/2013", 7], ["2/2013", 10], ["3/2013", 10], ["4/2013", 7], ["5/2013", 0]]
This assumes that original array is already sorted. If not, you will have to write additional function to sort out.
function findMaximums(data) {
var out = [], maximums = {}, order = new Set;
data.reduce(function(acc, pair) {
if (
// Accumulator has value and it's lower than current
(acc[pair[0]] && acc[pair[0]][1] < pair[1]) ||
// Accumulator doesn't have value
!acc[pair[0]]
) {
acc[pair[0]] = pair; // Store maximum in accumulator
order.add(pair[0]) // Store order in set
}
return acc;
}, maximums);
order.forEach(function(key) {
out.push(maximums[key]); // Populate out with maximums by order
});
return out;
}
findMaximums(multiple);
/*[
[
"1/2013",
7
],
[
"2/2013",
10
],
[
"3/2013",
10
],
[
"4/2013",
7
],
[
"5/2013",
7
]
]*/
Update 1: same, but without Set.
function findMaximums(data) {
var order = [];
var maximums = data.reduce(function(acc, pair) {
if (
// Accumulator has value and it's lower than current
(acc[pair[0]] && acc[pair[0]][2] < pair[1]) ||
// Accumulator doesn't have value
!acc[pair[0]]
) {
// Store maximum
acc[pair[0]] = pair;
// Store order
if (order.indexOf(pair[0]) === -1) {
order.push(pair[0])
}
}
return acc;
}, {});
return order.map(function(key) {
return maximums[key]; // Populate out with maximums by order
});
}
Update 2: Shorter version.
function findMaximums(data) {
return data.filter(function(p1, i1) {
return !data.some(function(p2, i2) {
return p1[0] === p2[0] && ( (p1[1] < p2[1]) || (p1[1] === p2[1] && i1 > i2) );
});
});
}
In this version I let pair to remain in output if there are no other pairs in input data that:
Have same month.
Have bigger value.
or
Have same value, but occur earlier than tested pair. This prevents duplicates.
Read here more about used Array methods: filter, some.
Assuming the array as defined in the original question, which is sorted to have each grouping together...
Completely untested code:
var reduced = [];
var groupName = '';
var groupMax;
var groupIndex;
var l = multiple.length; // Grab the array length only once
for (var i = 0; i < l; i++){
// Current Group Name doesn't match last Group Name
if (multiple[i][0] !== groupName) {
// Last Group Name is not empty (it's not the first Group)
if (groupName !== '') {
// Assume groupIndex has been set and grab the item
reduced.push(multiple[groupIndex]);
}
// Grab the new Group Name and set the initial Max and Index
groupName = multiple[i][0];
groupMax = multiple[i][1];
groupIndex = i;
}
// Current Value is bigger than last captured Group Max
if (multiple[i][1] > groupMax) {
// Grab the new Group Max and the current Index
groupMax = multiple[i][1];
groupIndex = i;
}
}
// Grab the last index, since there's no new group after the last item
reduced.push(multiple[groupIndex]);
There could be some syntax or logic errors. I didn't actually run this code, but I think the concept is correct.
Here's a tested version using a map to collect all the unique values, then the output is sorted by month/year and is independent of the order of the input data. This also works in all browsers (IE6+).
Working demo: http://jsfiddle.net/jfriend00/dk1tc73s/
function findLargest(list) {
var map = {}, output = [], item, key, val, current;
for (var i = 0; i < list.length; i++) {
item = list[i];
key = item[0];
val = item[1];
current = map[key];
if (current) {
// this date is in the map, so make sure to save the largest
// value for this date
if (val > current) {
map[key] = val;
}
} else {
// this date is not yet in the map, so add it
map[key] = val;
}
}
// map contains all the largest values, output it to an array
// the map is not in a guaranteed order
for (var key in map) {
output.push([key, map[key]])
}
// actually parse to numbers in sort so the comparison
// works properly on number strings of different lengths
function parseDate(str) {
var split = str.split("/");
return {m: +split[0], y: +split[1]};
}
// now sort the output
output.sort(function(t1, t2) {
var diffYear, a, b;
a = parseDate(t1[0]);
b = parseDate(t2[0]);
diffYear = a.y - b.y;
if (diffYear !== 0) {
return diffYear;
} else {
return a.m - b.m;
}
});
return output;
}