Sorting 2D array by two numeric items - javascript

I want to sort a 2D array first by column 5, then by column 4. They represent the PTS and GD on a football league table.
var table = [
["teamA", 6, 2, 0, 2, 7],
["teamB", 6, 1, 1, 6, 7],
["teamC", 6, 2, 1, 8, 7]
];
I've adapted the great advice I received on the forum:
Sorting 2D Array by numeric item
I replicated the function so that it first sorts by PTS and then by GD.
console.log(table.sort(comparePTS));
console.log(table.sort(compareGD));
function comparePTS(a, b) {
return b[5] - a[5]
}
function compareGD(a, b) {
return b[4] - a[4]
}
Although this works, it displays the table twice:
Sorted by PTS
[ [ "teamA", 6, 2, 0, 2, 7 ], [ "teamB", 6, 1, 1, 6, 7 ], [ "teamC", 6, 2, 1, 8, 7 ] ]
Sorted by PTS and GD
[ [ "teamC", 6, 2, 1, 8, 7 ], [ "teamB", 6, 1, 1, 6, 7 ], [ "teamA", 6, 2, 0, 2, 7 ] ]
And this seems the most clunky solution. What's the best way to achieve this within a single function? Thanks in advance.

You could chain the order functions until you have a difference for returning this value to the sorting function.
const
PTS = a => a[5],
GD = a => a[4],
ASC = fn => (a, b) => fn(a) - fn(b),
DESC = fn => (a, b) => fn(b) - fn(a),
sortBy = fns => (a, b) => {
var value;
fns.some(fn => value = fn(a, b));
return value;
};
var table = [["teamA", 6, 2, 0, 2, 7], ["teamB", 6, 1, 1, 6, 7], ["teamC", 6, 2, 1, 8, 7]];
table.sort(sortBy([DESC(PTS), DESC(GD)]));
console.log(table);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Split a two-dimensional array of specified length

Split the original array into a two-dimensional array of the specified length
list => source array
columns => columns number
targetList => two-dimensional array
const list = [1,2,3,4,5,6,7,8,9,10]
const columns = 4;
const targetList = [ [1,2,3], [4,5,6], [7,8,9], [10] ];
const columns = 5;
const targetList = [ [1,2], [3,4], [5,6], [7,8], [9,10] ];
const columns = 6;
const targetList = [ [1,2], [3,4], [5,6], [7,8], [9], [10] ];
const list = [1,2,3,4,5,6]
const columns = 4;
const targetList = [ [1,2], [3,4], [5], [6] ];
const list = [1,2,3,4]
const columns = 5;
const targetList = [ [1], [2], [3], [4] ];
You can use Array.prototype.reduce and transform the given list to the desired grid.
Push the a new row to the resultant grid if any of the following conditions meet:
If there are no rows currently.
If the last row is filled i.e. no more columns can be added to the last row, then add a new row with the current item.
If items remaining to be pushed becomes equal to rows remaining to be created.
const transform = (list, rows) =>
list.reduce((t, l, i) => {
if (
!t.length ||
t.at(-1).length >= Math.ceil(list.length / rows) ||
list.length - i === rows - t.length
) {
t.push([]);
}
t.at(-1).push(l);
return t;
}, []);
console.log(transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4));
console.log(transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5));
console.log(transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 6));
console.log(transform([1, 2, 3, 4, 5, 6], 4));
console.log(transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 4));
console.log(transform([1, 2, 3, 4], 5));
Other relevant documentations:
Array.prototype.at
Array.prototype.push

Split array into different size chunks (4, 3, 3, 3, 4, 3, 3, 3, etc)

