using reduce to compose an array of cumulative strings - javascript

I want to return something like this ['4', '42','420'] for a given number: 420
I am using reduce to accumulate the number and using the index to concat the number in the index before. First two iterations are working fine, but in the third one I am having trouble thinking how to deal with the result since it is undefined => [ '4', '42', '4,42undefined' ]
Any thoughts?
function createArrayOfTiers(num) {
const onArray = String(num).split('')
onArray.reduce((acc, curr, index) => {
if (!index) return curr
const newAcc = [...acc, acc + curr[index -1]]
return newAcc
},[])
}
createArrayOfTiers(420)

If you want to collect the incremental digits of a number, I think a clearer approach would be to use .map (or Array.from) instead: on each iteration, slice the (stringified) number from index 0 to the current index.
function createArrayOfTiers(num) {
const strNum = String(num);
return [...strNum].map((_, i) => strNum.slice(0, i + 1));
}
console.log(createArrayOfTiers(420));
To fix your existing code, you'll want to access the previous element of the accumulator, not the previous element of curr (which doesn't really make sense), multiply it by 10, and add to it the digit being iterated over. Also make sure to return the constructed array:
function createArrayOfTiers(num) {
const onArray = String(num).split('')
return onArray.reduce((acc, curr, index) => {
if (!index) return Number(curr);
return [...acc, acc[index - 1] * 10 + Number(curr)];
}, [])
}
console.log(createArrayOfTiers(420));

You have the error because you forgot to take first number in the third iteration. You take acc + curr[index - 1] but forgot about acc + curr[index - 2].
To fix it you can slice numbers from first to current and then join them.
function createArrayOfTiers(num) {
const onArray = String(num).split("");
return onArray.reduce((acc, curr, index, arr) => {
const newAcc = [...acc, arr.slice(0, index + 1).join("")];
return newAcc;
}, []);
}
console.log(createArrayOfTiers(420));

Try this:
function createArrayOfTiers(num) {
const str = String(num);
const res = str.split('').reduce((acc, curr, index) => {
acc = [...acc, str.substring(0,index)+curr];
return acc;
}, []);
return res;
}
console.log( createArrayOfTiers(420) );

You could map the stringed value by using a closure of a string which stores the value of the last result.
function createArrayOfTiers(num) {
return Array.from(num.toString(), (s => v => s += +v)(''));
}
console.log(createArrayOfTiers(420));
The reduce aspect ...
function createArrayOfTiers(num) {
return [...num.toString()].reduce((r, v) => [...r, (r[r.length - 1] || '') + v], []);
}
console.log(createArrayOfTiers(420));

my way
const createArrayOfTiers = arg =>
[...String(arg)].reduce((r,c,i)=>[...r, (r[i-1]||'')+c],[])
console.log('420 -->' ,JSON.stringify( createArrayOfTiers(420) ));
console.log("'elisa' -->" ,JSON.stringify( createArrayOfTiers('elisa') ));
.as-console-wrapper{max-height:100% !important;top: 0;}

thanks to CertainPerformance in this thread I understood that I was trying to access the index of an inexistent array in curr therefore I needed to access the index of the acc value and came up to this solution using an updated version of my code:
function createArrayOfTiers(num) {
const onArray = String(num).split('')
const reducer = onArray.reduce((acc, curr, index) => {
if (!index) return [curr]
const newAcc = [...acc, acc[index -1] + curr]
return newAcc
},[])
return reducer
}
createArrayOfTiers(420)
I also transformed curr to an array because in case of only one digit, should return an array as well.
Thanks to all who gave brilliant ideas!!

Related

Divide S3 prefix to list of object in Javascript

