Can't we reassign array values inside forEach? - javascript

The problem statement is, i should replace the any digit below 5 with 0 and any digit 5 and above with 1.
I am trying to reassign values, but it is not affecting, Why?
function fakeBinary(n) {
let numbersArr = n.split('');
numbersArr.forEach(num => {
if(Number(num) < 5) {
num = '0';
} else if(Number(num) >= 5) {
num = '1';
}
});
return numbersArr.join('');
}
console.log(fakeBinary('3457'));
I except the output of 0011, but the actual output is 3457.

forEach used like that won't do anything - use map instead.
let numbersArr = n.split("").map(num => {
if (Number(num) > 5) {
num = "0";
} else if (Number(num) <= 5) {
num = "1";
}
return num;
});
return numbersArr.join("");
Note that to produce your desired output, you need to change your conditions slightly:
if (Number(num) >= 5) {
num = "1";
} else {
num = "0";
}

forEach doesn't bring the element's reference for primitive values but rather brings a copy of the value in your case. You can easily access that manually through the index, though:
function fakeBinary(n) {
let numbersArr = n.split('');
numbersArr.forEach((num, i) => {
// ^--- note that `i` is brought and used below to access the element at index [i].
if(Number(num) < 5) {
numbersArr[i] = '0';
} else if(Number(num) >= 5) {
numbersArr[i] = '1';
}
});
return numbersArr.join('');
}
console.log(fakeBinary('3457'));
Please note that you may also use other prototypes, I just tried to stay as close as possible to your solution, you may also want to use map or, even (not appropriate, though) reduce or even a regular for loop.

To do it with forEach, you would need to use the additional arguments to reference the array and the index.
function fakeBinary(n) {
let numbersArr = n.split('');
numbersArr.forEach((num, index, arr) => {
if(Number(num) < 5) {
arr[index] = '0';
} else if(Number(num) >= 5) {
arr[index] = '1';
}
});
return numbersArr.join('');
}
console.log(fakeBinary('3457'));
But forEach is not the correct way to return a new array. You want to use map()
function fakeBinary(n) {
return n
.split('')
.map(num => (Number(num) < 5) ? '0' : '1')
.join('');
}
console.log(fakeBinary('3457'));

A shorter version using Array.from()
const fakeBinary = (str) => Array.from(str, n => +(+n >= 5)).join('');
console.log(fakeBinary('3457'));

Related

how to loop through two indexes in javascript

why it's giving me the first index and i need the ouput below which is "StOp mAkInG SpOnGeBoB MeMeS!"
function spongeMeme(sentence) {
let x = sentence.split("");
for(let i = 0; i < sentence.length;i+=2){
return x[i].toUpperCase()
}
}
console.log(spongeMeme("stop Making spongebob Memes!")) // S
// output : StOp mAkInG SpOnGeBoB MeMeS!"
You're returning just the first letter of what is stored inside of x.
Try to debug it and see what is going on.
The way I'd do it is like so:
function spongeMeme(sentence) {
return sentence.split("")
.map((s, i) => (i % 2 != 0 ? s.toUpperCase() : s))
.join("");
}
console.log(spongeMeme("stop Making spongebob Memes!"));
Try this:
function spongeMeme(sentence) {
let x = sentence.split("");
let newSentence = '';
for(let i = 0; i < sentence.length;i++){
// If the letter is even
if(i % 2 ==0) {
newSentence += x[i].toUpperCase();
}
// If the letter is odd
else {
newSentence += x[i].toLowerCase();
}
}
return newSentence
}
console.log(spongeMeme("stop Making spongebob Memes!"))
First of all you can only return once per function, so you have to create a variable and store all the new letter inside, and, at the end, return this variable.
You're immediately returning on the first iteration of your for loop, hence the reason you're only getting back a single letter.
Let's start by fixing your existing code:
function spongeMeme(sentence) {
let x = sentence.split("");
for(let i = 0; i < sentence.length; i+=2) {
// You can't return here. Update the value instead.
return x[i].toUpperCase()
}
// Join your sentence back together before returning.
return x.join("");
}
console.log(spongeMeme("stop Making spongebob Memes!"))
That'll work, but we can clean it up. How? By making it more declarative with map.
function spongeMeme(sentence) {
return sentence
.split('') // convert sentence to an array
.map((char, i) => { // update every other value
return (i % 2 === 0) ? char.toUpperCase() : char.toLowerCase();
})
.join(''); // convert back to a string
}
You can also do that:
const spongeMeme = (()=>
{
const UpperLower = [ (l)=>l.toUpperCase(), (l)=>l.toLowerCase() ]
return (sentence) =>
{
let i = -1, s = ''
for (l of sentence) s += UpperLower[i=++i&1](l)
return s
}
})()
console.log(spongeMeme("stop Making spongebob Memes!"))
.as-console-wrapper {max-height: 100% !important;top: 0;}
.as-console-row::after {display: none !important;}
OR
function spongeMemez(sentence)
{
let t = false, s = ''
for (l of sentence) s += (t=!t) ? l.toUpperCase() : l.toLowerCase()
return s
}
but I think this version is slower, because using an index should be faster than a ternary expression