I have an array like so: [1, 2, 3, 4, 5, 6, 7, 9, 10]. I need to chunk it into different size chunks, yet with a simple pattern of: 4, 3, 3, 3, 4, 3, 3, 3 like so:
[
[ // four
1,
2,
3,
4
],
[ // three (1/3)
5,
6,
7
],
[ // three (2/3)
8,
9,
10
],
[ // three (3/3)
11,
12,
13
],
[ // four
14,
15,
16,
17
],
[ // three (1/3)
18,
19,
20
], // and so on..
]
I have tried with this code I have customized:
const arr; // my array of values
const chuncked = arr.reduce((acc, product, i) => {
if (i % 3) {
return acc;
} else if (!didFourWayReduce) {
didFourWayReduce = true;
fourWayReduces++;
if ((fourWayReduces - 1) % 2) { // only make every second a "4 row"
return [...acc, arr.slice(i, i + 3)];
} else {
return [...acc, arr.slice(i, i + 4)];
}
} else {
didFourWayReduce = false;
return [...acc, arr.slice(i, i + 3)];
}
}, []);
And it works, almost, expect that the first chunk of threes (1/3) have the last element of the chunk with 4. So 1 key is repeated every first chunk of three. Like so:
[
[
1,
2,
3,
4
],
[
4, // this one is repeated, and it shouldn't be
5,
6
]
]
You could take two indices, one for the data array and one for sizes. Then slice the array with a given length and push the chunk to the chunks array.
Proceed until end of data.
var data = Array.from({ length: 26 }, (_, i) => i + 1),
sizes = [4, 3, 3, 3],
i = 0,
j = 0,
chunks = [];
while (i < data.length) chunks.push(data.slice(i, i += sizes[j++ % sizes.length]));
console.log(chunks);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const arr = Array.from({ length: 100 }, (_, i) => i);
const copy = [...arr];
const sizes = [4, 3, 3, 3];
const result = [];
let i = 0;
while (i <= arr.length && copy.length) {
result.push(copy.splice(0, sizes[i % sizes.length]));
i++;
}
console.log(result);
A recursive approach is fairly elegant:
const chunks = (xs, [s, ...ss]) =>
xs.length ? [xs .slice (0, s), ... chunks (xs .slice (s), [...ss, s])] : []
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
const sizes = [4, 3, 3, 3]
console .log (chunks (data, sizes))
.as-console-wrapper { max-height: 100% !important; top: 0; }
By replacing [s, ...ss] with [...ss, s], we pass a cycled version of the sizes array, so that for instance, [4, 3, 3, 3] becomes [3, 3, 3, 4]. This makes it easy to parse step-by-step.
Mod operator to check if it should be 4 or 3. Use two arrays just to make it easier (can be done with one)
const groupIt = arr => arr.reduce(({
group,
out
}, v, i) => {
var max = out.length % 4 === 0 ? 4 : 3
group.push(v)
if (group.length === max || i === arr.length - 1) {
out.push(group)
group = []
}
return {
group,
out
}
}, {
group: [],
out: []
}).out
console.log(groupIt([1, 2, 3, 4, 5, 6, 7, 8]))
var test = (new Array(30)).fill(0).map((x,i) => i + 1)
console.log(groupIt(test))
with just one:
const groupIt = arr => arr.reduce((out, v, i) => {
var max = (out.length - 1) % 4 === 0 ? 4 : 3
out[out.length - 1].push(v)
if (out[out.length - 1].length === max) {
out.push([])
}
return out
}, [[]])
console.log(groupIt([1, 2, 3, 4, 5, 6, 7, 8]))
var test = (new Array(30)).fill(0).map((x, i) => i + 1)
console.log(groupIt(test))
This answer is similar to that of Nina Scholz, but uses a for loop, which I personally find more clear.
const arr = Array.from({length: 100}, (_, i) => i + 1);
const sizes = [4, 3, 3, 3];
const result = [];
for (let i = 0, j = 0; i < arr.length; i += sizes[j], j = (j + 1) % sizes.length) {
result.push(arr.slice(i, i + sizes[j]));
}
console.log(result);

Sum of nested arrays according to their indexes

