I am trying to use array.reduce on an array representing a single binary value. For instance [1,0,1] in binary would convert to 5 in decimal.
I have been able to succesfully convert between binary and decimal using a while loop, but would like to upgrade my code to use the reduce method.
What I have implemented so far is accurate up to 6 elements in the array. I am not sure why, but after 6 digts, the conversion fails.
In addition, I am using a formula to make the conversion. For example: to convert 111001 to decimal, you would have to do (1*2^5) + (1*2^4) (1*2^3) + (0*2^2) + (0*2^1) + (1*2^0).
const getDecimalValue = function (head) {
let total = head.reduce(
(sum) =>
sum + (head.shift() * Math.pow(2, head.length))
)
return total
}
console.log(getDecimalValue([1, 0, 1]) == 5)
console.log(getDecimalValue([1, 1, 1, 0, 0, 1]) == 57)
console.log(getDecimalValue([1, 1, 1, 0, 0, 1, 1]) == 115)
console.log(getDecimalValue([0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0]) == 7392)
console.log(getDecimalValue([1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]) == 18880)
This was my code using the while loop
let sum = 0
while ((i = head.shift()) !== undefined) {
sum += (i * Math.pow(2, head.length))
console.log(i * Math.pow(2, head.length))
}
return sum
You could reduce the array and multiply last value and add the actual value.
const getDecimalValue = array => array.reduce((r, v) => r * 2 + v, 0);
console.log(getDecimalValue([1, 0, 1]) == 5)
console.log(getDecimalValue([1, 1, 1, 0, 0, 1]) == 57)
console.log(getDecimalValue([1, 1, 1, 0, 0, 1, 1]) == 115)
console.log(getDecimalValue([0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0]) == 7392)
console.log(getDecimalValue([1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]) == 18880)
The problem is that you are mutating the array as reduce is iterating over it. This changes the array underneath, so the operation gets out of sync with the underlying values. Here is a (somewhat loose) illustration of how reduce normally walks the array:
arr = [1, 2, 3]
^ ^ ^
| | |
iteration 1 -- | |
iteration 2 ----- |
iteration 3 --------
Here is what happens when you modify it each iteration:
//start
arr = [1, 2, 3]
//at iteration 1
[2, 3] _
^
|
iteration 1 --
//at iteration 2
[3] _ _
^
|
iteration 2 -----
//at iteration 3
[] _ _
^
|
iteration 3 ------
Instead, the straight forward way to get your functionality is to make each item a power of 2 that is equal to the reverse index of the item
//index: 0 1 2 3
arr = [1, 0, 1, 0]
//reverse index: 3 2 1 0
And conveniently, to get that, you simply need to subtract 1 from the array length (because indeces are 0-based and length = 1 only has index = 0) and then subtract the normal index. This gives you the powers of two each value represents:
//reverse index: 3 2 1 0
arr = [1, 0, 1, 0]
//power of 2: 8 4 2 1
//multiply and sum: 8 + 0 + 2 + 0 = 10
And here is the code using reduce:
const getDecimalValue = function (head) {
let total = head.reduce(
(sum, item, index, array) =>
sum + item * Math.pow(2, (array.length - index - 1)),
// reverse index ^^^^^^^^^^^^^^^^^^^^^^^^
0
)
return total
}
console.log(getDecimalValue([1, 0, 1]) == 5)
console.log(getDecimalValue([1, 1, 1, 0, 0, 1]) == 57)
console.log(getDecimalValue([1, 1, 1, 0, 0, 1, 1]) == 115)
console.log(getDecimalValue([0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0]) == 7392)
console.log(getDecimalValue([1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0]) == 18880)
I find it way more readable using the following function with Array.prototype.reduceRight().
function binaryToDecimal(arr) {
let decimal = 0;
arr.reduceRight((base, binaryNum) => {
if (binaryNum === 1) {
decimal += base;
}
base = base * 2;
return base;
}, 1);
return decimal;
}
binaryToDecimal([1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0])===3462
Related
I have an array containing numbers, in that array there're some numbers that occur many times in consecutive order one after the other, and I want to compress those repeated numbers into a very specific format 'Number*Times' to reduce the size of the array:
input: [0, 1, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 5, 6, 0]
---------^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^--------
output: [0, 1, 2,'0x3', 3, 2, '0x6', 5, 6, 0]
let array = [0, 1, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 5, 6, 0];
let string = array.toString();
let string_compressed = string.replace(/(\d+,)(\1)+/g, (x) => {
return "Number*" + x.split(",").length + ",";
});
let array_compressed = string_compressed
.split(",")
.map((x) => (isNaN(Number(x)) ? x : Number(x)));
console.log(array_compressed); //[0, 1, 2, 'Number*4', 3, 2, 'Number*7', 5, 6, 0]
I don't know how to get the number that repeated so I put Number instead!
I used regex to solve it, I know if you think to solve problem with regex, they become two problems!
BUT Guys I'm sure this isn't the efficient way to solve this problem, And there're other ways to solve it!
what do you suggest if you want to solve this problem?
Because your regex to find how many numbers repeat already only matches numbers that are in consecutive order in your array, you can simply just take the first index of the x.split(",") array and return that.
Edit:
Also, as #qrsngky out, your x.split(",").length wasn't actually the right length, because when you split it by comma, there is a null character at the end:
let array = [0, 1, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 5, 6, 0];
let string = array.toString();
let string_compressed = string.replace(/(\d+,)(\1)+/g, (x) => {
console.log(x.split(","));
return "Number*" + x.split(",").length + ",";
});
let array_compressed = string_compressed
.split(",")
.map((x) => (isNaN(Number(x)) ? x : Number(x)));
console.log(array_compressed);
Sorry for missing that, and props to the comments! I just fixed it by subtracting one from the length.
Edit 2:
For edge cases, we can just add a comma and use slice.
I attached the complete fixed code snippet below:
let array = [0, 1, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 5, 6, 0, 0, 0];
let string = array.toString() + ",";
let string_compressed = string.replace(/(\d+,)(\1)+/g, (x) => {
return x.split(",")[0] + "*" + (x.split(",").length - 1) + ",";
});
let array_compressed = string_compressed
.slice(0, -1)
.split(",")
.map((x) => (isNaN(Number(x)) ? x : Number(x)));
console.log(array_compressed);
Assume your original array consists of none other than numbers.
Non-regex approach: based on a for loop and counting how many repetitions had been encountered.
let array = [0, 1, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 5, 6, 0]
let buffer = [];
let currentNum = array[0], count = 1;
for (let i = 1; i < array.length; i++) {
if (array[i] === currentNum) {
++count;
} else {
buffer.push( count === 1 ? currentNum : (currentNum + 'x' + count) );
currentNum = array[i];
count = 1;
}
}
//don't forget the last number
if(currentNum !== undefined) buffer.push( count === 1 ? currentNum : (currentNum + 'x' + count) );
console.log(buffer);
The if(currentNum !== undefined) check is only useful in case it's an empty array.
Another example of how not to do it with string manipulation by coding what you want done:
function packArray(array) {
var packed = [];
for( var i = 0; i < array.length; i=j) {
var entry = array[i];
for(var j = i+1; array[j] === entry && j<array.length; ++j);
packed.push( j > i+1 ? `${entry}x${j-i}` : entry);
}
return packed;
}
console.log( packArray([0, 1, 2, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0, 5, 6, 0]))
You could substitute let for var except for var j which should remain the same to allow access to j outside the nested for loop.
I have an array of arrays that looks like this:
var grid = [
[0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0],
[0,0,0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0,0,0,0]
];
I want to count the number of 1s in a row until we hit a zero. And then if we hit a 1 again, count the sequential occurrences again.
So for grid[0], it should return 9.
For grid[1], it should return [3,3].
grid[2], [2,1,1,2].
As an extra layer of fun, I'm also trying to do this for "columns." I.e. How many times "1" appears in the 1st column, which would consist of grid[0][1], grid[1][1], grid[2][1] and so on. If there's a better way to organize the data to achieve this, I'm open to suggestions as in total I have 25 rows/arrays within the array.
I'm unsure if there is a way that doesn't involve looping through the data over and over. Currently I'm doing this:
var guides = [];
for ( var i = 0; i < grid.length; i++ ) {
var row = grid[i];
var chunks = [];
var count = 0;
for (var j = 0; j < row.length; j++ ) {
if ( j === 1 ) {
count++;
} else {
chunks.push(count);
count = 0;
}
}
guides.push(chunks);
}
Here's one example how you can use recursion to check those number 1 strikes. For the columns, you can transpose the original Grid and re-use the same function.
var grid = [
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
]
// Returns the array of 1 strikes in the given arr
const occurrenceOfOne = (arr, strike = 0, final = []) => {
// Recursive calls until the array is empty
if (arr.length === 0) {
return strike === 0 ? final : final.concat([strike])
}
// If the item is 0, the strike ends.
if (arr[0] === 0) {
if (strike !== 0) {
return occurrenceOfOne(arr.slice(1), 0, final.concat([strike]))
}
}
// If the item is 1, the strike continues
if (arr[0] === 1) {
return occurrenceOfOne(arr.slice(1), strike + 1, final)
}
// Default value 0 found and strike is 0 as well.
return occurrenceOfOne(arr.slice(1), 0, final)
}
// Copied transpose function from this gist:
// https://gist.github.com/femto113/1784503
function transpose(a) {
return a[0].map((_, c) => a.map((r) => r[c]))
}
console.log(occurrenceOfOne(grid[0]))
console.log(occurrenceOfOne(grid[1]))
console.log(occurrenceOfOne(grid[2]))
const transposedGrid = transpose(grid)
console.log(occurrenceOfOne(transposedGrid[6]))
console.log(occurrenceOfOne(transposedGrid[7]))
I am counting the Consecutive Zeros and treating each one as separate block and pushing it to array blocks like this [3,1,1] , what i need to do is to push the position of first element in each block in another array like this [2,13,15]
var A = [1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0];
var N = A.length
function Avaiblocks(A,N,X){
var counter = 0;
var Blocks = [];
var POS =[];
for(var i = 0; i < A.length; i++) {
if(A[i] === 0){
counter++;
POS.push(i)
} else {
if (counter !== 0) {
Blocks.push(counter)
counter = 0;
}
}
}
if (counter !== 0){
Blocks.push(counter)
}
return POS;
let arr = [1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0]
let result = Array.from(arr.join("").matchAll(/0+/g),m=>m['index'])
console.log(result)
Iterate the given array with Array.prototype.reduce.
Pass the reducer function and an additional empty array as collector to it.
For each iteration the reduce function has access to its collector (here list), the currently processed item, the current idx(index) and the processed arr(array) itself.
The next iteration treats the return value of the former/current iteration as collector/list again.
Thus, for the OP's example, one needs to return an array which, for each iteration, either concats the current idx in case the first 0 value of a sequence of consecutive zeros was found (hence the condition ... (item === 0 && arr[idx - 1] !== 0)) or does concat an empty array and returns this result ...
function collectIndexOfFirstOneOfConsecutiveZeros(list, item, idx, arr) {
return list.concat(
(item === 0 && arr[idx - 1] !== 0)
? idx
: []
);
}
const sampleList = [1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0];
console.log(
sampleList.reduce(collectIndexOfFirstOneOfConsecutiveZeros, [])
);
// ... or directly like ...
console.log(
[1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0]
.reduce((list, item, idx, arr) =>
list.concat((item === 0 && arr[idx - 1] !== 0) ? idx : []),
[]
)
);
I need to write a small map/reduce function that should return 1 if there are at least 4 continuous 0 in an array, otherwise it should return something different than 1.
Example:
[6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1] => 1
[6, 7, 5, 0, 0, 0, 3, 4,0, 0, 0, 0, 0, 1] => 1
[5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1] => something # 1
This is my algorithm:
[6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.map((i) => i === 0 ? 1 : 0)
.reduce((prev, i) => {
if (i !== 0) {
return prev + i;
} else {
if (prev >= 4) {
return 4;
}
return 0;
}
}, 0);
The .map method will mark 0 as 1 and non-0 as zero. So [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1] becomes [0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0]. Then the .reduce method will collect the number of zeroes.
If the current element is 1 (meaning zero in the input array) it returns the previous value plus the current value (1). This means it represents the number of zeroes. If the current item is 0 (meaning not 0 in the input array), it resets prev to 0 when it is less than 4, otherwise it carries on.
At the end, if the value is 4, it means that there are at least 4 continuous 0.
The algorithm seems to run, but not meet the requirement. It requires returning 1 if the input has 4 consecutive 0, otherwise it should return any non-zero number.
Do you think of any way to do this?
Here's an ES6 function that uses a 4-bit representation, where each 1-bit represents a non-zero value in the input array.
Only the most recent 4 bits are retained: each iteration the next bit is shifted in from the right, while the left most is dropped, always keeping 4 bits. If the outcome becomes zero at any time, the loop stops:
function has0000(arr) {
let c = 1;
return +!arr.every( n => c = (c*2+!!n) & 0xF );
}
console.log(has0000([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]));
console.log(has0000([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 1]));
Your code
Although the algorithm for counting zeroes is correct, the return value is not a 1 when needed, and vice versa.
The return value is whatever is the last returned value in the reduce callback, so it represents a count, which could be any non-negative number.
Even when there are 4 consecutive zeroes found, this count can start from zero again, and so you lose the knowledge that you actually had a match.
The main fix to this is to swap the if statements, and do the if (prev >= 4) first. That way you will keep the count at 4 once it reaches that, no matter what follows. It will never get reset to 0 anymore.
The second fix is to detect the final return is exactly 4 and transform that to a one. This must be done in a way that 4 is the only value that gets transformed to a 1, nothing else. This you could do by dividing by 4.
So without changing anything else, with minimal changes, this is the corrected code:
var res = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.map((i) => i === 0 ? 1 : 0)
.reduce((prev, i) => {
if (prev >= 4) {
return 4;
} else if (i !== 0) {
return prev + i;
} else {
return 0;
}
}, 0) / 4;
console.log(res);
The downside of sticking to only map and reduce is that you cannot exit before having gone through all input elements. This is a pity, because once you have found 4 zeroes, there really is no reason to keep going. The result is already final at that moment.
And here is your corrected code in a more condensed format, where I also left out the map as it is a trivial thing to incorporate in the reduce callback:
var res = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.reduce((prev, i) => prev >= 4 ? 4 : i ? 0 : prev + 1, 0)
/ 4;
console.log(res);
You could use Array#some and a this object for counting.
Array#some allows to end the iteration if the right count is found, as opposite of Array#reduce, which iterates all elements.
function isContinuous(array) {
return +array.some(function (a, i) {
if (a === 0) {
this.count++;
return this.count === 4;
}
this.count = 0;
}, { count: 0 });
}
console.log(isContinuous([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]));
console.log(isContinuous([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]));
console.log(isContinuous([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]));
In general, your implementation looks good to me, except the small nuance: you should specify -3 as an initial value for the reduce function (and modify its body accordingly in order to satisfy your requirements).
Below provided the modified version of your implementation, which returns 1 in case if there are present at least 4 consecutive zeroes, and otherwise returns one of the following values: -3, -2, -1 or 0
var result = [6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]
.map((i) => i === 0 ? 1 : 0)
.reduce((prev, i) => {
if (prev === 1) {
return 1;
}
if(i === 0) {
return -3;
}
return prev + (i === 1);
}, -3);
console.log(result)
Using .map and .reduce to meet your requirement:
//the following returns 1 if there is at least 4 consecutive 0's
//otherwise returns 0, 2, 4 or 6:
function cons4zeroes(arr) {
return arr
.map((i) => i ^ 0 ? 0 : 2)
.reduce((p, i) => p ^ 1 ?
i ^ 0 ?
(p + i) % 7 :
0 :
1,
0)
}
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]), " => something # 1"
);
This solution builds on your logic, but maps each 0 to 2 instead of 1, reserving 1 for when 4 consecutive 0's have been found. Then, summing the consecutive 2's, resetting to 0 if a non-zero value is found before the 4th zero, taking the sum modulo 7 to convert the sum to 1 when 4 zeroes are found (4 × 2 = 1 mod 7).
Alternative without .map():
//the following returns 1 if there is at least 4 consecutive 0's
//otherwise returns 0, 2, 4 or 6:
function cons4zeroes(arr) {
return arr.reduce((p, i) => p ^ 1 ?
i ^ 0 ? 0 : (p + 2) % 7 : 1, 0)
}
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 0, 3, 4, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([6, 7, 5, 0, 0, 0, 3, 4, 0, 0, 0, 0, 0, 1]), " => 1"
);
console.log(
cons4zeroes([5, 0, 0, 0, 3, 4, 0, 0, 8, 0, 0, 1]), " => something # 1"
);
I'm trying to create something that can detect if multiple values are in a array, this is what I tried to use, but that always returns true
row7 = [1, 1, 1, 1, 1, 1, 1, 1];
var field = [row0, row1, row2, row3, row4, row5, row6, row7];
console.log(field[0,1]); // Get the first item in the array
var needles = [1, 1, 1, 1]
console.log(needles)
var haystack = field[0,row7]
console.log(haystack)
console.log(contains(haystack,needles));
function contains(haystack, needles) {
return needles.map(function (needle) {
return haystack.indexOf(needle);
}).indexOf(-1) == -1;
}
Case true:
row 7 = [1, 1, 1, 1, 1, 1, 1, 1]
Checked for (four in a row):
needles = [1, 1, 1, 1]
And false:
row7 = [1, 1, 0, 1, 0, 0, 0, 1]
Edit:
Entire array:
/* Array containing the playing field 8 x 8
C0 C1 C2 C3 C4 C5 C6 C7
R0[0][0][0][0][0][0][0][0]
R1[0][0][0][0][0][0][0][0]
R2[0][0][0][0][0][0][0][0]
R3[0][0][0][0][0][0][0][0]
R4[0][0][0][0][0][0][0][0]
R5[0][0][0][0][0][0][0][0]
R6[0][0][0][0][0][0][0][0]
R7[0][0][0][0][0][0][0][0]
*\
var row0 = [0, 0, 0, 0, 0, 0, 0, 0],
row1 = [0, 0, 0, 0, 0, 0, 0, 0],
row2 = [0, 0, 0, 0, 0, 0, 0, 0],
row3 = [0, 0, 0, 0, 0, 0, 0, 0],
row4 = [0, 0, 0, 0, 0, 0, 0, 0],
row5 = [0, 0, 0, 0, 0, 0, 0, 0],
row6 = [0, 0, 0, 0, 0, 0, 0, 0],
row7 = [0, 0, 0, 0, 0, 0, 0, 0];
var field = [row0, row1, row2, row3, row4, row5, row6, row7];
Is there any way to achieve this using plain JS?
Thanks in advance.
I think that the easiest (not the most efficient) solution is to firstly convert both arrays into a string and then check if one string is a sub-string of another one:
function contains(array1, array2) {
if(array1 == null || array2 == null)
return false;
return array1.toString().contains(array2.toString());
}
var row7 = [1, 1, 1, 1, 1, 1, 1, 1];
var needles = [1, 1, 1, 1];
contains(row7, needles); //returns true
row7 = [1, 1, 0, 1, 0, 0, 0, 1]
contains(row7, needles); //returns false
UPDATE:
I've just realized that contains function is not supported by all browsers. So it is better to use indexOf i.e. return array1.toString().indexOf(array2.toString()) != -1;
change your contains method to
function contains(haystack, needles)
{
return haystack.join(",").indexOf(needle.join(",")) != -1;
}
Explanation -
do a join of needle to get "1,1,1,1"
and the join of haystack to get "1,1,1,1,1,1,1,1"
when you do indexOf you will get 0 since needles is contained in haystack
You can use Array.prototype.every() with an offset for the search.
function check(haystack, needles) {
var offset = 0;
while (offset + needles.length <= haystack.length) {
if (needles.every(function (a, i) {
return haystack[i + offset] === a;
})) {
return true;
}
offset++;
}
return false;
}
var row7 = [1, 1, 1, 1, 1, 1, 1, 1],
row8 = [1, 1, 0, 1, 0, 0, 0, 1],
needles = [1, 1, 1, 1];
document.write(check(row7, needles) + '<br>');
document.write(check(row8, needles) + '<br>');