Javascript. I am trying to use .findIndex() but I get the following message: TypeError: a is not a function

I have ‘array magazine’ and ‘string ransomNote’. And want to access an element of the array based on an element from the string.
This is what I am trying: magazine.findIndex(ransomNote[i])
var canConstruct = function(ransomNote, magazine){
magazine = magazine.split('');
//console.log(magazine);
for(let i = 0; i < ransomNote.length; i++){
if (magazine.includes(ransomNote[i])){
element_to_erase = magazine.findIndex(ransomNote[i]);
magazine = magazine.splice(element_to_erase , 1);
//console.log(magazine);
continue;
} else {
return false;
}
}
return true;
};
console.log(canConstruct('aa', 'aab'));
findIndex takes a function as an argument, and you are passing it a string
you need to do
magazine.findIndex((magazineString) => magazineString === ransomNote[i])
Or just use indexOf as its pointed in the comments, and probably validate if that returns something other than -1 (indexOf) or undefined (findIndex) in either case.
Instead of converting the input string magazine to an array, you can use magazine.search directly -
const removeCharAt = (str = 0, pos = 0) =>
str.substr(0, pos) + str.substr(pos + 1)
const canConstruct = (ransomNote = "", magazine = "") => {
for (const char of ransomNote) {
const pos = magazine.search(char)
if (pos >= 0)
magazine = removeCharAt(magazine)
else
return false
}
return true
}
console.log(canConstruct('aa', 'aab'))
// true
console.log(canConstruct('az', 'aab'))
// false
console.log(canConstruct('stay inside', 'diet coke on sale this sunday'))
// true
There is another issue. While splice, u don't need to reassign. Splice modify the same array.
You can simplify using array.every.
var canConstruct = function (ransomNote, magazine) {
magazine = magazine.split("");
return ransomNote.split("").every((rChar) => {
const rIndex = magazine.indexOf(rChar);
if (rIndex !== -1) {
magazine.splice(rIndex, 1);
return true;
}
});
};
console.log(canConstruct("aa", "aab"));
console.log(canConstruct("az", "aab"));

Return String as Sorted Blocks - Codewars Challenge - JavaScript

