(Deep?) copy Map in JavaScript [duplicate] - javascript

This question already has answers here:
Shallow-clone a Map or Set
(6 answers)
Closed 2 years ago.
How can I deep copy a map object to another map ? I'm trying to use ES6 method but this return Map(0){} empty object. My first try was send a map object to function and assign this to a new value in this class. But on next() method I'm removing one property from this map and this change map in unit test file
creatureTurnQueue.js
export default class CreatureTurnQueue {
constructor() {
this.creatureMap = new Map();
this.creatureArray = [];
this.observersArray = [];
}
initQueue(list = {}) {
this.creatureMap = Object.assign({}, list)
this.creatureMap = list //<=old method
list.forEach(val => this.creatureArray.push(val));
}
getActiveCreature() {
let [first] = this.creatureArray.filter(el => el);
return first;
}
next(list = {}) {
this.creatureMap.delete(this.creatureMap.keys().next().value);
if (this.creatureMap.size == 0) {
this.notifyObserver();
this.initQueue(list);
return true;
}
}
addObserver(_observer) {
this.observersArray.push(_observer)
}
removeObserver(_observer) {
this.observersArray.pull(_observer)
}
notifyObserver() {
this.observersArray.forEach(item => item.resetCounterAttack())
}
}
creatureTurnQueueTest.js
import Creature from "../creature.js";
import CreatureTurnQueue from "../creatureTurnQueue.js";
import Point from './../point';
export default class CreatureTurnQueueTest {
queueShoulChangeActiveCreature() {
let creatureTurnQueue = new CreatureTurnQueue();
let creture1 = new Creature("aaaa", 1, 1, 1, 1);
let creture2 = new Creature("bbbb", 1, 1, 1, 1);
let creture3 = new Creature("cccc", 1, 1, 1, 1);
let point1 = new Point(1, 0)
let point2 = new Point(2, 0)
let point3 = new Point(3, 0)
let creatureMap = new Map();
creatureMap.set(point1, creture1);
creatureMap.set(point2, creture2);
creatureMap.set(point3, creture3);
creatureTurnQueue.initQueue(creatureMap);
console.log("~ creatureMap", creatureMap) <= map have 2 elements
creatureMap.forEach(item => { <= creatureMap return 2 elements becouse this one value is removed
if (item !== creatureTurnQueue.getActiveCreature()) {
console.log("~ item", item)
console.log("~ creatureTurnQueue.getActiveCreature()", creatureTurnQueue.getActiveCreature())
throw `Exception: => Kolejka nie dziala poprawnie zwracana aktywna creatura jest inna`;
}
if (creatureTurnQueue.next(creatureMap)) {
throw `Exception: => Kolejka nie dziala poprawnie w momecie wywolania funkcji next()`;
}
});
}
}
The rest of a classes
point.js
export default class Point {
constructor(_x, _y) {
this.x = _x;
this.y = _y;
}
}
creature.js
import CreatureStatistics from "./creatureStatistics.js";
export default class Creature {
constructor(_name, _attack, _armor, _maxHp, _moveRange) {
this.stats = this.createCreature(_name, _attack, _armor, _maxHp, _moveRange);
this.stats.currentHp = this.stats.maxHp;
this.stats.wasCounterAttack = false;
}
createCreature(_name, _attack, _armor, _maxHp, _moveRange) {
return new CreatureStatistics(
_name || "Smok",
_attack || 1,
_armor || 1,
_maxHp || 100,
_moveRange || 10
);
}
setDefaultStats() {
// this.stats.wasCounterAttack == true ? this.stats.wasCounterAttack = false : this.stats.wasCounterAttack = true
this.stats.currentHp = this.stats.currentHp != undefined ? this.stats.currentHp : this.stats.maxHp;
}
// popraw counter atack
attack(_defender) {
_defender.setDefaultStats();
this.setDefaultStats();
if (_defender.isAlive()) {
_defender.stats.currentHp = this.calculateDamage(_defender);
if (_defender.isAlive() && !_defender.stats.wasCounterAttack) {
_defender.stats.wasCounterAttack = true;
this.stats.currentHp = _defender.calculateDamage(this);
}
}
}
calculateDamage(attackedCreature) {
return attackedCreature.stats.currentHp - this.stats.getAttack() + attackedCreature.stats.getArmor() > attackedCreature.stats.getMaxHp()
? attackedCreature.stats.currentHp
: attackedCreature.stats.currentHp - this.stats.getAttack() + attackedCreature.stats.getArmor();
}
isAlive() {
if (this.stats.currentHp > 0) {
return true;
}
}
getCurrentHp() {
return this.stats.currentHp;
}
resetCounterAttack() {
this.stats.wasCounterAttack = false;
}
canCounterAttack() {
return !this.stats.wasCounterAttack
}
}
creatureStatistics.js
export default class CreatureStatistics {
constructor(_name, _attack, _armor, _maxHp, _moveRange) {
this.name = _name;
this.attack = _attack;
this.armor = _armor;
this.maxHp = _maxHp;
this.moveRange = _moveRange;
}
getName() {
return this.name;
}
getAttack() {
return this.attack;
}
getArmor() {
return this.armor;
}
getMaxHp() {
return this.maxHp;
}
getMoveRange() {
return this.moveRange;
}
}

