Reverse array in-place using split-merge method - javascript

I am trying to reverse an array in JS in-place (without using extra array).
I am using split-merge recursively (I first split the array into two halves and then rearrange each half separately and after that combine the results of the two arrangements).
the problem seems to arise if the array length is an odd number, in which case there will exist an array that have a single item and another array that have two items.
example :
reverseArrayInPlace([1, 5, 0, 4, 6])
should work like this :
1- reverseArrayInPlace([1,5,0]) -> returns the below calls
reverseArrayInPlace([1, 5]) -> returns [5, 1]
reverseArrayInPlace([0]) -> returns [0]
now the two arrays of the first call should be merged and swapped. The result should be [0,5,1]
2- reverseArrayInPlace([4, 6]) -> returns [6,4]
Now, the result of the call (1) and call (2) must be merged and swapped (using concat also);
which will make the result as : [6,4,0,5,1].
I know there are other easier ways, but I want to know why my code is not returning the correct value .
let reverseArrayInPlace = ar => {
let splitArray = arr => {
if( arr.length === 1){
console.log('length = 1', arr);
return arr;
}
else if(arr.length === 2){
console.log('length = 2', arr);
let temp = arr[0]; arr[0] = arr[1]; arr[1] = temp;
return arr;
}
else{
reverseArrayInPlace(arr);
//reverseArrayInPlace (ar2);
}
}
let mergeArray = (arr1, arr2) => {
console.log("Swapping : ", arr1, arr2);
console.log('Concated : ',arr2.concat(arr1));
if(arr1 === undefined)
return arr2;
else if(arr2 === undefined)
return arr1;
else
return arr2.concat(arr1);
}
let half = Math.ceil(ar.length / 2);
//console.log('half = ', half);
ar1 = splitArray(ar.slice(0, half));
ar2 = splitArray(ar.slice(half));
//console.log(arr1, arr2);
return mergeArray(ar1, ar2);
}
let ar = [1, 5, 0, 4, 6];
console.log(reverseArrayInPlace(ar));

In your split array function, you miss a return:
let splitArray = arr => {
if( arr.length === 1){
console.log('length = 1', arr);
return arr;
}
else if(arr.length === 2){
console.log('length = 2', arr);
let temp = arr[0]; arr[0] = arr[1]; arr[1] = temp;
return arr;
}
else{
***RETURN*** reverseArrayInPlace(arr);
//reverseArrayInPlace (ar2);
}
}
I used debugger in chrome navigateur to go step by step and noticed that ar1 was null on your step 1- reverseArrayInPlace([1,5,0]) -> returns the below calls
So the result contains only the second part: the reversed second array [6,4]

The missing return statement is the initial problem, but more broadly speaking, this is not a true inplace algorithm, because it still reserves O(n) auxiliary memory by creating new arrays.
For an algorithm to be inplace, there should be no O(n) auxiliary memory usage. Instead make also the splitArray and mergeArray inplace functions, so that at all times there is only one array that is being mutated.
For that to happen, you would need to pass the start/end indices of the subarray that is going to be subject of the split/merge operation.
It is also safer to include the empty-array case in the first base case of splitArray: so use <= 1 instead of === 1.
Here is your code altered with that idea:
let reverseArrayInPlace = (arr, start=0, end=ar.length) => {
let splitArray = (start, end) => {
if (end - start <= 1) return;
if (end - start === 2) {
let temp = arr[start]; arr[start] = arr[start+1]; arr[start+1] = temp;
}
else{
reverseArrayInPlace(arr, start, end);
}
}
let mergeArray = (start, mid, end) => {
arr.splice(start, 0, ...arr.splice(mid, end - mid));
}
let half = (start + end) >> 1;
splitArray(start, half);
splitArray(half, end);
mergeArray(start, half, end);
return arr;
}
let ar = [1, 5, 0, 4, 6];
console.log(reverseArrayInPlace(ar));
You also don't really need to deal with the second base case separately. The operation will work fine if you deal with that case as a recursive case.
Here is your code altered with that idea:
let reverseArrayInPlace = (arr, start=0, end=ar.length) => {
let splitArray = (start, end) => {
if (end - start > 1) reverseArrayInPlace(arr, start, end);
}
let mergeArray = (start, mid, end) => {
arr.splice(start, 0, ...arr.splice(mid, end - mid));
}
let half = (start + end) >> 1;
splitArray(start, half);
splitArray(half, end);
mergeArray(start, half, end);
return arr;
}
let ar = [1, 5, 0, 4, 6];
console.log(reverseArrayInPlace(ar));