If I have these corresponding array with 2 nested arrays (this may have 2 or more) inside:
const nums = [
[4, 23, 20, 23, 6, 8, 4, 0], // Each array consists of 8 items
[7, 5, 2, 2, 0, 0, 0, 0]
];
How can I perform addition with them in accordance to their indexes ?
Expected Result:
// 11 is from 4 (array1 - index1) + 7 (array2 - index1)
// and so on.
[11, 28, 22, 25, 6, 8, 4, 0]
What I did was:
// This will work but it will only be applicable for 2 arrays as what if there will be 2 or more making it dynamic
const total = Array.from({ length: 8 }, (_, i) => nums[0][i] + nums[1][i]);
This support n nested arrays
const nums = [
[4, 23, 20, 23, 6, 8, 4, 0],
[7, 5, 2, 2, 0, 0, 0, 0],
[2, 1, 2, 5, 7, 8, 9, 4]
];
const total = nums.reduce((a, b) => a.map((c, i) => c + b[i]));
console.log(total);
One possible solution is to Array.map() each element of the first inner array to the sum of elements in the same column. For get the summatory of elements in the same column we can use Array.reduce() inside the map():
const nums = [
[4, 23, 20, 23, 6, 8, 4, 0],
[7, 5, 2, 2, 0, 0, 0, 0],
[1, 3, 4, 7, 1, 1, 1, 1],
];
let [first, ...rest] = nums;
let res = first.map((e, i) => rest.reduce((sum, x) => sum + x[i], e));
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
You can use nested forEach() loop
const nums = [
[4, 23, 20, 23, 6, 8, 4, 0],
[7, 5, 2, 2, 0, 0, 0, 0]
];
function sum(arr){
let max = Math.max(...arr.map(x => x.length));
let res = Array(max).fill(0)
res.forEach((x,i) => {
nums.forEach(a => {
res[i] = res[i] + (a[i] || 0)
})
})
return res;
}
console.log(sum(nums))
You can use reduce and an inner loop. Some of the things to be careful of are different array lengths & values that are not numbers.
const nums = [
[4, 23, 20, 23, 6, 8, 4, 0], // Each array consists of 8 items
[7, 5, 2, 2, 0, 0, 0, 0]
];
const otherNums = [
[4, 23, 20, 23, 6, 8, 4, 0, 9, 55], // Each array consists of 8 items
[7, 5, 2, 2, 0, 0, 0, 0, "cat", null, 78],
[7, 5, 2, 2, 0, 0, 0, 0, "dog", null, 78],
[7, 5, 2, 2, 0, 0, 0, 0, "elephant", null, 78]
];
const sumArraysByIndex = nums => nums.reduce((sums, array) => {
for (const index in array) {
if (sums[index] === undefined) sums[index] = 0
if (isNaN(array[index])) return sums
sums[index] += array[index]
}
return sums
}, [])
console.log(sumArraysByIndex(nums))
console.log(sumArraysByIndex(otherNums))
Get the minimum length of subarray and then create the array of that length, using index return the addition.
const nums = [
[4, 23, 20, 23, 6, 8, 4, 0],
[7, 5, 2, 2, 0, 0, 0, 0]
];
const [arr1, arr2] = nums;
const min = Math.min(nums[0].length, nums[1].length);
const output = Array.from({length: min}, (_, i) => arr1[i] + arr2[i]);
console.log(output);

JS how to reform this irregular object

