We have:
var range = [9,18,3,14,2,6,12,7,11,2,1,4]
var total = 89;
var group_size = total / 3;
As result I need 3 groups similar sized but group1 and group2 can never be bigger than group_size.
The result for this example would be
var group1 = 27; // 9,18
var group2 = 25; // 3,14,2,6
var group3 = 37; // 12,7,11,2,1,4
How can I achieve this in Javascript/jQuery?
I think you need to pop values with Javascript if you want this. Something like:
var range = [9,18,3,14,2,6,12,7,11,2,1,4]
var total = 89;
var group_size = total / 3;
var values = [0];
var groupnr = 0;
// Reverse the array, because pop will get the last element
range = range.reverse();
// While elements
while( range.length ) {
// get the last element and remove from array
var curvalue = range.pop();
// To large, create a new element
if( values[groupnr] + curvalue > group_size && groupnr < 2 ) {
groupnr++;
values[groupnr] = 0;
}
// Increase
values[groupnr] += curvalue;
}
console.log(values);
Update: This really answers a similar question (since removed by the author) which didn't include the restriction that the first two groups could not exceed the mean. With that restriction, it's a much simpler problem, and one that probably wouldn't have caught my attention. I'm leaving my answer here as the question it answers seems to be algorithmically interesting.
I have an answer using the Ramda functional programming library. You can see it in action on JSFiddle. (See below for an updated version of this that doesn't depend upon Ramda.) Ramda offers a number of convenient functions that make the code simpler. None of them should be surprising if you're at all used to functional programming, although if you're used to tools like Underscore or LoDash the parameter orders might seem backwards. (Believe me, there is a good reason.)
var equalSplit = (function() {
var square = function(x) {return x * x;};
var variance = function(groups) {
var sizes = map(sum, groups),
mean = sum(sizes) / sizes.length;
return sum(map(pipe(subtract(mean), square), sizes));
};
var firstGroupChoices = function(group, count) {
if (group.length < 2 || count < 2) {return group;}
var mean = sum(group) / count;
var current = 0, next = group[0], idx = 0;
do {
current = next;
next = next + group[++idx];
} while (next < mean);
if (next === mean) {
return [group.slice(0, idx + 1)]
} else {
return [
group.slice(0, idx),
group.slice(0, idx + 1)
];
}
};
var val = function(group, count, soFar) {
if (count <= 0 || group.length == 0) {
return {groups: soFar, variance: variance(soFar)};
}
if (count == 1) {
return val([], 0, soFar.concat([group]));
}
var choices = firstGroupChoices(group, count);
var values = map(function(choice){
return val(group.slice(choice.length), count - 1,
soFar.concat([choice]));
}, choices);
return minWith(function(a, b) {
return a.variance - b.variance;
}, values);
};
return function(group, count) {
return val(group, count, []).groups;
}
}());
Here is some sample output from the Fiddle:
==================================================
Input: [9,18,3,14,2,6,12,7,11,2,1,4]
Split into 3 groups
--------------------------------------------------
Groups: [[9,18,3],[14,2,6,12],[7,11,2,1,4]]
Totals: [30,34,25]
Variance: 40.66666666666667
==================================================
==================================================
Input: [9,18,3,2,6,12,11,2,4]
Split into 3 groups
--------------------------------------------------
Groups: [[9,18],[3,2,6,12],[11,2,4]]
Totals: [27,23,17]
Variance: 50.66666666666667
==================================================
==================================================
Input: [23,10,6,22,22,21,22,14,16,21,13,14,22,16,22,6,16,14,8,20,10,19,12,14,12]
Split into 5 groups
--------------------------------------------------
Groups: [[23,10,6,22,22],[21,22,14,16],[21,13,14,22],[16,22,6,16,14,8],
[20,10,19,12,14,12]]
Totals: [83,73,70,82,87]
Variance: 206
==================================================
I am not at all convinced that this algorithm will give you an actual optimal solution. I think there is some chance that a search might fall into a local minimum and not notice the global minimum in a nearby valley of the search space. But I haven't tried very hard to either prove it or come up with a counterexample. There is also a reasonable chance that it actually is correct. I have no idea if there is some more efficient algorithm than this. The problem feels vaguely like partitioning problems and knapsack problems, but I know I've never run across it before. Many of those problems are NP Hard/NP Complete, so I wouldn't expect this to have a really efficient algorithm available.
This one works in a fairly simple recursive manner. The internal val function accepts an array of numbers, the number of groups to create, and and accumulator (soFar) containing all the groups that have been created so far. If count is zero, it returns a simple result based upon the accumulator. If count is one, it recurs with an empty group, a count of zero, a an accumulator that now includes the original group as its last element.
For any other value of count it calculates the mean size of the remaining group, and then chooses the last initial partial sum of the group smaller than the mean and the first one larger than it (with a special case if there is one exactly equal to it), recurs to finds the value if those partial sequences are used as groups in the anser, and then returns the one with the smaller value.
Values are determined by calculated the variance in the total values of each subgroup formed. The variance is the sum of the squares of the distance from the mean.
For instance, if you started with these values:
[8, 6, 7, 5, 3, 1, 9]
And wanted to break them into three groups, you would have a mean of
(8 + 6 + 7 + 5 + 3 + 1 + 9 = 39) / 3 => 13
If you broke them up like this:
[[8, 6], [7, 5, 3], [1, 9]]
you would have totals
[14, 15, 10]
and a variance of
(14 - 13)^2 + (15 - 13)^2 + (10 - 13)^2 => 14
But if you broke them like this:
[[8, 6], [7, 5], [3, 1, 9]]
your totals would be
[14, 12, 13]
and your variance would be
[14 - 13)^2 + (12 - 13)^2 + (13 - 13)^2 => 2
And since the latter split has the lower variance it is considered better.
Example
equalSplit([9, 18, 3, 2, 6, 12, 11, 2, 4], 3 []) = minVal(
equalSplit([18, 3, 2, 6, 12, 11, 2, 4], 2, [[9]]),
equalSplit([3, 2, 6, 12, 11, 2, 4], 2, [[9, 18]])
);
equalSplit([18, 3, 2, 6, 12, 11, 2, 4], 2, [[9]]) =
equalSplit([12, 11, 2, 4], 1, [[9], [18, 3, 2, 6]]);
equalSplit([3, 2, 6, 12, 11, 2, 4], 2, [9, 18]) = minVal(
equalSplit([12, 11, 2, 4], 1, [[9, 18], [3, 2, 6]])
equalSplit([11, 2, 4], 1, [[9, 18], [3, 2, 6, 12]]
);
equalSplit([12, 11, 2, 4], 1, [[9], [18, 3, 2, 6]]) =
equalSplit([], 0, [[9], [18, 3, 2, 6], [12, 11, 2, 4]])
equalSplit([12, 11, 2, 4], 1, [[9, 18], [3, 2, 6]]) =
equalSplit([], 0, [[9, 18], [3, 2, 6], [12, 11, 2, 4]]) =
equalSplit([11, 2, 4], 1, [[9, 18], [3, 2, 6, 12]]
equalSplit([], 0, [[9, 18], [3, 2, 6, 12], [11, 2, 4]]
equalSplit([], 0, [[9], [18, 3, 2, 6], [12, 11, 2, 4]]) =
variance((9), (18 + 3 + 2 + 6), (12 + 11 + 2 + 4)) =
variance(9, 29, 29) = 266.67
equalSplit([], 0, [[9, 18], [3, 2, 6], [12, 11, 2, 4]]) =
variance((9 + 18), (3 + 2 + 6), (12 + 11 + 2 + 4)) =
variance(27, 11, 29) = 194.67
equalSplit([], 0, [[9, 18], [3, 2, 6, 12], [11, 2, 4]] =
variance((9 + 18), (3 + 2 + 6 + 12), (11 + 2 + 4)) =
variance(27, 23, 17) = 50.67
There is almost certainly plenty that could be done to clean up this code. But perhaps it's at least a start on your problem. It's been a very interesting challenge.
Update
I did create a version which does not depend upon Ramda. The code is very similar. (I guess I really didn't need Ramda, at least not for the final version.):
var equalSplit = (function() {
var sum = function(list) {return list.reduce(function(a, b) {
return a + b;
}, 0);};
var square = function(x) {return x * x;};
var variance = function(groups) {
var sizes = groups.map(sum),
mean = sum(sizes) / sizes.length;
return sum(sizes.map(function(size) {
return square(size - mean);
}, sizes));
};
var firstGroupChoices = function(group, count) {
if (group.length < 2 || count < 2) {return group;}
var mean = sum(group) / count;
var current = 0, next = group[0], idx = 0;
do {
current = next;
next = next + group[++idx];
} while (next < mean);
if (next === mean) {
return [group.slice(0, idx + 1)]
} else {
return [
group.slice(0, idx),
group.slice(0, idx + 1)
];
}
};
var val = function(group, count, soFar) {
if (count <= 0 || group.length == 0) {
return {groups: soFar, variance: variance(soFar)};
}
if (count == 1) {
return val([], 0, soFar.concat([group]));
}
var choices = firstGroupChoices(group, count);
var values = choices.map(function(choice){
return val(group.slice(choice.length), count - 1,
soFar.concat([choice]));
});
return values.sort(function(a, b) {
return a.variance - b.variance;
})[0];
};
return function(group, count) {
return val(group, count, []).groups;
}
}());
Of course, as now noted at the top, this answers a somewhat different question than is now being asked, but I think it's a more interesting question! :-)
Related
If I have an array like
const arr = [1, 5, 7, 5, 13, 8, 1, 7, 3, 8, 5, 2, 1, 5, 7];
What would be the best way of finding that the array starts to repeat itself? In this instance that the first three numbers and the last three numbers are in a repeating pattern.
This is a random array, the repeating could easily start at index 365 and not necessarily from the first index.
Any ideas?
Thanks in advance
This does what you're looking for...
const arr1 = [1, 5, 7, 5, 13, 8, 1, 7, 3, 8, 5, 2, 1, 5, 7];
const arr2 = [1, 5, 7, 5, 13, 8, 1, 7, 3, 8, 5, 2, 1, 4, 7];
function patternFound(arr) {
var newArray = arr.map(function(o, i) {
if (i < arr.length - 1) {
return arr[i] + "|" + arr[i + 1];
}
})
.sort();
newArray = newArray.filter(function(o, i) {
if (i < arr.length - 1) {
return (o == newArray[i + 1]);
}
});
return newArray.length > 0;
}
console.log(patternFound(arr1));
console.log(patternFound(arr2));
Basically, it creates an array of paired elements from the first array, with a pipe delimiter (["1|5", "5|7", "7|5" etc.]), sorts it and then looks for duplicates by comparing each element to the next.
There's probably a much smaller way of doing this, but I didn't want to spend time making something that was unreadable. This does what you want and does it simply and clearly.
The first array is the one you supplied, and the second has been changed so there's no matching pattern.
You could use a single loop approach with short circuit and a hash table for found pairs like
{
"1|5": true,
"5|7": true,
"7|5": true,
"5|13": true,
"13|8": true,
"8|1": true,
"1|7": true,
"7|3": true,
"3|8": true,
"8|5": true,
"5|2": true,
"2|1": true
}
The iteration stops immediately on index 12 with the other found pair 1|5.
function check(array) {
var hash = Object.create(null);
return array.some(function (v, i, a) {
var pair = [v, a[i + 1]].join('|');
return hash[pair] || !(hash[pair] = true);
});
}
console.log(check([1, 5, 7, 5, 13, 8, 1, 7, 3, 8, 5, 2, 1, 5, 7])); // true
console.log(check([1, 5, 7, 5, 13, 8, 1, 7, 3, 8, 5, 2, 1, 3, 7])); // false
A nested loop seems the simplest approach.
Make sure to offset the nested loop to save calculations:
/**
* Takes array and returns either boolean FALSE or the first index of a pattern
*
* #param {any[]} arr
* #returns {(false | number)}
*/
function findArrayPattern(arr) {
if (arr.length < 2) {
return false;
}
for (var point1 = 0; point1 < arr.length - 2; point1++) {
var p1 = arr[point1];
var p2 = arr[point1 + 1];
for (var point2 = point1 + 2; point2 < arr.length - 1; point2++) {
var p3 = arr[point2];
var p4 = arr[point2 + 1];
if (p1 == p3 && p2 == p4) {
return point1;
}
}
}
return false;
}
//TEST
var arr = [1, 5, 7, 5, 13, 8, 1, 7, 3, 8, 5, 2, 1, 5, 7];
var pattern = findArrayPattern(arr);
if (pattern !== false) {
console.log("a pattern was found at " + pattern);
} else {
console.log("no pattern was found");
}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
Am trying to find a better way to return a range of array values from an existing array.
For an list/array of numbers, say:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
I want to select a range of 5 numbers centred around the a given number x.
(Psuedocode since I guess i'm really referring to array indexes here.. values don't matter just the position)
So if x is 4, we can return a range centred on that:
[2, 3, 4, 5, 6]
But if x is 2, we cannot centre the range, so we'd have to do our best and return:
[1, 2, 3, 4, 5]
... Not centered, but atleast we have returned 5 numbers.
Similarly if x is 10:
[5, 6, 7, 8, 9, 10]
... 10 is the limit, so cannot centre, so the 5 numbers are pushed backwards.
I've got this working in some JS code, but it feels like too much code with too many conditionals.
Wondering if there is any known method or algorithm that can help?
You can do something like this.
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function findSubset(arr, item) {
// check index
var index = arr.indexOf(item);
// if element not found then return
if (index == -1) return;
// if element is at starting position
// then return first 5 element
if (index < 3)
return arr.slice(0, 5);
// if elements at ending position
// then return last 5 elements
if (index > arr.length - 4)
return arr.slice(-5);
// otherwisse return elements based on the index
// within the required range
return arr.slice(index - 2, index + 3);
}
console.log(
findSubset(arr, 1),
findSubset(arr, 10),
findSubset(arr, 5),
findSubset(arr, 9),
findSubset(arr, 3)
)
A generic solution with a varying count.
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function findSubset(arr, item, count = 5) {
var index = arr.indexOf(item),
// calculate floor and ceil value for comparison
// and getting subset array
f = Math.floor(count / 2),
c = Math.ceil(count / 2);
if (index == -1) return;
if (index < c)
return arr.slice(0, count);
if (index > arr.length - c - 1)
return arr.slice(-count);
return arr.slice(index - 2, index + c);
}
console.log(
findSubset(arr, 1, 3),
findSubset(arr, 10, 7),
findSubset(arr, 5, 1),
findSubset(arr, 9, 4),
findSubset(arr, 8, 1),
findSubset(arr, 7, 3),
findSubset(arr, 3, 9)
)
You could move the found index by subtracting the half size and take a max value for negative indices and a min value for indices which are greater than the array length minus the size of the wanted sub array.
value array index adj max min
----- ------------------------------ ----- --- --- ---
v
2 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 1 -1 0 0
[ ]
v
5 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 4 2 2 2
[ ]
vv
10 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 9 7 7 5
[ ]
function getSub(array, value, size) {
var index = array.indexOf(value) - (size - 1) / 2,
max = Math.max(index, 0),
min = Math.min(max, array.length - size);
return array.slice(min, min + size);
}
console.log(getSub([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 2, 5));
console.log(getSub([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5, 5));
console.log(getSub([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10, 5));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Recipe of the below algorithm :
Create subset s of max length a.length.
Calculate starting index that is index of x minus half the length of s.
Adjust starting index to prevent index overflow.
Copy s.length items from a to s.
Return s.
Array s is guaranteed to be contained in array a since s is never bigger than a.
var a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
function subset (a, x, n) {
var s = new Array(Math.min(a.length, n));
var j = a.indexOf(x) - Math.floor(s.length / 2);
// overlap to the left : [.[1]2 3 4]
if (j < 0) {
j = 0;
}
// overlap to the right : [1 2 3[4].]
else if (j > a.length - s.length) {
j = a.length - s.length;
}
for (var i = 0; i < s.length; i++) {
s[i] = a[j + i]
}
return s;
}
console.log("x = 2, n = 4, s =", subset(a, 2, 4).join(","));
console.log("x = 9, n = 4, s =", subset(a, 9, 4).join(","));
console.log("x = 5, n = 4, s =", subset(a, 5, 4).join(","));
console.log("x = 2, n = 5, s =", subset(a, 2, 5).join(","));
console.log("x = 9, n = 5, s =", subset(a, 9, 5).join(","));
console.log("x = 5, n = 5, s =", subset(a, 5, 5).join(","));
console.log("x = 5, n = 20, s =", subset(a, 5, 20).join(","));
However, hard to know if it's shorter than your own code since you didn't provide it :-|
I have an array of x/y/z positions from a BufferGeometry, there are nearly 60000 points (18000 values),
[3, 2, 1, 3, 2, 1, 3, 2, 1, ...]
I need to shuffle those positions and then get the 30000 first to get random points. I am thinking of converting this array to an array of Vector 3 before shuffling to not lose all the "trio" values.
[new THREE.Vector3(3, 2, 1), new THREE.Vector3(3, 2, 1), new THREE.Vector3(3, 2, 1), ...]
I need vocabulary help.
Is there specific names for those two arrays please ? (that would help me found out about the next questions).
Is there specific methods to convert an array to another ?
And is there a best way to shuffle the raw positions array ?
The short answer is: you don't need to convert to THREE.Vector3 in order to extract n random points from the array. There is no specific name for both arrays.
Below I provide the functions to execute the operations you would like to perform:
And is there a best way to shuffle the raw positions array ? (yes, see below)
var points = [5, 6, 7, 3, 2, 1, 5, 6, 7, 3, 2, 1, 5, 6, 7]
// Assuming there are more points than the number of points you need to get back
function getRandomPoints (points, numberOfPoints) {
var resultPoints = []
for (var i = 0; i < numberOfPoints; i++) {
// Get a random index
var randomIndex = Math.floor(Math.random() * points.length);
var index = (randomIndex - randomIndex % 3)
resultPoints.push(points[index])
resultPoints.push(points[index + 1])
resultPoints.push(points[index + 2])
points.splice(index, index + 3);
}
return resultPoints;
}
var randomPoints = getRandomPoints(points, 2);
Is there specific methods to convert an array to another ? (yes, see below)
var points = [5, 6, 7, 3, 2, 1, 5, 6, 7, 3, 2, 1, 5, 6, 7]
function convertToVector (points) {
var resultPoints = []
for (var i = 0; i < points.length; i = i + 3) {
var vectorPoint = new THREE.Vector3(points[i], points[i + 1], points[i + 2])
resultPoints.push(vectorPoint)
}
return resultPoints
}
var convertedPoints = convertToVector(points)
Given the array (list) of integers below, I am looking to extract each integer and if three or more integers ascend consecutively, I want to replace the middle integers with a "-" to represent a range. And then lastly return values as a string.
For example, the first 7 integers from list: -6, -3, -2, -1, 0, 1, 3
Would become '-6,-3-1,3'
Because there is more than three consecutive integers from -3 to 1.
Ultimately, solution(list) should return the following string: "-6,-3-1,3-5,7-11,14,15,17-20"
In its present form it returns the following string: "-6,-3,-2,-1,0,1,3,4,5,7,8,9,10,11,14,15,17,18,19,20"
Which is simply the array converted into a string.
var list = [-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20];
solution(list);
function solution(list) {
final = [];
range = [];
while (list.length > 0) {
take = list.splice(0,1);
range.push(take);
n = 1;
while (take+n === list[0]) {
a = list.splice(0,1);
range.push(a);
n++;
}
if (range.length >= 3) {
min = Math.min(range).toString();
max = Math.max(range).toString();
final.push(min + "-" + max);
range.length = 0;
} else if (range.length === 2) {
final.push(range[0].toString());
final.push(range[1].toString());
range.length = 0;
} else if (range.length === 1) {
final.push(range[0].toString());
range.length = 0;
}
}
return final.join(",");
}
However, I was able to get the desired result successfully in Ruby:
list = [-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]
def solution(list)
final = []
range = []
while (list.length > 0) do
take = list.shift
range << take
n = 1
while (take+n == list[0]) do
a = list.slice!(0)
range << a
n +=1
end
if (range.length >= 3)
final << (range.min.to_s + "-" + range.max.to_s)
range = []
elsif (range.length == 2)
final << range[0]
final << range[1]
range = []
elsif (range.length == 1)
final << range[0].to_s
range = []
end
end
return final.join(",")
end
My approach in Ruby is almost identical that of my JavaScript. So, if I was wondering if someone could:
1) Explain why this approach works with Ruby, but not Javascript. Please feel free to inform me even if it is a simple syntax error on my part.
2) How I could possibly go about extracting the integers and returning the correct ranges in JavaScript?
Your help is greatly appreciated! Thanks!
Here's a better ruby solution:
list = [-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]
stringified = list.chunk_while{|a, b| a == b - 1}.map do |seq|
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the core of the solution
if seq.length > 2 # long enough sequence
"#{seq.first}-#{seq.last}"
else
seq
end
end.join(', ')
stringified # => "-6, -3-1, 3-5, 7-11, 14, 15, 17-20"
This should also be a hint for your javascript implementation. Separate concerns as much as possible. Chunking, stringifying, turning a sequence into a range-like string: these all should be separate pieces of code. If they're not all tangled together, they're much easier to reason about.
In Javascript, you could use a three pass approach, first for getting the grouped ranges, then get the ranges in an array and later join the whole array.
var array = [-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20],
result = array
.reduce(function (r, a) {
var last = r[r.length - 1];
if (last && last[1] + 1 === a) {
last[1] = a;
} else {
r.push([a, a]);
}
return r;
}, [])
.reduce(function (r, a) {
return r.concat(a[0] === a[1] ? a[0] : a[1] - a[0] < 2 ? a : a.join('-'));
}, [])
.join();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Splice returns an array, you have to take the first item from returned array
take = list.splice(0,1)[0];
Also note that shift (take = list.shift()) is a better candidate for this operation.
Math.min doesn't accept arrays, you can use a workaround with apply/call
Math.min.apply(null,range);
In ES6 this can be done as Math.min(...range) using spread syntax
It is better to clear an array by assigning to a new array.
range = [];
I obviously prefer the functional and declarative method by #ninasholz This is just to explain why my code didn't work
var list = [-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20];
console.log(solution(list));
function solution(list) {
final = [];
range = [];
while (list.length > 0) {
//splice returns array take first item
take = list.splice(0,1)[0];
range.push(take);
n = 1;
while (take + n === list[0]) {
a = list.splice(0,1)[0];
range.push(a);
n++;
}
console.log(range);
if (range.length >= 3) {
//Math.min doesnt accept arrays
min = Math.min.apply(null,range);
max = Math.max.apply(null,range);
final.push(min + "-" + max);
range = [];
} else if (range.length === 2) {
final.push(range[0]);
final.push(range[1]);
range = [];
} else if (range.length === 1) {
final.push(range[0]);
range = [];
}
}
return final.join(",");
}
I cannot figure out the best way to dynamically generate a multidimensional array with 2 different sizes.
We have a UI that requires a row of 4 items, then 3. This pattern would repeat until the content in the array has been spent.
This is essentially what I need to do:
// Convert
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
// to
const rows [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10, 11], [12, 13, 14]];
This is what I currently have, it is only converting the arrays to 4 each.
const buildRows = (arr, length) => arr.reduce((rows, val, i) => (
i % length == 0 ? rows.push([val]) : rows[rows.length-1].push(val)
) && rows, []);
Thank you in advance for the help.
The following solution will mutate (empty) the input array array:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
let result = [], i = 0;
while(array.length) { // while there still items in array (array is continually getting shrunk untill it is emptied (array.length === 0))
result.push(array.splice(0, i++ % 2? 3: 4)); // cut the first 3 or 4 numbers depending on the index of the cut i (if i is pair, then cut 4, else, cut 3) and then push the cut-out items into the result array
}
console.log(result);
If you don't want to mutate it, then use slice instead of splice, but you'll have to provide the start index of the cut:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
let result = [], i = 0, next = 0; // next will be the index from which the cut will start
while(next < array.length) { // there is still items to cut
let itemsToCut = i % 2? 3: 4; // determine how many items we are going to cut
result.push(array.slice(next, next + itemsToCut)); // cut the items between next and next + itemsToCut
next += itemsToCut; // increment next by the number of cut-out items so it will point to the next item
i++;
}
console.log(result);
I suggest a more self-documenting generator solution where even & odd row-sizes are not hardcoded but supplied via arguments:
function* reshape(array, ...rows) {
let i = 0;
while (true) for (let row of rows) {
if (i >= array.length) return;
yield array.slice(i, i += row);
}
}
// Example:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
for (let row of reshape(array, 4, 3)) console.log(row);
A true generator purist would simplify reshape by first introducing a repeat generator:
function* repeat(iterable) {
while (true) yield* iterable;
}
function* reshape(array, ...rows) {
let i = 0;
for (let row of repeat(rows)) {
if (i >= array.length) break;
yield array.slice(i, i += row);
}
}
// Example:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
for (let row of reshape(array, 4, 3)) console.log(row);
You can achieve that using Array#reduce, a pointer to the last place, and a step variable that alternates between 3 and 4:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let last = 0;
let step = 0;
const result = array.reduce((r, num, i) => {
if(i === last + step) { // when the previous sub array is full
r.push([]); // add another sub array
last = i; // mark the start index of the current sub array
step = step === 4 ? 3 : 4; // alternate the step
}
r[r.length - 1].push(num); // push the number to the last sub array
return r;
}, []);
console.log(result);
Straighfoward and easy-to-read solution:
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const r = [];
let chunk = [];
let l = 4;
array.forEach((el, i) => {
if (chunk.length < l) chunk.push(el);
if (chunk.length === l) {
r.push(chunk); chunk = [];
l = ( l === 4 ) ? 3 : 4;
}
})
console.log(r)
Yet another solution. Clearly everyone is having a good time with this one.
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
function divide(array, idx, num, result)
{
if(array.length <= idx) return result;
result.push(array.slice(idx,idx+num));
return divide(array, idx+num, num === 4 ? 3 : 4, result);
}
console.log(divide(array, 0, 4, []));
We can think of it as slicing elements from the array in a loop. It's just that we need to alternate between 4 and 3 instead of a constant value to slice.
We can parameterize alternating values by passing them in a function instead of hardcoding it in the solution like below:
Use Array##slice and
Just swap current and next like this by using destructuring assignment to achieve the solution.
Sub array sizes(4,3) can be modified outside actual logic or can be passed in a function to have flexible solution.
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
function TransformArray(array, current, next) {
let start = 0,
ans = [];
while (start < array.length - 1) {
ans.push(array.slice(start, start + current));
start += current;
[current, next] = [next, current]; //swap the count of array size
}
return ans;
}
console.log(TransformArray(array, 4, 3));
console.log(TransformArray(array, 3, 3));
Here's kind of a different way of doing this, I'm expanding a bit to allow you to arbitrarily pass array lengths, this allows the PM to change their mind any time and it isn't a big deal.
This could be cleaned up a bit, I wanted to leave it more verbose to make it easier to read.
// Setup the function getting in:
// an array
// first array's length
// second array's length
const arrayParser = (inArr,arr1len,arr2len) => {
// Create a new array.
let outArr = [];
// Basic forEach is basic, we need the value and the index.
inArr.forEach((val,idx) => {
// If the index's modulus of the total of the two array lengths is:
// 0 OR the first array's length
// Push a new empty array.
if (idx%(arr1len+arr2len)===0 || idx%(arr1len+arr2len)===arr1len) {
// Create a new array with the current value
outArr.push([]);
}
// Push the value to the last array in the out multidimensional array
outArr[outArr.length-1].push(val);
});
// You got it.. return the array.
return outArr;
};
// Single Dimensional Array
const singleArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30];
// Parse it.
// Expects:
// A Single Dimensional Array
// Length of the first array
// Length of the second array
console.log(arrayParser(singleArray,10,4));
console.log(arrayParser(singleArray,2,4));
console.log(arrayParser(singleArray,3,4));
console.log(arrayParser(singleArray,4,3));
console.log(arrayParser(singleArray,1,2));
This works, because you know the length of each of the inner arrays, so you don't really need to figure out anything.
Here's a 4,3 set broken out.
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Push a new array at 0 and 4.
4+3
index total Modulus
0 % 7 = 0 <-- push [], push 1
1 % 7 = 1 <-- push 2
2 % 7 = 2 <-- push 3
3 % 7 = 3 <-- push 4
4 % 7 = 4 <-- push [], push 5
5 % 7 = 5 <-- push 6
6 % 7 = 6 <-- push 7
7 % 7 = 0 <-- push [], push 8
8 % 7 = 1 <-- push 9
9 % 7 = 2 <-- push 10
10 % 7 = 3 <-- push 11
11 % 7 = 4 <-- push [], push 12
12 % 7 = 5 <-- push 13
13 % 7 = 6 <-- push 14
Returns
[[1,2,3,4],[5,6,7],[8,9,10,11],[12,13,14]]
It ain't pretty and as much as I try to write functional code, it's pretty easy with a while loop...
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14];
const newArray = [];
let i = 0;
while (i < array.length) {
let four = array.slice(i, i + 4)
if (!(four.length > 0)) {
break;
}
newArray.push(four)
i += 4;
let three = array.slice(i, i + 3);
if (!(three.length > 0)){
break;
}
newArray.push(three);
i += 3;
}
return newArray