Related

Recursion in javascript, function restArray

Anyone can help me? i dont know where is my failure
Implement the restArray function: from an array in which each
position can be a single number or another nested array of numbers,
determine the sum of all numbers contained in the array.
The array will be received by parameter.
Example:
const array = [3, [7, [5,6]], [9,6], 4];
restArray(array); --> It should return 40 and you subtract the result by the number of arrays, not counting the parent.
Example:
40-3 = 37
var restArray = function(array) {
let sum = 0;
if (array.length === 0) {
return -1;
}
for (arr of array) {
if (!Array.isArray(arr)) {
sum += arr;
} else {
sum += restArray(arr);
}
}
return sum;
};
console.log(restArray([3, [7, [5,6]], [9,6], 4]));
Just a minor mistake in your code. As the top level array should not count for subtracting one from the sum, you should not have that case handled in the first if block.
Instead apply that minus one when coming back from recursion, so to account for that nested array:
var restArray = function(array){
let sum = 0;
for (arr of array) {
if (!Array.isArray(arr)) {
sum += arr;
} else {
sum += restArray(arr) - 1;
}
}
return sum;
};
const array = [3, [7, [5,6]], [9,6], 4];
console.log(restArray(array));
It is possible to use reduce to shorten the code a bit and use a bit more functional programming approach:
const restArray = array =>
array.reduce((sum, arr) =>
sum + (Array.isArray(arr) ? restArray(arr) - 1 : arr)
, 0);
const array = [3, [7, [5,6]], [9,6], 4];
console.log(restArray(array));

Creating an array from another array

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 );

Split array into arrays of numbers where the sum is equal to a specific target

I need to create a function that take as parameter an array and a target. It should return an array of arrays where the sum of these numbers equals to the target
sumPairs(array, target) {
}
For example:
sumPairs([1, 2, 3, 4, 5], 7) // output : [[2, 5], [3, 4]]
I know I have to use map(), and probably reduce(), set(), or filter() maybe (I read their documentation in MDN but still cant find out). I tried some ways but I can't get it.
If you guys could help me to find out how to dynamically create arrays and push them into a new array..
I read there some solutions (Split array into arrays of matching values) but I hate to just use created functions without knowing what they really do or how they work.
Some very basic code for achieving it, Just run all over combinations and conditionally add the items you want.
function sumPairs(array, target) {
var res = [];
for(var i = 0; i < array.length; i++){
for(var j = 0; j < array.length; j++){
if(i!=j && array[i]+array[j]==target &&
res.filter((x)=> x[0] == array[j] && x[1] == array[i]).length == 0 )
res.push([array[i], array[j]]);
}
}
return res;
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
Option 2 - see this answer for more options (like using reduce)
function sumPairs(array, target) {
return array.flatMap(
(v, i) => array.slice(i+1).filter(w => (v!=w && v+w==target)).map(w=> [w,v])
);
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
"The exercise says that it sould be arrays of pairs that sum the
target value so I think only 2 items"
If you need a pair that matches a sum and you pick any number from the list, you are left with
the following equation to solve num + x = sum where we want to find x. E.g. if you picked 7 and the target sum is 10 then you know you are looking for a 3.
Therefore, we can first construct a counting map of the numbers available in our list linear (O(n)) time and then search for matches in linear time as well rather than brute forcing with a quadratic algorithm.
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
return nums.reduce((pairs, num) => {
countByNum[num]--;
const target = sum - num;
if (countByNum[target] > 0) {
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[num]++;
}
return pairs;
}, []);
}
function countGroupByNum(nums) {
return nums.reduce((acc, n) => (acc[n] = (acc[n] || 0) + 1, acc), {});
}
Here's another implementation with more standard paradigms (e.g. no reduce):
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
const pairs = [];
for (const num of nums) {
const target = sum - num; //Calculate the target to make the sum
countByNum[num]--; //Make sure we dont pick the same num instance
if (countByNum[target] > 0) { //If we found the target
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[target]++; //Didin't find a match, return the deducted num
}
}
return pairs;
}
function countGroupByNum(nums) {
const countByNum = {};
for (const num of nums) {
countByNum[num] = (countByNum[num] || 0) + 1;
}
return countByNum;
}
You can also sort your array and find all the pairs with given sum by using two pointer method. Place the first pointer to the start of the array and the second pointer to the end.
if the sum of the values at the two places is :
More than target: Decrement your second pointer by 1
Less than target: Increment your first pointer by 1
Equal to target: This is one possible answer, push them to your answer array and increment your first pointer by 1 and decrement your second pointer by 1.
This is more performant solution with complexity O(n*log(n))