The map was in fact a reference of the creatureMap from unit test script. Modifying the map inside your class will modify the creatureMap in unit test. You can try to create a new Map and copy over all values:
// Start class CreateTurnQueue
initQueue(list = {}) {
const newMap = new Map();
// Both Object and Map has entries method although the order is different
const iterator = list.entries();
for(const item of iterator) {
const [point, creature] = item;
newMap.set(point, creature);
this.creatureArray.push(creature);
}
this.creatureMap = newMap;
}
// End class CreateTurnQueue
Now you don't have a reference to the creatureMap from the unit test script and modify this.creatureMap will not affect the one you passed to the initQueue method as argument.

Related

How to get variable in set method

I have an issue with getting a variable in a setter method when I create a class.
In the picture above you can see my method "set quest" returns this (num 1) instead of this.questId (num 2) . How can I get this.questId in "set" and what I am doing wrong? Thank you in advance.
class TestItem {
constructor(testItem) {
this.quest = testItem.quest;
this.answList = testItem.answList;
this.questId = testItem.id;
}
set quest(value) {
console.log(this , this.questId);
let questItem = document.createElement('div');
questItem.classList.add('questItem');
questItem.textContent = `${this.questId}. ${value}`;
this._quest = questItem;
}
set answList(value) {
this._answList = value.map((item, i) => {
let answItem = document.createElement('span');
answItem.classList.add('answItem');
answItem.setAttribute('data-value', item.val);
answItem.textContent = item.answ;
return answItem;
});
}
getQuestBlock() {
let questBlock = document.createElement('div');
questBlock.classList.add(`quest${this.questId}`);
questBlock.append(this._quest);
this._answList.forEach(item => questBlock.append(item));
console.log(questBlock);
return questBlock;
}
}
function createTest(testData) {
let testContainer = document.querySelector('#testContainer');
testData.forEach((item, i) => {
let testItem = new TestItem(item);
let questBlock = testItem.getQuestBlock();
testContainer.append(questBlock);
});
}
document.addEventListener("DOMContentLoaded", createTest.bind(this ,testData));

Method chaining with Javascript - Plus Minus Question