I have this object which its keys are guaranteed sorted and will be used for the operation. And each of its value is a 2d array.
var obj = {
"0": [
[0, 1], [0, 3], [0, 4]
],
"1": [
[1, 2], [1, 3]
],
"2": [
[2, 3], [2, 5]
],
"3": [
[3, 4], [3, 6]
],
"5": [
[5, 6]
],
"6": [
[6, 5]
]
}
I am trying to concatenate them and for each of its last value of the array is the next index of the object. So, my expected result is an array like this,
The pattern is, I have to find a way from 0 which is the first index of obj, to the last index which is 6 by using the values in each of it and linking its last array value to the next object. If that makes sense.
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 6]
[0, 1, 2, 5, 6]
[0, 1, 3, 4, 5, 6]
[0, 1, 3, 4]
[0, 1, 3, 6]
[0, 3, 4, 5, 6]
[0, 3, 6]
[0, 4]
This is my code so far, as I don't know how to proceed further..
var result = [];
for (var key in obj) {
var myarr = obj[key];
for (var i = 0; i < myarr.length; i++) {
result.push(myarr[i])
}
}
Any idea or feedback is welcome.
Edit
One of the expected result was [0, 1, 2, 3, 4, 5, 6], here's the step by step explanation.
The obj key starts from 0 and ends in 6, I have to form a way from 0 to 6 with the arrays in its value.
Starts from obj[0], the first array returns [0, 1], save this to res. (res is now [0, 1])
The last value of array in res is 1, now find the next value in obj[1]
obj[1] has two arrays, and ends with 2 or 3.. So it's possible to append with both of them, so it can be [0, 1, 2] or [0, 1, 3]. In this case, get the first one which is [1, 2] and append the last value to res. (res is now [0, 1, 2]).
The last value of array in res is now 2, now find the next value in obj[2].
obj[2] has two arrays, and ends with 3, or 5.. It's possible to append with both of them, so it can be [0, 1, 2, 3] or [0, 1, 2, 5]. In this case, get the first one which is [2, 3] and append the last value to res. (res is now [0, 1, 2, 3])
The last value of array in res is now 3, now find the next value in obj[3].
Repeat step 4 or 6. (res is now [0, 1, 2, 3, 4]).
The last value of array in res is now 4, now find the next value in obj[4].
Repeat step 4 or 6. (res is now [0, 1, 2, 3, 4, 5]).
The last value of array in res is now 5, now find the next value in obj[5].
Now value 6 is found which should be the end of iteration if you look at the step 4. Repeat step 4 or 6. (res is now [0, 1, 2, 3, 4, 5, 6]).
Repeat from step 1, and form another way to do it, with no duplicates of [0, 1, 2, 3, 4, 5 ,6].
This is a proposal, with a single extra output, mentioned below.
[
[0, 1, 2, 3, 4, 5, 6],
[0, 1, 2, 3, 6],
[0, 1, 2, 5, 6],
[0, 1, 3, 4, 5, 6], /* extended from below */
[0, 1, 3, 4], /* original result */
[0, 1, 3, 6],
[0, 3, 4, 5, 6], /* extended from below */
[0, 3, 4], /* extra line, line should not be in result */
[0, 3, 6], /* but follows the same building rule than above */
[0, 4]
]
Basically this solution is building a tree with the given information about linked nodes.
If some nodes are not contiguous, a backtracking is made for the missing links, with the above function for nodes, checkNodes or with iterPath, to walk the actual collected nodes for missing items.
function getParts(value, path, nodes) {
function checkNodes(a) {
if (a[1] === value + 1) {
getParts(a[1], path.concat(a[1]), nodes);
return true;
}
}
function iterPath(k) {
return (object[k] || []).some(function (a) {
return path[path.length - 1] + 1 === a[1] || iterPath(a[1]);
});
}
value = value || 0;
path = path || [value];
nodes = nodes || [];
if (object[value]) {
object[value].forEach(function (a, i, aa) {
if (a[1] === lastKey) {
parts.push(path.concat(a[1]));
return;
}
getParts(a[1], path.concat(a[1]), nodes.concat(aa.slice(i + 1)));
});
return;
}
if (nodes.some(checkNodes)) {
return;
}
path.slice(1).some(iterPath) && getParts(path[path.length - 1] + 1, path.concat(path[path.length - 1] + 1), nodes);
parts.push(path);
}
var object = {
0: [[0, 1], [0, 3], [0, 4]],
1: [[1, 2], [1, 3]],
2: [[2, 3], [2, 5]],
3: [[3, 4], [3, 6]],
5: [[5, 6]],
6: [[6, 5]]
},
lastKey = 6,
parts = [];
getParts();
parts.forEach(function (a) { console.log(JSON.stringify(a)); });
.as-console-wrapper { max-height: 100% !important; top: 0; }
Well, I was sitting on this for some time now, and sharing across my take on the problem:
The input object can be considered as an adjacency list of a tree:
var obj={0:[[0,1],[0,3],[0,4]],1:[[1,2],[1,3]],2:[[2,3],[2,5]],3:[[3,4],[3,6]],5:[[5,6]],6:[[6,5]]};
and the following as the result required, which is in fact, as I see it, the list of all root-to-leaf paths of the tree:
[0,1,2,3,4]
[0,1,2,3,6]
[0,1,2,5,6]
[0,1,3,4]
[0,1,3,6]
[0,3,4]
[0,3,6]
[0,4]
a little different than the result set mentioned in the question which is the below:
[0,1,2,3,4,5,6]
[0,1,2,3,6]
[0,1,2,5,6]
[0,1,3,4,5,6]
[0,1,3,4]
[0,1,3,6]
[0,3,4,5,6]
[0,3,6]
[0,4]
The difference between the results is only the question whether 4 and 6 are leaf nodes
Solution:
So I assume that for our Tree here:
0 is the root node
4 and 6 are the leaf nodes
See code below - I created a tree first, and from that listed out all the root to leaf paths:
// removed "6:[[6,5]]" as 6 is a 'leaf' of the tree
var obj={0:[[0,1],[0,3],[0,4]],1:[[1,2],[1,3]],2:[[2,3],[2,5]],3:[[3,4],[3,6]],5:[[5,6]]};
var availableNodes = Object.keys(obj);
var tree = availableNodes.reduce(function(hash) {
return function(prev, curr) {
hash[curr] = hash[curr] || {};
hash[curr].children = hash[curr].children || [];
obj[curr].forEach(function(element) {
hash[element[1]] = hash[element[1]] || {};
hash[element[1]].children = hash[element[1]].children || [];
hash[curr].rootPath = hash[curr].rootPath || [];
hash[curr].children.push({value: element[1],children: hash[element[1]].children});
});
curr && prev.push({value: curr,children: hash[curr].children});
return prev;
};
}(Object.create(null)), []);
//console.log(JSON.stringify(tree));
var result = [];
function rootToLeafPaths(node, path) {
path.push(+node.value);
if (node.children.length === 0) {
result.push(Array.from(path));
path.pop();
} else {
node.children.forEach(function(element) {
rootToLeafPaths(element, path);
});
path.pop();
}
}
rootToLeafPaths(tree[0], []);
console.log(JSON.stringify(result));
.as-console-wrapper{top:0;max-height:100%!important;}

