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]);
Related
It is a simple exercise that I am doing for mere practice and leisure, I have done it in various ways but I was wondering if there is an even more practical way or to reduce the lines of code making use of the many methods of JavaScript.
The exercise is about receiving an array (arr) and a number (target) and returning another array with a pair of numbers found in 'arr' whose sum is equal to 'target'.
function targetSum3(arr, target) {
let newArr = [];
let copyArray = arr;
for (let i of copyArray) {
let x = Math.abs(i - target);
copyArray.pop(copyArray[i]);
if (copyArray.includes(x) && (copyArray.indexOf(x) != copyArray.indexOf(i))) {
newArr.push(i);
newArr.push(x);
return newArr;
}
}
return newArr;
}
If you are fine with a function that just returns a pair of numbers (the first match so to speak) whose sum equals the targets value, this might be enough:
function sumPair (arr, target) {
while(arr.length) {
let sum1 = arr.shift();
let sum2 = arr.find(val => sum1 + val === target);
if (sum2) return [sum2, sum1];
}
return null;
}
const targetSum = (arr, target) => {
const first = arr.find((v,i,a) => arr.includes(target-v) && (arr.indexOf(target-v) !== i));
return first ? [first, target - first] : null;
};
const values = [1,2,3,4,5,6,7,8,9];
console.log(targetSum(values, 1)); // null
console.log(targetSum(values, 2)); // null
console.log(targetSum(values, 3)); // [1, 2]
console.log(targetSum(values, 15)); // [6, 9]
console.log(targetSum(values, 20)); // null
I changed for loop with forEach (more efficient) and there is no need for the copyArray array so I removed it. I also changed pop() with shift(), I think you want to shift the array and not pop-it (if I understand the task correctly).
function targetSum3(arr, target) {
let newArr = [];
arr.forEach(element => {
let x = Math.abs(element - target); // calc x
arr.shift(); // removes first element from arr (current element)
if (arr.includes(x) && (arr.indexOf(x) != arr.indexOf(element))) {
newArr.push(element);
newArr.push(x);
return;
}
});
return newArr;
}
use Array.filter to find the target sum for all values in an given array. See comments in the snippet.
sumsForTargetInArray();
document.addEventListener(`click`,
evt => evt.target.id === `redo` && sumsForTargetInArray());
function sumsInArray(arr, target) {
// clone the array
const clone = arr.slice();
let result = [];
while (clone.length) {
// retrieve the current value (shifting it from the clone)
const current = clone.shift();
// filter arr: all values where value + sum = target
const isTarget = arr.filter(v => current + v === target);
// add to result.
// Sorting is to prevent duplicates later
if (isTarget.length) {
result = [...result, ...isTarget.map(v => [current, v].sort())];
}
}
// weed out duplicates (e.g. 0 + 3, 3 + 0)
const unique = new Set();
result.forEach(r => unique.add(`${r[0]},${r[1]}`));
// return array of array(2)
return [...unique].map(v => v.split(`,`).map(Number));
}
function sumsForTargetInArray() {
const testArr = [...Array(20)].map((_, i) => i);
const target = Math.floor(Math.random() * 30);
document.querySelector(`pre`).textContent = `testArray: ${
JSON.stringify(testArr)}\ntarget: ${target}\nResult: ${
JSON.stringify(sumsInArray(testArr, target))}`;
}
<pre></pre>
<button id="redo">Again</button>
The last part to this exercise is to write a recursive function that takes two parameters, a joined list and an index respectively. The function will find the value in the object within the list at it's respective index. The code i have written works the way i want (i can see it working when i console.log for every occasion the function is called. But on the last occasion it refers undefined as my value. I cannot understand why. Oh and it works for index of 0. code as followed.
and first, list looks like this:
list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
// console.log(value);
// console.log(targetNum);
// console.log(list);
nth(list, targetNum);
}
};
console.log(nth(arrayToList([1,2,3]),2));
below is the code for arrayToList it was the first part of the exercise and if you have any comments that's cool, cause the hints ended up suggesting to build the list from the end.
const arrayToList = (arr) => {
let list = {
value: arr[0],
rest: nestObject()
};
function nestObject() {
let rest = {};
arr.shift();
const length = arr.length;
if (length == 1) {
rest.value = arr[0];
rest.rest = null;
} else {
rest.value = arr[0];
rest.rest = nestObject();
}
return rest;
}
return list;
};
Both solutions are convoluted and unnecessary verbose. Actually, both functions could be one-liners. Here are a few hints:
For the toList thing consider the following:
if the input array is empty, return null (base case)
otherwise, split the input array into the "head" (=the first element) and "tail" (=the rest). For example, [1,2,3,4] => 1 and [2,3,4]
return an object with value equal to "head" and rest equal to toList applied to the "tail" (recursion)
On a more advanced note, the split can be done right in the function signature with destructuring:
const toList = ([head=null, ...tail]) => ...
Similarly for nth(list, N)
if N is zero, return list.value (base case)
otherwise, return an application of nth with arguments list.rest and N-1 (recursion)
Again, the signature can benefit from destructuring:
const nth = ({value, rest}, n) =>
Full code, if you're interested:
const toList = ([value = null, ...rest]) =>
value === null
? null
: {value, rest: toList(rest)}
const nth = ({value, rest}, n) =>
n === 0
? value
: nth(rest, n - 1)
//
let lst = toList(['a', 'b', 'c', 'd', 'e', 'f'])
// or simply toList('abcdef')
console.log(lst)
console.log(nth(lst, 0))
console.log(nth(lst, 4))
You simply need to add a return when recursively calling nth. Otherwise the logic is carried out but no value is returned (unless targetNum is 0)
const nth = (list, targetNum) => {
let value = Object.values(list)[0];
if (targetNum == 0) {
return value;
} else {
targetNum = targetNum -1;
list = Object.values(list)[1];
return nth(list, targetNum); // return needed here too
}
};
Or more succinctly:
const nth = (list, n) => n === 0 ? list.value : nth(list.rest, n - 1)
Here's another non-recursive arrayToList that builds the list from the end:
const arrayToList = arr => arr.slice().reverse().reduce((rest, value) => ({value, rest}), null);
(The slice here is just to make a copy of the array so that the original is not reversed in place.)
Georg’s recursive solutions are beautiful!
I’d like to add the hinted “build the list from the end” solution from the book:
const arrayToList => (arr) => {
var list
while (arr.length) {
list = {value: arr.pop(), rest: list}
}
return list
}
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!!
I'm new to coding. I want to return all longest strings of equal length. When I run it I get:
RangeError: Maximum call stack size exceeded.
at arrayOfStrings:8:10
I know that it is looping and hitting the call limit. Is this salvageable or is there a better way? Thanks for your help!
function arrayOfStrings(allLLongestStrings) {
allLLongestStrings => {
let maxLng = Math.max(...arrayOfStrings.map( elem => elem.length))
return arrayOfStrings.filter(elem => elem.length === maxLng)
}
return arrayOfStrings();
}
arrayOfStrings(
[
'otorhinolaryngological',
'Otorhinolaryngological',
'Psychophysicotherapeutics',
'Thyroparathyroidectomized',
'Pneumoencephalographically',
'Radioimmunoelectrophoresis',
'Psychoneuroendocrinological',
'Hepaticocholangiogastrostomy',
'Spectrophotofluorometrically',
'Antidisestablishmentarianism'
]
);
I would suggest to use a different approach by reducing the array and checking if the actual string is longer than the last stored one, then take a new array with this string, otherwise check the length and if it has the same take the string for the temporary result set.
function getLongestStrings(array) {
return array.reduce((r, s, i) => {
if (!i || r[0].length < s.length) { // first or longer string
return [s];
}
if (r[0].length === s.length) { // same length
r.push(s);
}
return r;
}, []);
}
console.log(getLongestStrings(['otorhinolaryngological', 'Otorhinolaryngological', 'Psychophysicotherapeutics', 'Thyroparathyroidectomized', 'Pneumoencephalographically', 'Radioimmunoelectrophoresis', 'Psychoneuroendocrinological', 'Hepaticocholangiogastrostomy', 'Spectrophotofluorometrically', 'Antidisestablishmentarianism']));
Try sorting the array by length and then picking out the first element of the array.
function arrayOfStrings(arr) {
return arr.sort((a, b) => b.length - a.length)[0]
}
I'm sorry if you intended something else, but I couldn't understand your code so I figured
you wanted the string with the maximum length in the array.
Below is an approach I would take to get the longest strings from the array. The code comments explain the logic:
function getLongest(a) {
//Find the length of the longest word in the array
var maxLength = Math.max(...a.map(s => s.length));
//Return array after filtering words with length less than maxLength.
return a.filter(s => s.length === maxLength);
}
console.log(getLongest([
'otorhinolaryngological',
'Otorhinolaryngological',
'Psychophysicotherapeutics',
'Thyroparathyroidectomized',
'Pneumoencephalographically',
'Radioimmunoelectrophoresis',
'Psychoneuroendocrinological',
'Hepaticocholangiogastrostomy',
'Spectrophotofluorometrically',
'Antidisestablishmentarianism'
]));
This code will only work in an ES6 environment as it takes advantage of spread syntax
function allLongestStrings(inputArray) {
const s = inputArray
const m = inputArray.map((e) => {
return e.length
}).sort((a, b) => b - a)[0]
return s.filter((b) => {
if ( b.length === m ) return b
})
}
I solved it like this:
function longest(array) {
let longitud = array.length;
const arreglo = [];
maximaLongitud = array.sort((a,b) => b.length - a.length)[0].length
console.log(maximaLongitud)
for (let i = 0; i < longitud; i++) {
if (array[i].length === maximaLongitud) {
arreglo.push(array[i]);
}
}
return arreglo;
}
I have an array of objects in my angular controller.
I want to return the value of the index of the field within the array which has a matching ID to my parameter.
There will only be one object in the array with a matching fieldId..
$scope.indexOfField = function(fieldId) {
return $scope.model.fieldData.filter(function(x) {
if (x.Id === fieldId) return // ???????
});
}
The .findIndex() method returns the index of the first element of the array that satisfies a condition given by a function. If the function returns false for all elements of the array, the result is -1.
See the documentation here.
In my example, x is an item for each iteration and I use cross function for my condition.
const datas = [];
const fieldId = 5;
let index = datas.findIndex( x => x.Id === fieldId );
You can't return index from filter method.
The filter() method creates a new array with all elements that pass
the test implemented by the provided function.
You can use forEach
$scope.indexOfField = function(fieldId) {
var i;
return $scope.model.fieldData.forEach(function(x, index) {
if (x.Id === fieldId) {
i = index;
}
});
// use i
}
or even better to use for as you can't stop forEach when you have found your id.
$scope.indexOfField = function(fieldId) {
var fieldData = $scope.model.fieldData,
i = 0, ii = $scope.model.fieldData.length;
for(i; i < ii; i++) if(fieldData[i].Id === fieldId) break;
// use i
}
From the Array.prototype.filter documentation:
callback is invoked with three arguments:
the value of the element
the index of the element
the Array object being traversed
However you should probably be using the some function if there is only one instance in your array (as it will stop as soon as it finds the first occurrence), and then find the index using indexOf:
var field = $scope.model.fieldData.filter(function(x) {
return x.Id === fieldId;
})[0];
var index = $scope.model.fieldData.indexOf(field);
Or iterate the array until you find the correct element:
var index;
$scope.model.fieldData.some(function(x, i) {
if (x.Id === fieldId) return (index = i);
});
ARRAY (FIND MULTIPLE INDEXES) METHOD
[10, 7, 13, 15, 230].map((e,i) => e > 13 ? i : undefined).filter(x => x)
//returns [3, 4](*** RETURNS multiple indexes ***)
//FILTER (is simply just REMOVING the UNDEFINED elements (which are FALSY and considered the same as FALSE)
otherwise you'll get...
[10, 7, 13, 15, 230].map((e,i) => e > 13 ? i : undefined) //returns [undefined, undefined, undefined, 3, 4]
RETURN MULTIPLE INDEXES (replaces findIndex METHOD)
[1, 1, 2, 2, 2, 3, 4, 5].map((e,i) => e === 2 ? i : undefined).filter(x => x) //returns [2, 3, 4]
RETURN MULTIPLE VALUES (replaces find METHOD)
[5, 12, 8, 130, 44].map((e,i) => e > 13 ? e : undefined).filter(x => x) // returns [130, 44]
The second argument to your callback is the index. I can't quite make out what you want your function to do/return, but if you add , index after function(x, that will give you access to the index for that iteration.
Working from the name of your function, I don't think you want filter at all:
$scope.indexOfField = function(fieldId) {
var result = -1;
$scope.model.fieldData.some(function(x, index) {
if (x.Id === fieldId) {
result = index;
return true;
}
});
return result;
}
Array#some stops as of the first iteration that returns a truthy value, so we'll stop searching the first time we find a match.
You cannot return directly the index but you can set the 'thisArg' parameter and set data inside it. This is cleaner than a global variable.
var data = {
indexes: []
};
var myArray = [1,2,3,4,5,6,7,8,9,10];
myArray.filter(function (element, index) {
if (element%2 == 0) {
this.indexes.push(index);
return true;
}
}, data);
console.log(data.indexes); // [1, 3, 5, 7, 9]
data.indexes.forEach(function(value) {
console.log(myArray[value]);
}); // 2, 4, 6, 8, 10
Use the findIndex method - Array.prototype.findIndex().
When the condition is met first, the index is returned.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
You most definitely can (ahem, guy who got accepted answer)
const polyfillter = (arr,condition) => arr.map( (e,i) => condition ? i : -1).filter(e=>e>=0)
Pass in an array, and your condition, and get back matching keys.
example
mynewstuff = polyfillter(myoldcrud, "e.length > 5")
Some languages can map a collection into an indexed collection where each element is mapped to a pair of {element, index}. That way you can map/filter/etc using any of those two values.
For example, Kotlin has withIndex and Swift has enumerated.
Javascript doesn't have that method, AFAIK. But you can easily build yours or use a workaround.
Workaround (not recommended)
// Turn an array of elements into an array of {value, index}
const indexedArray = array.map((v,i) => ({value:v, index:i}));
// Now I can filter by the elem but keep the index in the result
const found = array.filter(x => x.value === someValue)[0];
if (found) {
console.log(`${found.value} found at index ${found.index}`);
}
// One-liner for the question scenario, using some new js features.
// Note that this will fail at the last ".i" if the object is not found.
const index = fieldData.map((v,i) => ({v,i})).filter(x => x.v.id == fieldId)[0].i
Add a withIndex method to Array (recommended):
This is basically the same as the workaround, but creating a reusable function, which makes it much cleaner.
Array.prototype.withIndex = function() {
return this.map((v,i) => ({value: v, index: i}))
};
// Now the one-liner would be:
const index = fieldData.withIndex().filter(x => x.value.id == fieldId)[0].index;
// Better, with null checking:
const found = fieldData.withIndex().filter(x => x.value.id == fieldId)[0];
if (found) {
console.log(`${found.value} found at index ${found.index}`);
}
Try flatMap(), with or without i.
[5, 12, 8, 130, 44].flatMap((e, i) => (e > 13 ? e : [])); // returns [130, 44]
If there is only one object returned you can simply use the original array to refer to the returned object.
Since filter returns an array, you can do as follows
$scope.indexOfField = function(fieldId) {
var filteredArray = $scope.model.fieldData.filter(function(x) {
return x.Id === fieldId
});
var desiredObject = filteredArray[0]
return $scope.model.fieldData.indexOf(desiredObject);
}
['ab', 'cd', 'ef', 'id', 'junk', 'dummy','name'].map((x, ndx)=>['id', 'name'].includes(x)?ndx:'').filter(e=>e)
result: (2) [3, 6]
Filter will not return the index, but you can do something like this.
$scope.indexOfField = function(fieldId) {
$scope.model.fieldData.filter(function(x, i) {
if (x.Id === fieldId) {
var indexOfField = i;
}
});
return indexOfField;
};
$scope.indexOfField = function(fieldId) {
let index;
$scope.model.fieldData.filter((x, i) => {
if (x.Id === fieldId) index = i;
return x.Id === fieldId;
});
return index;
}
function modifyArray(nums) {
let newNums = nums.filter((num,index) => {
return num = num % 2 ? num * 3 : num * 2;
})
return newNums;
}
Here the index is the increment value you are looking for.