I've been trying to solve this codewars challenge. The idea is to return the string, rearranged according to its hierarchy, or separated into chunks according to the repeating character.
You will receive a string consisting of lowercase letters, uppercase letters and digits as input. Your task is to return this string as blocks separated by dashes ("-"). The elements of a block should be sorted with respect to the hierarchy listed below, and each block cannot contain multiple instances of the same character.
The hierarchy is:
lowercase letters (a - z), in alphabetic order
uppercase letters (A - Z), in alphabetic order
digits (0 - 9), in ascending order
Examples
"21AxBz" -> "xzAB12"
since input does not contain repeating characters, you only need 1 block
"abacad" -> "abcd-a-a"
character "a" repeats 3 times, thus 3 blocks are needed
"" -> ""
an empty input should result in an empty output
What I've tried actually works for the given test cases:
describe("Sample tests", () => {
it("Tests", () => {
assert.equal(blocks("21AxBz"), "xzAB12");
assert.equal(blocks("abacad"), "abcd-a-a");
assert.equal(blocks(""), "");
});
});
But fails when there are any repeating characters, besides in the test cases:
function repeatingChar(str){
const result = [];
const strArr = str.toLowerCase().split("").sort().join("").match(/(.)\1+/g);
if (strArr != null) {
strArr.forEach((elem) => {
result.push(elem[0]);
});
}
return result;
}
function blocks(s) {
if (s.length === 0) {
return '';
}
//if string does not contain repeating characters
if (!/(.).*\1/.test(s) === true) {
let lower = s.match(/[a-z]/g).join('');
let upper = s.match(/[A-Z]/g).join('');
let numbers = s.match(/[\d]/g).sort().join('');
return lower + upper + numbers;
}
//if string contains repeating characters
if (!/(.).*\1/.test(s) === false) {
let repeatChar = (repeatingChar(s)[0]);
let repeatRegex = new RegExp(repeatingChar(s)[0]);
let repeatCount = s.match(/[repeatRegex]/gi).length;
let nonAChars = s.match(/[^a]/gi).join('');
function getPosition(string, subString, index) {
return s.split(repeatChar, index).join(repeatChar).length;
}
let index = getPosition(s, repeatChar, 2);
// console.log('indexxxxx', index);
return s.slice(0, index) + nonAChars.slice(1) + ('-' + repeatChar).repeat(repeatCount - 1);
}
}
console.log(blocks("abacad"));
And actually, I'm not sure what's wrong with it, because I don't know how to unlock any other tests on Codewars.
You can see that what I'm trying to do, is find the repeating character, get all characters that are not the repeating character, and slice the string from starting point up until the 2 instance of the repeating character, and then add on the remaining repeating characters at the end, separated by dashes.
Any other suggestions for how to do this?
For funzies, here's how I would have approached the problem:
const isLower = new RegExp('[a-z]');
const isUpper = new RegExp('[A-Z]');
const isDigit = new RegExp('[0-9]');
const isDigitOrUpper = new RegExp('[0-9A-Z]');
const isDigitOrLower = new RegExp('[0-9a-z]');
const isLowerOrUpper = new RegExp('[a-zA-Z]');
function lowerUpperNumber(a, b)
{
if(isLower.test(a) && isDigitOrUpper.test(b))
{
return -1;
}
else if(isUpper.test(a) && isDigitOrLower.test(b))
{
if(isDigit.test(b))
{
return -1;
}
else if(isLower.test(b))
{
return 1;
}
}
else if(isDigit.test(a) && isLowerOrUpper.test(b))
{
return 1;
}
else if(a > b)
{
return 1;
}
else if(a < b)
{
return -1;
}
return 0;
}
function makeBlocks(input)
{
let sortedInput = input.split('');
sortedInput.sort(lowerUpperNumber);
let output = '';
let blocks = [];
for(let c of sortedInput)
{
let inserted = false;
for(let block of blocks)
{
if(block.indexOf(c) === -1)
{
inserted = true;
block.push(c);
break;
}
}
if(!inserted)
{
blocks.push([c]);
}
}
output = blocks.map(block => block.join('')).join('-');
return output;
}
console.log(makeBlocks('21AxBz'));
console.log(makeBlocks('abacad'));
console.log(makeBlocks('Any9Old4String22With7Numbers'));
console.log(makeBlocks(''));
The first obvious error I can see is let repeatCount = s.match(/[repeatRegex]/gi).length;. What you really want to do is:
let repeatRegex = new RegExp(repeatingChar(s)[0], 'g');
let repeatCount = s.match(repeatRegex).length;
The next is that you only look at one of the repeating characters, and not all of them, so you won't get blocks of the correct form, so you'll need to loop over them.
let repeatedChars = repeatingChar(s);
for(let c of repeatedChars)
{
//figure out blocks
}
When you're building the block, you've decided to focus on everything that's not "a". I'm guessing that's not what you originally wrote, but some debugging code, to work on that one sample input.
If I understand your desire correctly, you want to take all the non-repeating characters and smoosh them together, then take the first instance of the first repeating character and stuff that on the front and then cram the remaining instances of the repeating character on the back, separated by -.
The problem here is that the first repeating character might not be the one that should be first in the result. Essentially you got lucky with the repeating character being a.
Fixing up your code, I would create an array and build the blocks up individually, then join them all together at the end.
let repeatedChars = repeatingChar(s);
let blocks = []
for(let c of repeatedChars)
{
let repeatRegex = new RegExp(c, 'g');
let repeatCount = s.match(repeatRegex).length;
for(let i = 1; i <= repeatCount; i++)
{
if(blocks.length < i)
{
let newBlock = [c];
blocks.push(newBlock);
}
else
{
block[i - 1].push(c);
}
}
}
let tailBlocks = blocks.map(block => block.join('')).join('-');
However, this leaves me with a problem of how to build the final string with the non-repeating characters included, all in the right order.
So, to start with, let's make the initial string. To do so we'll need a custom sort function (sorry, it's pretty verbose. If only we could use regular ASCII ordering):
function lowerUpperNumber(a, b)
{
if(a.match(/[a-z]/) && b.match(/[A-Z0-9]/))
{
return -1;
}
else if(a.match(/[A-Z]/) && (b.match(/[0-9]/) || b.match(/[a-z]/)))
{
if(b.match(/[0-9]/))
{
return -1;
}
else if(b.match(/[a-z]/))
{
return 1;
}
}
else if(a.match(/[0-9]/) && b.match(/[a-zA-Z]/))
{
return 1;
}
else if(a > b)
{
return 1;
}
else if(a < b)
{
return -1;
}
return 0;
}
Then create the head of the final output:
let firstBlock = [...(new Set(s))].sort(lowerUpperNumber);
The Set creates a set of unique elements, i.e. no repeats.
Because we've created the head string, when creating the blocks of repeated characters, we'll need one fewer than the above loop gives us, so we'll be using s.match(repeatRegex).length-1.
I get the desire to short circuit the complicated bit and return quickly when there are no repeated characters, but I'm going to remove that bit for brevity, and also I don't want to deal with undefined values (for example try '123' as your input).
Let's put it all together:
function lowerUpperNumber(a, b)
{
if(a.match(/[a-z]/) && b.match(/[A-Z0-9]/))
{
return -1;
}
else if(a.match(/[A-Z]/) && (b.match(/[0-9]/) || b.match(/[a-z]/)))
{
if(b.match(/[0-9]/))
{
return -1;
}
else if(b.match(/[a-z]/))
{
return 1;
}
}
else if(a.match(/[0-9]/) && b.match(/[a-zA-Z]/))
{
return 1;
}
else if(a > b)
{
return 1;
}
else if(a < b)
{
return -1;
}
return 0;
}
function makeBlocks(s)
{
if (s.length === 0)
{
return '';
}
let firstBlock = [...(new Set(s))].sort(lowerUpperNumber);
let firstString = firstBlock.join('');
let blocks = [];
for(let c of firstString)
{
let repeatRegex = new RegExp(c, 'g');
let repeatCount = s.match(repeatRegex).length - 1;
for(let i = 1; i <= repeatCount; i++)
{
if(blocks.length < i)
{
let newBlock = [c];
blocks.push(newBlock);
}
else
{
blocks[i - 1].push(c);
}
}
}
blocks.unshift(firstBlock);
return blocks.map(block => block.join('')).join('-');
}
console.log(makeBlocks('21AxBz'));
console.log(makeBlocks('abacad'));
console.log(makeBlocks('Any9Old4String22With7Numbers'));
console.log(makeBlocks(''));
You'll see I've not bothered generating the characters that repeat, because I can just skip the ones that don't.

Why are for...of and for loop behaving differently?

Just was performing simple task in JS which was to take integer as an input, divide it into single digits and multiply them ignoring all zeros in it.
I have solved it but had some troubles which were simply solved by changing the loop. I am just curious why the code did not work properly with the for loop and started to work as I it for for of loop. I could not find out the answer by my self. If somebody could tell where I am wrong.
First one works as intended, second one always returns 1.
function digitsMultip1(data) {
var stringg = data.toString().split("", data.lenght);
for (let elements of stringg) {
if (elements != 0) {
sum = parseInt(elements) * sum
} else {
continue
};
}
return sum;
}
console.log(digitsMultip1(12035))
function digitsMultip2(data) {
var sum = 1;
var stringg = data.toString().split("", data.lenght);
for (var i = 0; i > stringg.lenght; i++) {
if (stringg[i] != 0) {
sum = parseInt(stringg[i]) * sum
} else {
continue
};
}
return sum;
}
console.log(digitsMultip2(12035))
There is no big difference. for..of works in newer browsers
The for...of statement creates a loop iterating over iterable objects, including: built-in String, Array, Array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables. It invokes a custom iteration hook with statements to be executed for the value of each distinct property of the object.
Several typos
length spelled wrong
> (greater than) should be < (less than) in your for loop
Now they both work
function digitsMultip1(data) {
var sum=1, stringg = data.toString().split("");
for (let elements of stringg) {
if (elements != 0) {
sum *= parseInt(elements)
} else {
continue
};
}
return sum;
}
console.log(digitsMultip1(12035))
function digitsMultip2(data) {
var sum = 1, stringg = data.toString().split("");
for (var i = 0; i < stringg.length; i++) {
if (stringg[i] != 0) {
sum *= parseInt(stringg[i])
} else {
continue
};
}
return sum;
}
console.log(digitsMultip2(12035))
You might want to look at reduce
const reducer = (accumulator, currentValue) => {
currentValue = +currentValue || 1; return accumulator *= currentValue
}
console.log(String(12035).split("").reduce(reducer,1));

What's wrong with this Codility Answer?

The question was to find the largest binary gap in a number, and while this worked in my IDE, Codility didn't accept it. Any thoughts?
const biGap = (number) => {
const binary = number.toString(2)
const re = /1(0+)1/g;
const found = binary.match(re)
let counter = 0
found.map((match) => {
if (match.length > counter) {
counter = match.length
}
})
return (counter - 2);
}
console.log(biGap(1041));
The main problem with your code is that binary.match(re) won't return overlapping matches. So if binary = "1010000001001", it will return ["101", "1001"], which is missing the long gap 10000001 between them.
You can solve this by changing the regexp to
const re = /0+1/g;
Then you should return counter - 1 instead of counter - 2.
You don't need to put 1 on both sides of the 0+ because number.toString(2) will never include leading zeroes, so there's always a 1 to the left of any string of zeroes, and it's not necessary to match it explicitly.
If you also want to include the binary gap in the low order bits of the number, you can change the regexp to simply:
const re = /0+/g;
Then you don't need to subtract anything from counter when returning.
const biGap = (number) => {
const binary = number.toString(2)
const re = /0+1/g;
const found = binary.match(re)
let counter = 0
found.map((match) => {
if (match.length > counter) {
counter = match.length
}
})
return (counter - 1);
}
console.log(biGap(1041));
console.log(biGap(parseInt("1010000001001", 2))); // Your code returns 2
function binGap(N) {
var max=0;
var count=0;
var binary = Number(N).toString(2);;
Array.prototype.forEach.call(binary,function(i) {
if(i == 0) {
count++;
} else {
if(count > max) {
max = count;
}
count = 0;
}
});
return max;
}
for starters make sure that the regex returns all the right candidates
map operator is used the wrong way. Reduce is the way to use in your case
you should not subtract 2 from the counter at return, instead do that in the reduce callback
don't console.log
And a final thought, why convert the number into string? why not use modulo 2? it's way simpler and efficient. (think of how much resources a regex requires)
a possible solution may be this
solution(N) {
while(N && N%2 === 0) {
N = N>>1
}
let counter = 0;
let tempCounter = 0;
let n=N;
while(n) {
if(n%2 === 1) {
counter = Math.max(counter, tempCounter);
tempCounter=0;
} else {
tempCounter = tempCounter + 1;
}
n = n>>1;
}
return counter;
}

Categories