How can I change the position of a particular item in an array?

I'm trying to change an item's index position in an array, but I cannot figure out a way.
{
"items": [
1,
3,
2
]
}
You can use splice to move an element in an array:
var arr = [
1,
3,
2
];
var oldIndex = 2,
newIndex = 1;
arr.splice(newIndex, 0, arr.splice(oldIndex, 1)[0]);
This makes [1, 2, 3]
The internal splice removes and returns the element, while the external one inserts it back.
Just for fun I defined a generic function able to move a slice, not just an element, and doing the computation of the index:
Object.defineProperty(Array.prototype, "move", {
value:function(oldIndex, newIndex, nbElements){
this.splice.apply(
this, [newIndex-nbElements*(newIndex>oldIndex), 0].concat(this.splice(oldIndex, nbElements))
);
}
});
var arr = [0, 1, 2, 7, 8, 3, 4, 5, 6, 9];
arr.move(5, 3, 4);
console.log('1:', arr) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
var arr = [0, 1, 2, 7, 8, 3, 4, 5, 6, 9];
arr.move(3, 9, 2);
console.log('2:', arr); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
var arr = [0, 1, 2, 4, 5, 3, 6, 7];
arr.move(5, 3, 1);
console.log('3:', arr); // [0, 1, 2, 3, 4, 5, 6, 7]
var arr = [0, 3, 1, 2, 4, 5, 6, 7];
arr.move(1, 4, 1);
console.log('3:', arr); // [0, 1, 2, 3, 4, 5, 6, 7]
JS Bin
If you want to sort them in Unicode order (where numbers become strings) you can use sort() function.
items.sort();
If you have your custom order, you need to provide a sort function to the sort function.
function compare(a, b) {
if (a is less than b by some ordering criterion) {
return -1;
}
if (a is greater than b by the ordering criterion) {
return 1;
}
// a must be equal to b
return 0;
}
and you use it like this:
items.sort(compare(a, b));

Categories