# My solutions to exercises in 'Eloquent JavaScript'

Last update: December 10th, 2020

## Exercise: Arrays and Lists

### Exercise Description

A list is a nested set of objects, with the first object holding a reference to the second, the second to the third, and so on.

``````let list = {
value: 1,
rest: {
value: 2,
rest: {
value: 3,
rest: null
}
}
};``````

Write a function `arrayToList` that builds up a list structure like the one shown when given `[1,2,3]` as argument. Also write a `listToArray` function that produces an array from a list. Then add a helper function `prepend`, which takes an element and a list and creates a new list that adds the element to the front of the input list, and `nth`, which takes a list and a number and returns the element at the given position in the list (with zero refeerring to the first element) or `underfined` when there is no such element.

### My solution

codepen (opens in a new tab)

``````function arrayToList(inputArray) {
let list = {};
if (inputArray.length > 0) {
list = prepend(inputArray, arrayToList(inputArray.slice(1)));
} else {
return null;
}
return list;
}

function prepend(el, list) {
return {value: el, rest: list};
}

console.log(arrayToList([1,3,5]))
// returns:
//
//  {
//  value:1,
//  rest: {
//      value:3,
//      rest: {
//          value:5,
//          rest:null
//          }
//      }
//  }

function listToArray(inputList) {
currentArray = [inputList.value];
if (inputList.rest) {
currentArray = currentArray.concat(listToArray(inputList.rest));
}
return currentArray;
}

console.log(listToArray({value: 1, rest: {value: 2, rest: {value:3, rest: null}}}))

// returns:
// [1, 2, 3]

function nth(list, nr) {
if (nr == 0) {
return list.value
} else {
return nth(list.rest, nr-1)
}
}

console.log(nth({value: 1, rest: {value: 2, rest: {value:3, rest: null}}},1))

// returns:
// 2``````

## Exercise: Deep Comparison

### Exercise Description

Write a function `deepEqual` that takes two values and returns true only if they are the same value or are objects with the same properties, where the values of the properties are equal when compared with a recursive call to `deepEqual`.

To find out whether values should be compared directly (use the `===` for that) or have their properties compared, you can use the `typeof` operator. If it produces `"object"` for both values, you should do a deep comparison. But you have to take on silly exception into account: because of a historical accident, `typeof null` also produces `"object"`.

### My solution (WIP)

codepen (opens in a new tab)

``````function deepEqual(first, second, indentation='') {
if (typeof first === typeof second) {
if (typeof first === "object") {
if (first !== null) {
firstKeys = Object.keys(first);
secondKeys = Object.keys(second);
if (firstKeys.length == secondKeys.length) {
trackInequalities = [];
for (i=0; i<=firstKeys.length-1; i++) {
trackInequalities.push(deepEqual(first[firstKeys[i]],second[secondKeys[i]], '    '));
}
return !trackInequalities.includes(false)
} else {
// Different amount of keys
return false;
}
} else {
// Both are null
return true;
}
} else {
return first === second
}
} else {
return false;
}
}

function test(assertion, expected, actual) {
console.log(assertion, expected === actual ? 'OK' : 'FAILED')
}

test('Shallow deepEqual with same identities should yield true', true, deepEqual(1,1))
test('Shallow deepEqual with different identities should yield false', false, deepEqual(1,2))
test('Deep deepEqual with same identities should yield true', true, deepEqual({value: 'test', rest: {value: 'test2', rest: null}}, {value: 'test', rest: {value: 'test2', rest: null}}))
test('Deep deepEqual with different identities should yield false', false, deepEqual({value: 'test', rest: {value: 'test2', rest: null}}, {value: 'test', rest: {value: 'diff', rest: null}}))
test('Deep deepEqual with identical objects containing arrays should yield true', true, deepEqual({value: 'test', rest: ['test', 'test2', 'test3']}, {value: 'test', rest: ['test', 'test2', 'test3']}))``````

## Exercise: Flattening

codepen (opens in a new tab)

Use the `reduce` method in combination with the `concat` method to "flatten" an array of arrays into a single array that has all the elements of the original arrays.

``````function flatten(inputArray) {
return inputArray.reduce((a,c) => {
if(Array.isArray(c)) {
return a.concat(flatten(c))
} else {
return a.concat(c);
}
}, [])
}