I got a code challenge that create a plus-minus functions using method chaining. i have created the code as follows but it eventually failed when it comes to the output rendering
like plus(3).minus(2).value() and minus(3).minus(3).value() kind of method invoking
code as follows
function plus(valuez)
{
this.valuez = this.valuez+ valuez;
function value{
return valuez
}
plus.minus = minus;
plus.value = value;
plus.plus = this;
return this;
}
function minus(valuez)
{
this.valuez = this.v+ valuez;
function value(){
return valuez
}
minus.plus = plus;
minus.minus = this
minus.value = value;
return this;
}
expected output is
1 and 6 but I only get the printed last number entered. how can I resolve this?
class Box {
constructor(v) { this._value = v }
plus(v) { this._value += v; return this; }
minus(v) { this._value -= v; return this; }
value() { return this._value; }
}
function plus(v) { return new Box(v) }
function minus(v) { return new Box(-v) }
console.log("plus(3).minus(2).value()", plus(3).minus(2).value());
console.log("minus(3).minus(3).value()", minus(3).minus(3).value());
function plus (x) { return { _value: x, plus(y){ return plus(this._value+y) }, minus(y){ return plus(this._value-y) }, value(){ return this._value } } }
function minus(x) { return plus(-x) }
console.log("plus(3).minus(2).value()", plus(3).minus(2).value());
console.log("minus(3).minus(3).value()", minus(3).minus(3).value());
Using closure
function plus (x) { return { plus(y){ return plus(x+y) }, minus(y){ return plus(x-y) }, value(){ return x } } }
function minus(x) { return plus(-x) }
console.log("plus(3).minus(2).value()", plus(3).minus(2).value());
console.log("minus(3).minus(3).value()", minus(3).minus(3).value());
Without a constructor or class-sugar you can return an object using closed over values from the addition/subtraction method. Something like:
const { plus, val, reset } = PM();
console.log(plus().plus(3).minus(4).plus(25).plus(4).trace());
console.log(reset().minus(2).plus(33).val());
function PM() {
let values = [];
let traced = [];
const reset = () => {
values = [];
traced = [];
return ret;
};
const add = value => {
const calc = +(values[values.length-1] || 0) + value;
traced.push(`${values[values.length-1] || 0}${
!value || value >= 0 ? "+" : ""}${value || 0}=${calc || 0}`);
values.push(calc || 0);
return ret;
};
const ret = {
plus: value => add(value),
minus: value => add(-value),
val: () => values,
trace: () => traced,
reset,
};
return ret;
}
Or combine this with an embedded constructor

Trampoline based linked list (lisp tree) to string with cycles