Example, I have a path(prefix) like this: A/B/C/
I want to get bellow list:
[{name:"A",path:"A/"},
{name:"B",path:"A/B/",
{name:"C",path:"A/B/C/"
]
I can split the path to a array, then loop the array to build the new list of object.
But in my mind, I just know there should be a simple and smarter way to achieve this by using reducer, but just stuck here.
You're right that you could use a reducer. Something like this:
const str = "A/B/C/"
const arr = str.split("/").filter(Boolean).reduce((acc, name) => {
const path = [...acc.map(o => o.name), name].join("/") + "/"
return [...acc, { name, path }]
}, [])
console.log(arr)
You can solve this with map, but maybe not as cleanly as you were anticipating:
const result = 'A/B/C'
.split('/')
.filter(x => x)
.map((name, i, arr) => {
const prev = arr[i - 1];
return prev
? { name, path: `${prev.name}${name}/` }
: { name, path: `${name}/` };
});
My odd solution, but I think sdgluck's is the cleanest answer.
arr = "A/B/C/"
.split("/")
.filter(e => e)
.map((e, i, a) => {
a2 = a.filter((el, ix) => {
if (ix <= i) return el;
});
return {[e] : a2.join("/")};
});

reduce method in javascript gets undefined

How do I use the reduce method in solving this problem? I keep getting undefined. I am trying to only print out the numbers that are bigger while going through the loop. so for example [ 1,4,2,9,1,12] will give me the printout of 1,4,9,12- here is what I got... and I keep getting undefined for the accumulator????
**
function main(array){
let myNum;
let answer=[];
outPut = array.reduce((accumulator, element) => {
const num = accumulator;
myNum= num > element? answer.push(num):
answer.push(element);
}, 0);
console.log(outPut);
}**
main([20, 3, 22, 15, 6, 1]);
You need to return the accumulator in reduce().
A simpler version is to check the index is zero or value is greater than previous in the accumulator to determine whether to add current value or not
const data = [ 1,4,2,9,1,2,12];
const res = data.reduce((a, c, i) => {
return (!i || c > a[a.length-1]) ? a.concat(c) : a;
},[])
console.log(res)
If you want [1,4,2,9,1,12] to return [1,4,9,12], array.reduce isn't the way to go.
Try
function sortArray(array) {
const sortedArray = array
.sort((a, b) => a - b) //sort by ascending
.filter((e, index) => array.indexOf(e) == index) //remove duplicates
console.log(sortedArray);
}
That's because the reduce() works with callback which return nunber. You are not returning anything.
But the main issue is the fact you are pushing items into answer. But then you are trying to log outPut, which is not initialized and the only value it could have is from wrongly used reduce().
Try this:
function main(array){
let myNum;
let answer=[];
array.reduce((accumulator, element) => {
const num = accumulator;
let biggerOne;
if(num > element) {
biggerOne = num;
} else {
biggerOne = element;
}
answer.push(biggerOne);
return biggerOne;
}, 0);
console.log(answer);
}**
main([20, 3, 22, 15, 6, 1]);

Javascript - Delete all duplicates from array

I have problem with delete all duplicate in array.
Array = [1,1,2,2,3]
Every solution, what I found, haves result this
Array = [1,2,3]
But I need this
Array = [3]
How can I do this?
You can first iterate over the array once to obtain a Map of the frequencies of each item and then filter to find the elements that only appeared once.
const arr = [1,1,2,2,3];
const freq = arr.reduce((acc,curr)=>(acc.set(curr,(acc.get(curr)||0)+1),acc),new Map);
const res = arr.filter(x => freq.get(x) === 1);
console.log(res);
You could store an object for occurences of each element and get the elements that have the occurence of 1
const arr = [1, 1, 2, 2, 3]
const occurrences = arr.reduce((acc, el) => {
acc[el] = (acc[el] || 0) + 1
return acc
}, {})
const res = Object.entries(occurrences)
.filter(([el, time]) => time === 1)
.map(([el]) => +el)
console.log(res)
Unlike some of the other solutions, this allows you to make a single loop over the array, rather than a reduce followed by a filter and/or map loop. That said, there are trade-offs in readability and other condition checks, and it plays a bit fast and loose with the semantic intention of a reduce, so it might be a wash in terms of benefits.
const myArray = [1,1,2,2,3];
const dupesRemoved = myArray.reduce((acc, cur, idx, src) => {
if (!acc.dupes.has(cur)) {
if (acc.singleInstances.has(cur)) {
acc.singleInstances.delete(cur);
acc.dupes.add(cur);
} else {
acc.singleInstances.add(cur);
}
}
if (idx === src.length - 1) {
return [...acc.singleInstances];
}
return acc;
}, { singleInstances: new Set(), dupes: new Set() });
console.log(dupesRemoved);
Here is a simple and short solution:
let arr = [1,1,2,2,3];
let filtered_arr = arr.filter(v => arr.indexOf(v) === arr.lastIndexOf(v));
console.log(filtered_arr);

What is the correct scope to declare a variable that needs to be remembered while looping?

Beginner at JavaScript here. Below is a JS challenge I've just completed for a course. The challenge:
Clean the room function: given an input of [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20], make a function that organizes these into individual array that is ordered. For example answer(ArrayFromAbove) should return: [[1,1,1,1],[2,2,2], 4,5,10,[20,20], 391, 392,591]
My solution:
let originalArray = [1,2,4,591,392,391,2,5,10,2,1,1,1,20,20];
const compareFunction = ((a, b) => {
return a-b;
});
let counter = 1;
const groupFunction = (currentValue, index, arr) => {
nextNumber = arr[index + 1];
if (currentValue === nextNumber){
counter++;
} else {
if (counter > 1){
let filledArray = new Array(counter).fill(currentValue);
counter = 1;
return filledArray;
} else {
return currentValue;
}
}
};
const filterFunction = (currentValue) => {
return currentValue !== undefined;
}
const finalFunction = (arr) => {
arr.sort(compareFunction);
let groupedArray = arr.map(groupFunction);
let finalArray = groupedArray.filter(filterFunction);
return finalArray;
}
finalFunction (originalArray);
Everything returns correctly, however I am under the impression that it is bad practice to declare global variables. With my "counter" variable, if I assign it within the groupFunction, the counter resets every loop through the array, making it useless. Where would be the appropriate place to put this variable? Is there another method / approach that would be better suited for the problem all together? Thank you!
In my opinion, your code is very hard to read, and it would probably be better if you'd rewrite it somehow, but I will leave that up to you. One thing you can do, to remove this global variable, is to use a concept called higher order function.
const higherOrderFunction = () => {
let counter = 1;
const groupFunction = (currentValue, index, arr) => {
nextNumber = arr[index + 1];
if (currentValue === nextNumber){
counter++;
} else {
if (counter > 1){
let filledArray = new Array(counter).fill(currentValue);
counter = 1;
return filledArray;
} else {
return currentValue;
}
}
}
return groupFunction;
};
You then get access to your groupFunction by calling the higher order function,
but the variable does not pollute your global scope:
let groupFunction = higherOrderFunction()
Here's a much shorter approach, that I know as beginner will have you searching to learn what some of the stuff used does, if you did not learn about it already.
Stay Curious.
const finalFunction = (arr) => {
const map = arr.reduce((acc, curr) => {
acc[curr] = (acc[curr] || 0) + 1;
return acc;
}, {});
return Object.keys(map)
.sort((a, b) => a - b)
.reduce((acc, val) => {
const value = Number(val);
acc.push(map[val] > 1 ? Array(map[val]).fill(value) : value);
return acc;
}, []);
};
And another one:
const finalFunction = (arr) => arr.sort((a, b) => a - b)
.reduce((acc, curr) => {
const last = acc.pop();
return curr === last
? [...acc, [curr, curr]]
: last && curr === last[0]
? [...acc, [...last, curr]]
: [...acc, ...(last ? [last] : []), curr];
}, []);

Filter array in javascript

I have an array which including all of the times as below :
["14:00-14:30" , "14:30-15:00", "15:00-15:30"]
as you can see this time slots, so basically, its from : 14:00 to 15:30
output will be : ["14:00-15:30"]
But if i have :
["14:00-14:30", "14:30-15:00", "15:30-16:00"]
in this case, the output would be : ["14:00-15:00", "15:30-16:00"]
My solution: convert all of this to a single array ["14:00", "14:30", "14:30", "15:00", ...]. And then forEach to each element, delete the one the have arr[i] === arr[i+1].
I got it working but I don't really like the way its. is there any better idea or how to use filter in this case ? Thanks.
Assuming the format of the array is correct:
const yourArray = ["14:00-14:30", "14:30-15:00", "15:30-16:00"];
const solution = yourArray.sort().reduce(
(acc, item, index) => {
if (index===0) {
acc.push(item);
return acc;
}
const currentValueParsed = acc[acc.length-1].split('-');
const newValueParsed = item.split('-');
if (currentValueParsed[1] === newValueParsed[0]) {
acc[acc.length-1] = `${currentValueParsed[0]}-${newValueParsed[1]}`;
return acc;
}
acc.push(item);
return acc;
}, []
);
console.log(solution); // ["14:00-15:00", "15:30-16:00"]
The code could be small, but I prefer being explicit.
We sort the array.
We add the first element to the final array.
And every new element, ee decide if we need to modify the last element of the solution array or add the new element to this array.
And because you look interested in evolve your original solution.
const yourArray = ["14:00-14:30", "14:30-15:00", "15:30-16:00"];
const solution = yourArray.sort()
.join('-')
.split('-')
.filter((item, pos, arr) => {
return pos === 0 || (item !== arr[pos - 1] && item !== arr[pos + 1]);
})
.reduce((acc, item, pos, arr) => {
if (pos % 2) {
acc.push(`${arr[pos - 1]}-${arr[pos]}`);
}
return acc;
}, []);
console.log(solution); // ["14:00-15:00", "15:30-16:00"]
Notes:
It is important to sort in the beginning.
pos % 2 is telling me if it is an even position.
I don't care arr[pos + 1] return undefined in the last item.
Filter won't work, since you're creating a new value, not just keeping existing values. Reduce would, though.
let start = null, end = null;
let finalAnswer = arr.reduce((result, current, i) => {
const [first, last] = current.split('-');
if (start === null) { start = first;}
if (first !== end && end !== null) { result.push(`${start}-${end}`); if (i === arr.length - 1) { result.push(current); }}
else if (i === arr.length - 1) { result.push(`${start}-${last}`); }
else { end = last; }
return result;
}, []);
I'm sure there's a cleaner way to do this -- I had to throw in edge cases more than I'd like -- but this works :)
The idea is that you keep track of the interval's start and end times; if the current interval's start equals the last interval's end, then update the end time to the current interval's. Otherwise, push the current start and end time and reset the counter for the next entry. The edge cases are to handle when the final entry either does or does not create its own new interval; if it does, push the entry as its own interval, and if not, push a new interval with the current start and the entry's end time.
this is really a reduce operation, so a solution could like:
const result = array
.sort() // if needed?
.map(tf => tf.split('-')) // make it easier to work with
.reduce((acc, currFrame, idx, arr) => {
let reducedFrame = acc[acc.length - 1] // get latest reduced frame
if (!reducedFrame || reducedFrame.length === 2) { // filled range or at start
reducedFrame = [currFrame[0]] // so start a new one
acc.push(reducedFrame)
}
const nextFrame = arr[idx + 1]
if (!nextFrame || nextFrame[0] !== currFrame[1]) { // at last frame or end of the current continuous frame
reducedFrame.push(currFrame[1]) // so end the reduced frame
}
return acc
}, [])
.map(tf => tf.join('-')) // put it back
or the dupe filter approach would work as well I believe, building off #Dalorzo:
const result = array
.join('-').split('-') // convert to array of singles
.filter((v,i) => array.indexOf(v) === i) // lose the dupes
.sort() // if needed (performs better here in this case)
.reduce((acc, cur, i, arr) => // join every 2
(i % 2 === 0)
? acc.concat([cur + '-' + arr[i + 1]])
: acc, [])
In order to create the desired array what I would do is to join and split like:
var arr =['14:00-14:30', '14:30-15:00', '15:30-16:00'];
var arr = arr.join('-').split('-');
The above will produce the array with all times and then you could remove the duplicates any way you want one way:
var result = arr.filter((v,i) => arr.indexOf(v) === i);

Categories

Resources