a = ['1', '2', ['3', ['4']]];

console.log(flatten(a)); // returns ["1, "2", "3", "4"]``````

codepen (opens in a new tab)

Write a higher-order function `loop` that provides something like a `for` loop statement. It takes a value, a test function, an update function, and a body function. Each iteration, it first runs the test function on the current loop value and stops if that returns false. Then it calls the body function, giving it the current value. Finally, it calls the update function to create a new value and starts from the beginning.

When defining the function, you can use a regular loops to do the actual looping.

``````function loop(value, testFunction, updateFunction, bodyFunction) {
console.log(value);
for(i=value; testFunction(i); i=updateFunction(i)) {
bodyFunction(i);
}
}

loop(0, i=>i<=10, i=>++i, console.log); // Returns 1-10``````

## Exercise: Everything

codepen (opens in a new tab)

Implement every as a function that takes an array and a predicate function as parameters. Write two versions, one using a loop and one using the `some` method.

``````// Using a loop
function every(inputArray, predicate) {
trackInequality = [];
for (let el of inputArray) {
if (!predicate(el)) {
trackInequality.push(true)
}
}
if (trackInequality.length > 0) {
return false;
} else {
return true;
}
}

console.log(every([1,2,3,4,5], x=>x<10)) // returns true
console.log(every([6,7,8,9,10], x=>x<10)) // returns false

// Using the some method
function everySome(inputArray, predicate) {
return inputArray.some(predicate);
}

console.log(everySome([1,2,3,4,5], x=>x>=10)) // returns false
console.log(everySome([6,7,8,9,10], x=>x>=10)) // returns true``````

## Exercise: Dominant writing direction

codepen (opens in a new tab)

Write a function that computes the dominant writing direction in a string of text. Remember that each script object has a direction property that can be "ltr" (left to right), "rtl" (right to left), or "ttb" (top to bottom).

The dominant direction is the direction of a majority of the characters that have a script associated with them. The characterScript and countBy functions defined earlier in the chapter are probably useful here.

``````function dominantDirection(text) {
let scripts = countBy(text, char => {
let script = characterScript(char.codePointAt(0));
return script ? script.direction : 'none';
}).filter(({name}) => name != 'none');
let total = scripts.reduce((n, {count}) => n + count, 0);
if (total == 0) return "No scripts found"

if scripts.length == 0 return 'ltr';

return scripts.reduce((a,b) => a.count > b.count ? a : b).name

}

console.log(dominantDirection("Hello!"));
// → ltr
console.log(dominantDirection("Hey, مساء الخير"));
// → rtl``````

## Exercise: A Vector Type

Write a class `Vec` that represents a vector in two-dimensional space. It takes `x` and `y` parameters (numbers), which it should save to properties of the same name.

Give the `Vec` prototype two methods, `plus` and `minus`, that take another vector as a parameter and return a new vector that has the saum or difference of the two vectors' (`this` and the parameter) `x` and `y` values.

Add a getter property `length` to the prototype that computes the length of the vector–that is, the distance of the point `(x, y)` from the origin `(0,0)`.

``````class Vec {
constructor(x, y) {
this.x = x;
this.y = y;
}

plus(vector) {
let newVector = new Vec(vector.x + this.x, vector.y + this.y);
return newVector;
}

minus(vector) {
let newVector = new Vec(vector.x - this.x, vector.y - this.y);
return newVector;
}

get length() {
return Math.sqrt(this.x**2 + this.y**2);
}
}

exampleVector = new Vec(5,7);

console.log(exampleVector.length); // returns 8.6023
console.log(exampleVector.plus(new Vec(5,3))); // returns {x: 10, y: 10}``````

## Exercise: Groups

Write a class called `Group` (since `Set` is already taken). Like `Set`, it has `add`, `delete`, and `has` methods. Its constructor creates an empty group, `add` adds a value to the group (but only if it isn't already a member), `delete` removes its argument from the group (if it was a member), and `has` returns a Boolean value indicating whether its argument is a member of the group.

Use the `===` operator, or something equivalent such as `indexOf`, to determine whether two values are the same.

Give the class a static `from` method that takes an iterable object as argument and creates a group that contains all the values produced by iterating over it.

``````class Group {
constructor() {
this.group = [];
return this;
}

if (!this.has(value)) {
this.group.push(value);
return this;
}
}

delete(value) {
if (this.has(value)) {
this.group = this.group.filter(x => x !== value)
return this;
}
}

has(value) {
return this.group.includes(value)
}

from(iterableObject) {
for (let value of iterableObject) {
}
return this;
}
}``````