I have problem with my trampoline based function that stringify lisp list. Here is the code:
function Pair(car, cdr) {
this.car = car;
this.cdr = cdr;
}
const nil = new function Nil() {};
// ----------------------------------------------------------------------
Pair.fromArray = function(array) {
var result = nil;
var i = array.length;
while (i--) {
let car = array[i];
if (car instanceof Array) {
car = Pair.fromArray(car);
}
result = new Pair(car, result);
}
return result;
};
// ----------------------------------------------------------------------
function Thunk(fn, cont = () => {}) {
this.fn = fn;
this.cont = cont;
}
// ----------------------------------------------------------------------
Thunk.prototype.toString = function() {
return '#<Thunk>';
};
// ----------------------------------------------------------------------
function trampoline(fn) {
return function(...args) {
return unwind(fn.apply(this, args));
};
}
// ----------------------------------------------------------------------
function unwind(result) {
while (result instanceof Thunk) {
const thunk = result;
result = result.fn();
if (!(result instanceof Thunk)) {
thunk.cont();
}
}
return result;
}
// ----------------------------------------------------------------------
// original function have different data types here
// with simplified version this is fine
function toString(x) {
return x.toString();
}
// ----------------------------------------------------------------------
const pair_to_string = (function() {
const prefix = (pair, rest) => {
var result = [];
if (pair.ref) {
result.push(pair.ref + '(');
} else if (!rest) {
result.push('(');
}
return result;
};
const postfix = (pair, rest) => {
if (!rest || pair.ref) {
return [')'];
}
return [];
};
return trampoline(function pairToString(pair, quote, extra = {}) {
const {
nested,
result = [],
cont = () => {
result.push(...postfix(pair, nested));
}
} = extra;
result.push(...prefix(pair, nested));
let car;
if (pair.cycles && pair.cycles.car) {
car = pair.cycles.car;
} else {
car = toString(pair.car, quote, true, { nested: false, result, cont });
}
if (car !== undefined) {
result.push(car);
}
return new Thunk(() => {
if (pair.cdr instanceof Pair) {
if (pair.cycles && pair.cycles.cdr) {
result.push(' . ');
result.push(pair.cycles.cdr);
} else {
if (pair.cdr.ref) {
result.push(' . ');
} else {
result.push(' ');
}
return pairToString(pair.cdr, quote, {
nested: true,
result,
cont
});
}
} else if (pair.cdr !== nil) {
result.push(' . ');
result.push(toString(pair.cdr, quote));
}
}, cont);
});
})();
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote) {
var result = [];
pair_to_string(this, quote, {result});
return result.join('');
};
// ----------------------------------------------------------------------
function range(n) {
return new Array(n).fill(0).map((_, i) => i);
}
// ----------------------------------------------------------------------------
function markCycles(pair) {
var seen_pairs = [];
var cycles = [];
var refs = [];
function visit(pair) {
if (!seen_pairs.includes(pair)) {
seen_pairs.push(pair);
}
}
function set(node, type, child, parents) {
if (child instanceof Pair) {
if (parents.includes(child)) {
if (!refs.includes(child)) {
refs.push(child);
}
if (!node.cycles) {
node.cycles = {};
}
node.cycles[type] = child;
if (!cycles.includes(node)) {
cycles.push(node);
}
return true;
}
}
}
const detect = trampoline(function detect_thunk(pair, parents) {
if (pair instanceof Pair) {
delete pair.ref;
delete pair.cycles;
visit(pair);
parents.push(pair);
var car = set(pair, 'car', pair.car, parents);
var cdr = set(pair, 'cdr', pair.cdr, parents);
var thunks = [];
if (!car) {
detect(pair.car, parents.slice());
}
if (!cdr) {
const cdr_args = [pair.cdr, parents.slice()];
return new Thunk(() => {
return detect_thunk(...cdr_args);
});
}
}
});
function mark_node(node, type) {
if (node.cycles[type] instanceof Pair) {
const count = ref_nodes.indexOf(node.cycles[type]);
node.cycles[type] = `#${count}#`;
}
}
detect(pair, []);
var ref_nodes = seen_pairs.filter(node => refs.includes(node));
ref_nodes.forEach((node, i) => {
node.ref = `#${i}=`;
});
cycles.forEach(node => {
mark_node(node, 'car');
mark_node(node, 'cdr');
});
}
// ----------------------------------------------------------------------
// this works fine
//console.log(Pair.fromArray([[[range(8000), range(10)]]]).toString());
var data = new Pair(1, new Pair(new Pair(2, nil), new Pair(3, nil)));
data.cdr.car.cdr = data.cdr;
data.cdr.cdr.cdr = data;
markCycles(data)
console.log(data.toString());
console.log("#0=(1 . #1=((2 . #1#) 3 . #0#)) - valid");
The problem is the last parenthesis is missing, I'm not sure how I should use continuation in trampoline to fix the issue.
Here is the code that was working without trampoline:
// ----------------------------------------------------------------------
Pair.prototype.toString = function(quote, rest) {
var arr = [];
if (this.ref) {
arr.push(this.ref + '(');
} else if (!rest) {
arr.push('(');
}
var value;
if (this.cycles && this.cycles.car) {
value = this.cycles.car;
} else {
value = toString(this.car, quote, true);
}
if (value !== undefined) {
arr.push(value);
}
if (this.cdr instanceof Pair) {
if (this.cycles && this.cycles.cdr) {
arr.push(' . ');
arr.push(this.cycles.cdr);
} else {
if (this.cdr.ref) {
arr.push(' . ');
} else {
arr.push(' ');
}
const cdr = this.cdr.toString(quote, true);
arr.push(cdr);
}
} else if (this.cdr !== nil) {
arr = arr.concat([' . ', toString(this.cdr, quote, true)]);
}
if (!rest || this.ref) {
arr.push(')');
}
return arr.join('');
};
I have two cases that should work first big list (8000 elements) and small cycle. With the code in stack snippet it work with long list but not with cycles without the trampoline it overflow the stack on big list. Also it's lisp so it need to work any any tree not only linked list.
EDIT: if you try to answer please at least don't change the data structures. It need to be Pair class with car and cdr and cycles need to be calculated before they are converted to string. So it work with multiple functions that check if data in memory is cycle.
This is what I would do.
const pure = value => ({ constructor: pure, value });
const bind = (monad, arrow) => ({ constructor: bind, monad, arrow });
const thunk = eval => ({ constructor: thunk, eval });
function evaluate(expression) {
let expr = expression;
let stack = null;
while (true) {
switch (expr.constructor) {
case pure:
if (stack === null) return expr.value;
expr = stack.arrow(expr.value);
stack = stack.stack;
break;
case bind:
stack = { arrow: expr.arrow, stack };
expr = expr.monad;
break;
case thunk:
expr = expr.eval();
}
}
}
const monadic = func => thunk(() => {
const gen = func();
function next(data) {
const { value, done } = gen.next(data);
return done ? value : bind(value, next);
}
return next(undefined);
});
class Pair {
constructor(car, cdr) {
this.car = car;
this.cdr = cdr;
}
static fromArray(array) {
const loop = (array, index) => monadic(function* () {
if (index === array.length) return pure(null);
const item = array[index];
const car = Array.isArray(item) ? yield loop(item, 0) : item;
const cdr = yield loop(array, index + 1);
return pure(new Pair(car, cdr));
});
return evaluate(loop(array, 0));
}
duplicates() {
const visited = new WeakSet();
const result = new WeakSet();
const loop = pair => monadic(function* () {
if (visited.has(pair)) {
result.add(pair);
} else {
visited.add(pair);
const { car, cdr } = pair;
if (car instanceof Pair) yield loop(car);
if (cdr instanceof Pair) yield loop(cdr);
}
return pure(result);
});
return evaluate(loop(this));
}
toString() {
let result = "";
const duplicates = this.duplicates();
const visited = [];
const loop = (pair, end) => monadic(function* () {
const index = visited.indexOf(pair);
if (index < 0) {
const duplicate = duplicates.has(pair);
if (duplicate) {
const last = visited.push(pair) - 1;
result += end ? ` . #${last}=(` : `#${last}=(`;
} else result += end ? " " : "(";
const { car, cdr } = pair;
if (car instanceof Pair) yield loop(car, false);
else result += JSON.stringify(car);
if (cdr instanceof Pair) yield loop(cdr, true);
else if (cdr === null) result += ")";
else result += ` . ${JSON.stringify(cdr)})`;
if (duplicate && end) result += ")";
} else {
result += end ? ` . #${index}#)` : `#${index}#`;
}
return pure(result);
});
return evaluate(loop(this, false));
}
}
const data = new Pair(1, new Pair(new Pair(2, null), new Pair(3, null)));
data.cdr.car.cdr = data.cdr;
data.cdr.cdr.cdr = data;
console.log(data.toString());
const range = length => Array.from({ length }, (x, i) => i);
console.log(Pair.fromArray([[[range(8000), range(10)]]]).toString());
Hope that helps.

