This question is about an exercise in the book Eloquent JavaScript - javascript

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
}

Related

Is there a way to make this Javascript code more efficient?

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>

JavaScript returning ObjectObject with no fields returning as expected { a: 2, b: 2, c: 1 }

I am constructing a reduce function that accepts an array, a callback, and an initial value and returns a single value. The function satisfies 2/3 of the following conditions: should not mutate the input array, should sum up an array, and should create a "totals" object from an array.
function reduce(array, callback, num) {
let sum = 0;
let totals;
for ( let i = 0; i < array.length; i++) {
sum += num + array[i];
}
return sum
array.forEach(function(ele, index){
totals = callback(totals, ele);
});
return totals;
}
I've satisfied all these conditions except the final one. I get the following error after running my code where my reduce function fails to "create a "totals" object from an array":
expected 'expected
'0[object Object]a[object Object]b[object Object]c[object Object]a[object Object]b'
to deeply equal
{ a: 2, b: 2, c: 1 }.
Any input here would be appreciated.
First, the return keyword ends the function. So, writing any code past that return sum would have no effect at all. And in fact, you don't need that return, nor the loop above it:
function reduce(array, callback, totals) {
// By default, if totals is empty, reduce uses the first element as a base
if (typeof totals === "undefined" && array.length > 0) {
totals = array[0];
array = array.slice(1);
}
array.forEach(function(ele, index){
totals = callback(totals, ele);
});
return totals;
}
console.log( reduce([1, 2, 3], (sum, v) => sum + v) ); // 6
console.log( reduce([1, 2, 3], (sum, v) => sum + v, 3) ); // 9
Sorry but your code makes no sense. Here's why:
function reduce(array, callback, num) {
let sum = 0;
let totals;
// Problem one - you are hardcoding behaviour instead of
// using a callback which is normally supposed to inject the reducing logics
for ( let i = 0; i < array.length; i++) {
sum += num + array[i];
}
return sum // after that line all the code is a deadcode
// as the return command terminates your reduce function
// and returns sum
array.forEach(function(ele, index){
totals = callback(totals, ele);
// as I said this is the dead code. Anyways pay attention that
// totals variable is instantiated but undefined. What are you planning
// to achieve by providing it to the callback function?
});
return totals;
}
And here's my implementation of the standard JS API Array.prototype.reduce function:
function myReduce (cb, initialValue) {
if (!Array.isArray(this) || (!this.length && !initialValue))
throw TypeError;
// making a copy of the original array to prevent
// it from being mutated by a callback
const array = [...this];
// if no initial value is provided, we take the first element
// of array instead
let acc = initialValue || array[0];
// if no initialValue is provided we start iteration from 1
// else the iterations starts from 0
for (let i = Number(!initialValue); i < array.length; i++)
acc = cb (acc, array[i], i, array);
return acc;
}
// here's what you supposedly are trying to achieve:
const myArray = [1,2,3,4,5];
// hereinafter I will use Function.prototype.call
// to bind the array to myReduce function
console.log(myReduce.call(myArray, (acc, cur) => acc + cur)); // => 15
// And more advanced example of a simple array-to-object mapper:
const myArray2 = [
{id: 0, email: 'user0#co.cc'},
{id: 1, email: 'user1#co.cc'},
{id: 2, email: 'user2#co.cc'},
];
console.log(myReduce.call(
myArray2, // this
(acc, {email, id}) => ({...acc, [id]: email}), // cb
{} // initial state
)); // => Object {0: "user0#co.cc", 1: "user1#co.cc", 2: "user2#co.cc"}
Hopefully that will be helpful. Good luck!

How to adjust return values of map() function?