## Exercise: Iterable Groups

Make the `Group` class from the previous exercise iterable. Refer to the section about the iterator interface earlier in the chapter if you aren't clear on the exact form of the interface anymore.

``````class GroupIterator {
constructor(group) {
this.x = 0;
this.group = group.group;
}

next() {
if (this.x === this.group.length) return {done: true};

let value = this.group[this.x];
this.x++

return {value, done: false}
}
}

Group.prototype[Symbol.iterator] = function() {
return new GroupIterator(this);
}

for (let el of group.from([4,5,6])) {
console.log(el); // Returns 4 \ 5 \ 6
}``````

## Exercise: Borrowing a Method

Can you think of a way to call `hasOwnProperty` on an object that has its own property by that name?

``````class MyObject {
constructor() {
}

hasOwnProperty(thisVarWillBeIgnored) {
console.log("I do what I want");
}
}

let myObject = new MyObject("hopeful");

myObject.hasOwnProperty('test'); // returns "I do what I want"
console.log(Object.hasOwnProperty.call(this,'test'));  // returns false``````

## Exercise: Measuring a Robot

codepen (opens in a new tab)

Write a function `compareRobots` that takes two robots (and their starting memory). It should generate 100 tasks and let each of the robots solve each of these tasks. When done, it should output the average number of steps each robot took per task.

``````function compareRobots(robotOne, robotTwo) {
robotOneResults = [];
robotTwoResults = [];
for (let i=0; i<100; i++) {
let villageState = VillageState.random();
robotOneResults.push(runRobot(villageState, robotOne, []));
robotTwoResults.push(runRobot(villageState, robotTwo, []));
}
console.log(`Robot 1: \${getAverage(robotOneResults)}`);
console.log(`Robot 2: \${getAverage(robotTwoResults)}`);
}

function getAverage(inputArray) {
return inputArray.reduce((acc, c) => (acc + c)) / inputArray.length;
}

compareRobots(goalOrientedRobot, randomRobot); // See codepen or the book for the definition of these robots
// > Robot 1: 16.69
// > Robot 2: 92.04
``````

## Exercise: Robot Efficiency

codepen (opens in a new tab)

Can you write a robot that finishes the task faster than `goalOrientedRobot`? If you observe that robot's behavior, what obviously stupid thing does it do? How could those be improved?

Here's `goalOrientedRobot`:

``````function goalOrientedRobot({place, parcels}, route) {
if (route.length == 0) {
let parcel = parcels;
if(parcel.place != place) {
} else {
}
}
return {direction: route, memory: route.slice(1)}
}``````

One 'stupid' thing that `goalOrientedRobot` does is that when the route array is empty, it determines its next step based on the parcel with index `0` in the `parcels` array. It would probably be more efficient to, instead, determine which parcel is closest to the current location, and start with that one.

``````function nearestParcelRobot({place, parcels}, route) {
if (route.length == 0) {
let parcel = parcels[getNearestParcelIndex(place, parcels)];
if(parcel.place != place) {
} else {
}
}
return {direction: route, memory: route.slice(1)}
}

function getNearestParcelIndex(currentLocation, parcels) {
let nearestParcelLocation = '';
let shortestRouteSoFar = 99;
let index = -1;
for (let parcel of parcels) {
index++;
if (route.length < shortestRouteSoFar) {
shortestRouteSoFar = route.length;
console.log(shortestRouteSoFar);
indexForShortestRoute = index;
}
}
return indexForShortestRoute;
}

compareRobots(goalOrientedRobot, nearestParcelRobot);
// > Robot 1: 17.44
// > Robot 2: 16.35``````

## Exercise: Persistent Group

``````class PGroup {
constructor(inputArray) {
this.members = inputArray;
}

if (this.has(value)) return this;
return new PGroup([...this.members, value]);
}

delete(value) {
if (!this.has(value)) return this;
return new PGroup(this.members.filter(x => x !== value));
}

has(value) {
return this.members.includes(value);
}

}

PGroup.empty = new PGroup([]);