Javascript: Keep getting one off when looking for consecutive numbers in array

I am doing some coding practice and found some questions online.
I keep getting 1 integer lower than expected when looking to return the number of consecutive numbers inside an array.
function LongestConsecutive(arr) {
arr.sort((a,b) => {return a-b});
let highest = 0;
let counter = 0;
let prevNum;
arr.forEach((num,index,arr) => {
if (prevNum === undefined) {
prevNum = num
} else {
if (num + 1 == arr[index + 1]) {
counter += 1;
highest = Math.max(highest,counter)
} else {
counter = 0;
}
}
})
return highest;
}
for example, the input [5, 6, 1, 2, 8, 9, 7], should return 5 -- because when sorted, there are 5 consecutive numbers. I keep getting one lower than I should so for this example, I get 4. The only way to get the correct answer is when I return 'highest + 1', which obviously is avoiding the problem.
The first iteration will hit
if (prevNum === undefined) {
prevNum = num;
}
But isn’t that already the first consecutive number? So counter = 1; and highest = 1; should be here.
Next, you reset counter = 0; in an else case. Why? There’s at least one number that is consecutive, so reset it to 1 instead.
Then, you’re not really using prevNum for anything. if (prevNum === undefined) can be replaced by if (index === 1).
You then check if the current number (num) precedes the next number (arr[index + 1]), but you skip this check for the first index. How about checking if the current number succeeds the previous?
This code uses the above changes plus some code quality changes:
function longestConsecutive(arr) { // Non-constructor functions start with a lower-case letter
arr.sort((a, b) => a - b); // Use expression form
let highest = 0;
let counter = 0;
arr.forEach((num, index, arr) => {
if (index === 0) {
highest = 1;
counter = 1;
} else if (num - 1 === arr[index - 1]) { // Merge `else if`, use strict equal
counter += 1;
highest = Math.max(highest, counter);
} else {
counter = 1;
}
});
return highest;
}
Well, by the definition of consecutive, you'll always have 1 consecutive number. So you need to start the counter from 1.
I tried this code (its different than the one posted in the question) which gives the expected result. In addition, if there are two sets of consecutive numbers of same (and largest) length, both are printed,
var arr = [5, 6, 1, 2, 8, 9, 7, 99, 98];
arr.sort((a, b) => a - b);
var prevNum = arr[0];
var consecutiveNumbersArr = [prevNum];
// Map of consecutiveNumbersArr array as key and
// the array length as values
var arrMap = new Map();
for (let i = 1; i < arr.length; i++) {
let num = arr[i];
if (num === prevNum+1) {
prevNum = num;
consecutiveNumbersArr.push(num);
continue;
}
arrMap.set(consecutiveNumbersArr, consecutiveNumbersArr.length);
consecutiveNumbersArr = [];
consecutiveNumbersArr.push(num);
prevNum = num;
}
arrMap.set(consecutiveNumbersArr, consecutiveNumbersArr.length);
// the largest length of all the consecutive numbers array
var largest = 0;
for (let value of arrMap.values()) {
if (value > largest) {
largest = value;
}
}
// print the result - the largest consecutive array
for (let [key, value] of arrMap) {
if (value === largest) {
console.log("RESULT: " + key);
}
}
Can also be achieved with array:reduce
function longestRun(array) {
const { streak } = array
.sort((a, b) => a - b) // sort into ascending order
.reduce(({ count, streak }, current, index, arr) => {
if (current === arr[index - 1] + 1) {
count++; // increment if 1 more than previous
} else {
count = 1; // else reset to 1
}
return {count, streak: Math.max(streak, count)};
}, { count: 0, streak: 0 }); // initial value is 0,0 in case of empty array
return streak;
}
console.log(longestRun([])); // 0
console.log(longestRun([0])); // 1
console.log(longestRun([0, 1])); // 2
console.log(longestRun([0, 1, 0])); // 2
console.log(longestRun([0, 0, 0])); // 1
console.log(longestRun([2, 0, 1, 0, 3, 0])); // 4
If you are able to split arrays into subarrays via a condition, you can do it by splitting the array at non consecutive points.
const arr = [5, 6, 1, 2, 8, 9, 7, 11, 12, 13, 14]
// utility for splitting array at condition points
const splitBy = (arr, cond) => arr.reduce((a, cur, i, src) => {
if(cond(cur, i, src)){
a.push([])
}
a[a.length - 1].push(cur)
return a
}, [])
const consecutives = splitBy(
arr.sort((a, b) => a - b),
(cur, i, src) => cur !== src[i-1] + 1
).sort((a, b) => b.length - a.length)
// largest consecutive list will be the first array
console.log(consecutives[0].length)