Implementing Dijkstra in Javascript

I am trying to get an implementation of Dijkstra's algorithm in JavaScript to run properly.
I found a walkthrough Here and do not understand the return line
return `Path is ${path} and time is ${times[endNode]}
I have collected the code from the link below to make it easier.
class Graph {
constructor() {
this.nodes = [];
this.adjacencyList = [];
}
addNode(node) {
this.nodes.push(node);
this.adjacencyList[node] = [];
}
addEdge(node1, node2, weight) {
this.adjacencyList[node1].push({node:node2, weight: weight});
this.adjacencyList[node2].push({node:node1, weight: weight});
}
findPathWithDijkstra(startNode, endNode) {
let times = [];
let backtrace = [];
let pq = new PriorityQueue();
times[startNode] = 0;
this.nodes.forEach(node => {
if (node !== startNode) {
times[node] = Infinity
}
});
pq.enqueue([startNode, 0]);
while (!pq.isEmpty()) {
let shortestStep = pq.dequeue();
let currentNode = shortestStep[0];
this.adjacencyList[currentNode].forEach(neighbor => {
let time = times[currentNode] + neighbor.weight;
if (time < times[neighbor.node]) {
times[neighbor.node] = time;
backtrace[neighbor.node] = currentNode;
pq.enqueue([neighbor.node, time]);
}
});
}
let path = [endNode];
let lastStep = endNode;
while(lastStep !== startNode) {
path.unshift(backtrace[lastStep])
lastStep = backtrace[lastStep]
}
return `Path is ${path} and time is ${times[endNode] }`
}
}
class PriorityQueue {
constructor() {
this.collection = [];
}
enqueue(element){
if (this.isEmpty()){
this.collection.push(element);
} else {
let added = false;
for (let i = 1; i <= this.collection.length; i++){
if (element[1] < this.collection[i-1][1]){
this.collection.splice(i-1, 0, element);
added = true;
break;
}
}
if (!added){
this.collection.push(element);
}
}
};
dequeue() {
let value = this.collection.shift();
return value;
};
isEmpty() {
return (this.collection.length === 0)
};
}
let testGraph01 = new Graph();
let testGraph02 = new Graph();
let testGraph03 = new Graph();
//creating graphs
testGraph01.addNode("A");
testGraph01.addNode("B");
testGraph01.addNode("C");
testGraph01.addNode("D");
testGraph01.addEdge("A","B",10);
testGraph01.addEdge("C","D",5);
testGraph01.addEdge("D","B",5);
console.log(testGraph01.findPathWithDijkstra("A","D"));
I am looking for an explanation of the templated return. How does it work in javascript i guess?

Javascript Composite pattern, can not use overwritten methods correctly

I have a javascript compositer pattern which i implemented (see code below).
In my main class i instantiate either the MenuItem or the Menu. I have to call the method update() on the component and they should return the corresponding code.
However it doesnt return the correct amount of totalitems. it alwasy returns the default value 0 which is defined in MenuComponent.
I think it has something to do with the this keyword but i can not find the exact solution.
MenuItem:
//MENU ITEM
//----------
var MenuItem = function(id) {
MenuComponent.apply(this, [id, name]);
};
MenuItem.prototype = Object.create(MenuComponent.prototype);
MenuItem.prototype.constructor = MenuItem;
MenuItem.prototype.update = function() {
//works
console.log(this.ingredients)
//Doesnt work, this should display same as this.ingredients
console.log(this.calculateIngredients())
console.log("--------------")
};
Menu:
//MENU
//--------
var Menu = function(id, name) {
MenuComponent.apply(this, [id, name]);
this.menuitems = [];
};
Menu.prototype = Object.create(MenuComponent.prototype);
Menu.prototype.constructor = Menu;
Menu.prototype.add = function(menuitem) {
this.menuitems.push(menuitem);
};
Menu.prototype.remove = function(menuitem) {
for(var s, i = 0; s = this.getMenuItem(i); i++) {
if(s == menuitem) {
this.menuitems.splice(i, 1);
return true;
}
if(s.remove(menuitem)) {
return true;
}
}
return false;
};
Menu.prototype.getMenuItem = function(i) {
return this.menuitems[i];
};
Menu.prototype.calculateIngredients = function() {
this.ingredients = 0;
for(var key in this.menuitems) {
this.ingredients += this.menuitems[key].calculateIngredients();
}
return this.ingredients;
};
MenuComponent
//MenuComponent
//-------------
var MenuComponent = function(id, name) {
if(this.constructor === MenuComponent) {
throw new Error("Can't instantiate abstract class");
}
this.id = id;
this.name = name;
this.ingredients = 0;
};
MenuComponent.prototype.calculateIngredients = function() {
return this.ingredients;
};
MenuComponent.prototype.update = function() {
console.log(this.ingredients)
console.log("-----------------")
};
example
// HANDLER
var menuitem1 = new MenuItem(1)
, menuitem2 = new MenuItem(2)
, menuitem3 = new MenuItem(3)
, menuitem4 = new MenuItem(4)
, menuitem5 = new MenuItem(5)
, menuitem6 = new MenuItem(6)
, menuitem7 = new MenuItem(7)
, menu = new Menu(1);
menu.add(menuitem1);
menu.add(menuitem2);
menu.add(menuitem3);
menu.add(menuitem4);
menuitem1.ingredients = 1
menuitem2.ingredients = 5
menuitem3.ingredients = 7;
menuitem4.ingredients = 2
// lets say i want to update the ingredient count of the following
menuitem1.update();
menuitem2.update();
menu.update();
//the update goes wrong, it doesnt display the correct amount, it alwasy displays 0 on the amounts where i commented
JSFiddle
Instead of
MenuComponent.prototype.update = function() {
console.log(this.ingredients) // 0
};
You want to call
MenuComponent.prototype.update = function() {
console.log(this.calculateIngredients()) // 15
};
whole code on jsfiddle: http://jsfiddle.net/krzysztof_safjanowski/gjTb4/

Categories

Resources