I have been trying to make a excercise in the course I am taking. At the end, I did what was asked, but I personally think I overdid too much and the output is not convenient -- it's a nested array with some blank arrays inside...
I tried to play with return, but then figured out the problem was in the function I used: map always returns an array. But all other functions, which are acceptable for arrays (in paticular forEach and I even tried filter) are not giving the output at all, only undefined. So, in the end, I have to ask you how to make code more clean with normal output like array with just 2 needed numbers in it (I can only think of complex way to fix this and it'll add unneeded junk to the code).
Information
Task:
Write a javascript function that takes an array of numbers and a target number. The function should find two different numbers in the array that, when added together, give the target number. For example: answer([1,2,3], 4) should return [1,3]
Code
const array1 = [1, 2, 3];
const easierArray = [1, 3, 5] //Let's assume number we search what is the sum of 8
const findTwoPartsOfTheNumber = ((arr, targetNum) => {
const correctNumbers = arr.map((num, index) => {
let firstNumber = num;
// console.log('num',num,'index',index);
const arrayWeNeed = arr.filter((sub_num, sub_index) => {
// console.log('sub_num',sub_num,'sub_index',sub_index);
if (index != sub_index && (firstNumber + sub_num) === targetNum) {
const passableArray = [firstNumber, sub_num] //aka first and second numbers that give the targetNum
return sub_num; //passableArray gives the same output for some reason,it doesn't really matter.
}
})
return arrayWeNeed
})
return correctNumbers;
// return `there is no such numbers,that give ${targetNum}`;
})
console.log(findTwoPartsOfTheNumber(easierArray, 8));
console.log(findTwoPartsOfTheNumber(array1, 4));
Output
[[],[5],[3]]
for the first one
You can clean up the outpu by flatting the returned arrays :
return arrayWeNeed.flat();
and
return correctNumbers.flat();
const array1 = [1, 2, 3];
const easierArray = [1, 3, 5] //Let's assume number we search what is the sum of 8
const findTwoPartsOfTheNumber = ((arr, targetNum) => {
const correctNumbers = arr.map((num, index) => {
let firstNumber = num;
// console.log('num',num,'index',index);
const arrayWeNeed = arr.filter((sub_num, sub_index) => {
// console.log('sub_num',sub_num,'sub_index',sub_index);
if (index != sub_index && (firstNumber + sub_num) === targetNum) {
const passableArray = [firstNumber, sub_num] //aka first and second numbers that give the targetNum
return sub_num; //passableArray gives the same output for some reason,it doesn't really matter.
}
})
return arrayWeNeed.flat();
})
return correctNumbers.flat();
// return `there is no such numbers,that give ${targetNum}`;
})
console.log(findTwoPartsOfTheNumber(easierArray, 8));
console.log(findTwoPartsOfTheNumber(array1, 4));
However, using a recursive function could be simpler :
const answer = (arr, num) => {
if (arr.length < 1) return;
const [first, ...rest] = arr.sort();
for (let i = 0; i < rest.length; i++) {
if (first + rest[i] === num) return [first, rest[i]];
}
return answer(rest, num);
};
console.log(answer([1, 2, 3], 4));
console.log(answer([1, 3, 5], 8));
It looks like you are trying to leave .map() and .filter() beforehand, which you can't (without throwing an error). So I suggest a normal for approach for this kind of implementation:
const array1 = [1,2,3];
const easierArray = [1,3,5] //Let's assume number we search what is the sum of 8
const findTwoPartsOfTheNumber = (arr,targetNum) =>{
for(let index = 0; index < arr.length; index++) {
let firstNumber = arr[index];
// console.log('num',num,'index',index);
for(let sub_index = 0; sub_index < arr.length; sub_index++){
const sub_num = arr[sub_index];
// console.log('sub_num',sub_num,'sub_index',sub_index);
if (index != sub_index && (firstNumber + sub_num) === targetNum){
const passableArray = [firstNumber,sub_num]//aka first and second numbers that give the targetNum
return passableArray; //passableArray gives the same output for some reason,it doesn't really matter.
}
}
}
return `there is no such numbers,that give ${targetNum}`;
}
console.log(findTwoPartsOfTheNumber(easierArray,8));
console.log(findTwoPartsOfTheNumber(array1,4));
console.log(findTwoPartsOfTheNumber(array1,10));
I've just grab your code and changed map and filter to for implementation.
There doesn't appear to be any requirement for using specific array functions (map, forEach, filter, etc) in the problem statement you listed, so the code can be greatly simplified by using a while loop and the fact that you know that the second number has to be equal to target - first (since the requirement is first + second == target that means second == target - first). The problem statement also doesn't say what to do if no numbers are found, so you could either return an empty array or some other value, or even throw an error.
const answer = (list, target) => {
while (list.length > 0) { // Loop until the list no longer has any items
let first = list.shift() // Take the first number from the list
let second = target - first // Calculate what the second number should be
if (list.includes(second)) { // Check to see if the second number is in the remaining list
return [first, second] // If it is, we're done -- return them
}
}
return "No valid numbers found" // We made it through the entire list without finding a match
}
console.log(answer([1,2,3], 3))
console.log(answer([1,2,3], 4))
console.log(answer([1,2,3], 7))
You can also add all the values in the array to find the total, and subtract the total by the target to find the value you need to remove from the array. That will then give you an array with values that add up to the total.
let arr1 = [1, 3, 5]
const target = 6
const example = (arr, target) => {
let total = arr.reduce((num1, num2) => {
return num1 + num2
})
total = total - target
const index = arr.indexOf(total)
if (index > -1) {
return arr.filter(item => item !== total)
}
}
console.log(example(arr1, target))
Map and filter are nice functions to have if you know that you need to loop into the whole array. In your case this is not necessary.
So you know you need to find two numbers, let's say X,Y, which belong to an array A and once added will give you the target number T.
Since it's an exercise, I don't want to give you the working code, but here is a few hints:
If you know X, Y must be T - X. So you need to verify that T - X exists in your array.
array.indexOf() give you the position of an element in an array, otherwise -1
If X and Y are the same number, you need to ensure that their index are not the same, otherwise you'll return X twice
Returning the solution should be simple as return [X,Y]
So this can be simplified with a for (let i = 0; i < arr.length; i++) loop and a if statement with a return inside if the solution exist. This way, if a solution is found, the function won't loop further.
After that loop, you return [] because no solution were found.
EDIT:
Since you want a solution with map and filter:
findTwoPartsOfTheNumber = (arr, tNumber) => {
let solution = [];
arr.map((X, indexOfX) => {
const results = arr.filter((Y, indexOfY) => {
const add = Y + X
if (tNumber === add && indexOfX != indexOfY) return true;
else return false;
});
if (results > 0) solution = [X, results[0]];
})
return solution;
}

in the easiest and most concise way as possible

I want to sort an array values in an ascending or descending order without using sort().
I have created a function, however I am not satisfied with it.
I believe the code below could be much shorter and more concise.
Please let me know where to modify or you may entirely change the code too. Thank you in advance.
const func = arg => {
let flip = false;
let copy = [];
for(let val of arg) copy[copy.length] = val;
for(let i=0; i<arg.length; i++) {
const previous = arg[i-1];
const current = arg[i];
if(previous > current) {
flip = true;
copy[i] = previous;
copy[i-1] = current;
}
}
if(flip) return func(copy);
return copy;
};
l(func([5,2,8,1,9,4,7,3,6]));
If your input is composed of whole numbers, as in the example, pne option is to reduce the array into an object, whose keys are the numbers, and whose values are the number of times those values have occured so far. Then, iterate over the object (whose Object.entries will iterate in ascending numeric key order, for whole number keys), and create the array to return:
const func = arr => {
const valuesObj = {};
arr.forEach((num) => {
valuesObj[num] = (valuesObj[num] || 0) + 1;
});
return Object.entries(valuesObj)
.flatMap(
([num, count]) => Array(count).fill(num)
);
};
console.log(
func([5,2,8,1,9,10,10,11,4,7,3,6])
);
This runs in O(N) time.
To account for negative integers as well while keeping O(N) runtime, create another object for negatives:
const func = arr => {
const valuesObj = {};
const negativeValuesObj = {};
arr.forEach((num) => {
if (num >= 0) valuesObj[num] = (valuesObj[num] || 0) + 1;
else negativeValuesObj[-num] = (negativeValuesObj[-num] || 0) + 1;
});
return [
...Object.entries(negativeValuesObj).reverse()
.flatMap(
([num, count]) => Array(count).fill(-num)
),
...Object.entries(valuesObj)
.flatMap(
([num, count]) => Array(count).fill(num)
)
];
};
console.log(
func([5,2,8,1,-5, -1, 9,10,10,11,4,7,3,6, -10])
);
For non-integer items, you'll have to use a different algorithm with higher computational complexity.

How to remove certain elements from an array into a new array and leave the others only the original array?

How to write a function to remove certain elements into a new array and leave the original array with only the remaining elements?
the first part is easy using a for loop pushing the even numbers into a new array but mutating the original array to leave only the odd numbers is hard
function remove(arr, cb){
var removed = [];
var newArr = [];
for(var i = 0; i < arr.length; i++) {
if(cb(arr[i], i, arr)) {
removed.push(arr[i]);
}
}
return removed;
}
Use an else statement to fill newArr with values that should stay in the original arr, then empty it using splice() before copying the items from newArr back into it.
function remove (arr, cb) {
var removed = [];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (cb(arr[i], i, arr)) {
removed.push(arr[i]);
} else {
newArr.push(arr[i]);
}
}
arr.splice(0);
for (var i = 0; i < newArr.length; i++) {
arr.push(newArr[i]);
}
return removed;
}
Welcome to Stackoverflow!
Personally, I'd avoid anything that mutates an input parameter, as this increases code complexity and makes it hard to reason about what's happening from the calling side.
Instead, I'd write a method that returns an array of two arrays. This can be easily split into two variables at the calling end using by using array destructuring.
See the example below:
const splitArr = (arr, pred) =>
arr.reduce(
(prev, curr, idx) => {
prev[+pred(curr, idx, arr)].push(curr);
return prev;
}, [[], []]
);
// usage //
const myArr = [1, 2, 3, 4];
const [arr1, arr2] = splitArr(myArr, x => x > 2);
console.log(arr1);
console.log(arr2);
Because pred is a function that returns a boolean value, we can co-erce this value to 0 or 1 using +someBoolean. We can then use this value as an index to decide into which of the two output arrays the value should be pushed.
You were definitely on the right track with your solution, a couple tweaks and we can make it very readable and also very easy to work with. I tried to keep the format of what it looked like you were doing.
I do take advantage of destructuring here, this could be returned as just an object, and then reference the properties.
const myArr = [0,1,2,3,4,5,6,7,8,9,10];
const splitItems = (arr, logicFunc) => {
let secondSet = []
const firstSet = arr.filter(v => {
if (logicFunc(v)) return true
else secondSet.push(v)
})
return { firstSet, secondSet }
}
const myLogicFunc = v => (v < 3 || v == 9)
const { firstSet, secondSet } = splitItems(myArr, myLogicFunc)
console.log(`My first set: ${firstSet}`) // My first set: 0,1,2,9
console.log(`My second set: ${secondSet}`) // My second set: 3,4,5,6,7,8,10
/* OR without destructuring:
const myArrays = splitItems(myArr, myLogicFunc)
console.log(`My first set: ${myArrays.firstSet}`)
console.log(`My second set: ${myArrays.secondSet}`)
*/
Please let me know if you have any questions
In modern JavaScript apps we do not mutate arrays we create new array, this avoids side effects, so what we do is create two new arrays
const split = (source, conditionFunc) = [ source.filter(i => conditionFunc(i)), source.filter(i => !conditionFunc(i))];
Then you have an array of two arrays of the values that meed condition and those that don't and you have not caused any side effects.
const odssAndEvens = split(source, i => i % 2 === 1);
Or with reduce so you don't iterate the array twice
const split = (source, conditionFunc) = source.reduce((results, item) => {
if (conditionFunc(item)) {
results[0].push(item);
} else {
results[1].push(item);
}
return results;
}, [[],[]]);

Categories

Resources