JavaScript - Special case of subset sum algorithm

From a given array of positive integers, I want to know if the sum of E elements from the array is equal to a given number N.
For example, given the array arr = [1, 2, 3, 4] , e = 3 and n = 9. It means if the sum of 3 elements in arr equals to 9. The result is true since 2 + 3 + 4 is equal to 9.
Another example with arr = [1, 2, 3, 4] , e = 2 and n = 7. It is true since 3 + 4 is equal to 7.
I'm trying to resolve it with recursion, but I'm stuck. My idea is to nest loops dynamically to walk through the elements to the array and compare them.
My attempt is this:
function subsetsum(arr, elements, n) {
loop(arr, elements, n, [], 0);
}
function loop(arr, elements, n, aux, index) {
if(aux.length != elements) {
aux[index] = arr.length - 1;
loop(arr, elements, n, aux, index + 1);
} else {
if ((elements - index + 1) < 0) {
return 0;
} else {
if (aux[elements - index + 1] > 0) {
aux[elements - index + 1]--;
loop(arr, elements, n, aux, index);
}
}
}
}
subsetsum([1, 2, 3, 4], 3, 9));
A related question is at Find the highest subset of an integer array whose sums add up to a given target. That can be modified to restrict the number of elements in the subset as follows:
// Find subset of a, of length e, that sums to n
function subset_sum(a, e, n) {
if (n < 0) return null; // Nothing adds up to a negative number
if (e === 0) return n === 0 ? [] : null; // Empty list is the solution for a target of 0
a = a.slice();
while (a.length) { // Try remaining values
var v = a.shift(); // Take next value
var s = subset_sum(a, e - 1, n - v); // Find solution recursively
if (s) return s.concat(v); // If solution, return
}
}
I've been playing around with this for a while and decided to use a short-cut, mainly the permutation code from this previous SO question.
My code uses basically uses the permutation code to create an array of all the possible permutations from the input array, then for each array (using map) grabs a slice corresponding to the number specified as amount, sums that slice and if it is the same as total returns true.
some then returns the final result as to whether there are any permutations that equals the total.
function checker(arr, amount, total) {
var add = function (a, b) { return a + b; }
return permutator(arr).map(function(arr) {
var ns = arr.slice(0, amount);
var sum = ns.reduce(add);
return sum === total;
}).some(Boolean);
}
checker([1, 2, 3, 4], 3, 9); // true
I've included two demos - 1) a demo showing this code, and 2) code that provides a more detailed breakdown: basically map returns an object containing the slice info, the sum totals and whether the condition has been met.
This is probably not what you're looking for because it's a bit long-winded, but it was certainly useful for me to investigate :)
Edit - alternatively here's a hacked version of that permutation code from the previous question that delivers the results and an array of matches:
function permutator(inputArr, amount, total) {
var results = [], out = [];
function permute(arr, memo) {
var cur, memo = memo || [];
var add = function (a, b) { return a + b; }
for (var i = 0; i < arr.length; i++) {
cur = arr.splice(i, 1);
if (arr.length === 0) {
results.push(memo.concat(cur));
}
var a = arr.slice();
// this is the change
var sum = memo.concat(cur).reduce(add);
if (memo.concat(cur).length === amount && sum === total) {
out.push(memo.concat(cur))
}
permute(a, memo.concat(cur));
arr.splice(i, 0, cur[0]);
}
return [results, out];
}
return permute(inputArr);
}
permutator([1,2,3,4], 3, 9);
DEMO
If I understand you correctly, the solution of this task must be simple like this:
function subsetsum(arr, countElements, sum) {
var length = arr.length-1;
var temp = 0;
var lastElement = length-countElements;
console.log(lastElement);
for (var i = length; i > lastElement; i--) {
temp = temp+arr[i];
console.log('Sum: '+temp);
}
if (temp === sum) {
console.log('True!');
} else {console.log('False!')}
};
subsetsum([1, 2, 3, 4], 2, 7);

Categories

Resources