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

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.

Related

JavaScript to check which CSS classes are NOT in use on a page

I am building a JavaScript snippet that I am using to check which CSS classes are NOT in use on a page. Here is the code:
function httpGet(theUrl) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
function getAllClasses() {
//get inline css classes
let headContent = document.getElementsByTagName('head')[0].innerHTML;
let classes = getAllCSSClasses(headContent);
//get external linraties
let csses = document.querySelectorAll('link[rel="stylesheet"]');
for (i = 0; i < csses.length; ++i) {
if (csses[i].href && csses[i].href.indexOf("cookieconsent") == -1){
let styledata = httpGet(csses[i].href);
let cclasses = getAllCSSClasses(styledata);
if ( cclasses instanceof Array )
classes = classes.concat( cclasses );
else
classes.push( cclasses );
//console.log(csses[i].href)
//classes = Object.assign([], classes, cclasses);
//console.log(classes)
//classes.concat(cclasses);
}
}
return classes;
}
function getAllCSSClasses(cssdata) {
var re = /\.[a-zA-Z_][\w-_]*[^\.\s\{#:\,;]/g;
var m;
let classes = [];
do {
m = re.exec(cssdata);
if (m) {
for(let key in m) {
if(
(typeof m[key] == "string") &&
(classes.indexOf(m[key]) == -1) &&
(m[key].indexOf(".") == 0)
)
classes.push(m[key].replace(/\s/g, " "));
}
}
} while (m);
return classes;
}
function getHTMLUsedClasses() {
var elements = document.getElementsByTagName('*');
var unique = function (list, x) {
if (x != "" && list.indexOf(x) === -1) {
list.push(x);
}
return list;
};
var htmlclasses = [].reduce.call(elements, function (acc, e) {
if (typeof e.className == "string"){
return [].slice.call(e.classList).reduce(unique, acc);
} else {
return [];
}
}, []);
return htmlclasses;
}
function getUndefinedClasses(cssclasses, htmlclasses) {
var undefinedclasses = [];
for (let key in htmlclasses) {
if(cssclasses.indexOf("." + htmlclasses[key]) == -1 ) {
undefinedclasses.push(htmlclasses[key]);
}
}
return undefinedclasses;
}
var cssclasses = getAllClasses();
var htmlclasses = getHTMLUsedClasses();
var un = getUndefinedClasses(cssclasses, htmlclasses);
console.log(un )
//copy(un);
Now, the problem is in getHTMLUsedClasses, it doesn't work well.
For example, HTML has an 'img-responsive' class, but the output doesn't show it
var unique = function (list, x) {
if (x != "" && list.indexOf(x) === -1) {
//img-responsive here exists
list.push(x);
}
return list;
};
but:
var htmlclasses = [].reduce.call(elements, function (acc, e) {
if (typeof e.className == "string"){
return [].slice.call(e.classList).reduce(unique, acc);
} else {
return [];
}
}, []);
//doesn't exists in the final htmlclasses
return htmlclasses;
So I am guessing that [].slice.call(e.classList).reduce(unique, acc) is not working well. I don't quite understand what 'reduce' is doing here (I took this from another example). Can anyone explain?
I think I've found what was the issue. I read that "reduce() does not execute the function for array elements without values." and some classes can come empty so "return [];" will destroy all that is in the array so far. And instead I think that I have to return accumulator like this:
function getHTMLUsedClasses() {
var elements = document.getElementsByTagName('*');
var unique = function (list, x) {
if (x && list.indexOf(x) === -1) {
list.push(x);
}
return list;
};
var htmlclasses = [].reduce.call(elements, function (acc, e) {
if (e.classList.length > 0){
return [].slice.call(e.classList).reduce(unique, acc);
} else {
return acc;
}
}, []);
return htmlclasses;
}
Now the resulting array looks much better, I have in it "img-responsive".
Here is the whole code again:
function httpGet(theUrl) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
function getAllClasses() {
//get inline css classes
let headContent = document.getElementsByTagName('head')[0].innerHTML;
let classes = getAllCSSClasses(headContent);
//get external linraties
let csses = document.querySelectorAll('link[rel="stylesheet"]');
for (i = 0; i < csses.length; ++i) {
if (csses[i].href && csses[i].href.indexOf("cookieconsent") == -1){
let styledata = httpGet(csses[i].href);
let cclasses = getAllCSSClasses(styledata);
if ( cclasses instanceof Array )
classes = classes.concat( cclasses );
else
classes.push( cclasses );
}
}
return classes;
}
function getAllCSSClasses(cssdata) {
var re = /\.[a-zA-Z_][\w-_]*[^\.\s\{#:\,;]/g;
var m;
let classes = [];
do {
m = re.exec(cssdata);
if (m) {
for(let key in m) {
if(
(typeof m[key] == "string") &&
(classes.indexOf(m[key]) == -1) &&
(m[key].indexOf(".") == 0)
)
classes.push(m[key].replace(/\s/g, " "));
}
}
} while (m);
return classes;
}
function getHTMLUsedClasses() {
var elements = document.getElementsByTagName('*');
var unique = function (list, x) {
if (x && list.indexOf(x) === -1) {
list.push(x);
}
return list;
};
var htmlclasses = [].reduce.call(elements, function (acc, e) {
if (e.classList.length > 0){
return [].slice.call(e.classList).reduce(unique, acc);
} else {
return acc;
}
}, []);
return htmlclasses;
}
function getUndefinedClasses(cssclasses, htmlclasses) {
var undefinedclasses = [];
for (let key in htmlclasses) {
if(cssclasses.indexOf("." + htmlclasses[key]) == -1 ) {
undefinedclasses.push(htmlclasses[key]);
}
}
return undefinedclasses;
}
//console.log(cssclasses)
var cssclasses = getAllClasses();
var htmlclasses = getHTMLUsedClasses();
var un = getUndefinedClasses(cssclasses, htmlclasses);
console.log(un )
//copy(un);

Click event listener is being repeatedly triggered without any clicks?

I am developing a "Battleship" game with two grids made up of divs and am currently attempting to add a click event listener to all of the divs.
The issue that I am having is that the event listener is being repeatedly triggered (until every single div has been clicked) when I refresh my page and I can't understand why...
Here's the event listener in question:
let aiGridCells = document.querySelectorAll(".ai-grid__game-cell");
aiGridCells.forEach(cell => {
cell.addEventListener("click", humanPlayer.humanAttack(cell.getAttribute('data-ai'),aiPlayer))
});
Where humanPlayer is an object that has been generated by a factory function:
const humanPlayer = playerFactory('human');
import gameboardFactory from './gameboardFactory';
const playerFactory = (name) => {
const playerBoard = gameboardFactory();
const humanAttack = (cell, player) => { // Where player is opponent
if (player.playerBoard.gameBoard[cell].id !== 'miss') {
player.playerBoard.receiveAttack(cell);
};
};
const aiAttack = (player) => { // Where player is opponent
const availableCells = [];
for (let i = 0; i < player.playerBoard.gameBoard.length; i++) {
if (player.playerBoard.gameBoard[i].id === null) {
availableCells.push(i);
};
};
const attackCell = Math.floor(Math.random() * availableCells.length);
player.playerBoard.receiveAttack(attackCell);
};
return {
name,
playerBoard,
humanAttack,
aiAttack
}
};
export default playerFactory;
and gameboardFactory is:
import shipFactory from './shipFactory';
const gameboardFactory = () => {
const gameBoard = [];
const shipYard = [];
const init = () => {
for (let i = 0; i<100; i++) {
gameBoard.push({id: null})
};
};
const checkValidCoordinates = (direction, start, end) => {
if (direction === 'horizontal') {
if ((start <= 9) && (end <= 9)) {
return true;
} else {
let newStart = (start/10).toString(10);
let newEnd = (end/10).toString(10);
if ((newStart.charAt(0)) === (newEnd.charAt(0))) {
return true;
};
};
} else {
if ((start <= 9) && (end <= 9)) {
return false
} else if (start <= 9) {
let newStart = start.toString(10);
let newEnd = end.toString(10);
if ((newStart.charAt(0)) === (newEnd.charAt(1))) {
return true;
};
} else {
let newStart = start.toString(10);
let newEnd = end.toString(10);
if ((newStart.charAt(1)) === (newEnd.charAt(1))) {
return true;
};
};
};
return false
};
const checkIfShipPresent = (direction, start, end) => {
if (direction === 'horizontal') {
for (let i = start; i <= end; i++) {
if (gameBoard[i].id !== null) {
return true;
}
};
return false;
} else {
for (let i = start; i <= end; i += 10) {
if (gameBoard[i].id !== null) {
return true;
}
};
return false;
};
};
const placeShip = (id, direction, start, end) => {
if (!checkValidCoordinates(direction, start, end)) {
return;
};
if (checkIfShipPresent(direction, start, end)) {
return;
};
const newShip = [];
if (direction === 'horizontal') {
for (let i = start; i <= end; i++) {
gameBoard[i].id = id;
newShip.push(i);
};
} else {
for (let i = start; i <= end; i += 10) {
gameBoard[i].id = id;
newShip.push(i);
};
};
shipYard.push(shipFactory(id, newShip));
};
const receiveAttack = (cell) => {
console.log(cell)
if (gameBoard[cell].id !== null) {
const attackedShip = shipYard.filter((ship) => {
return ship.id === gameBoard[cell].id;
})[0];
if (!attackedShip.hits.includes(cell)) {
attackedShip.hits.push(cell);
};
} else {
gameBoard[cell].id = 'miss';
};
};
const checkIfAllShipsSunk = () => {
let allShipsSunk = true;
shipYard.forEach((ship) => {
if (ship.isSunk() === false) {
allShipsSunk = false;
};
});
return allShipsSunk;
};
if (gameBoard.length === 0) {
init();
};
return {
gameBoard,
placeShip,
receiveAttack,
shipYard,
checkIfAllShipsSunk
};
};
export default gameboardFactory;
I'm completely lost to what the issue could be and have tried countless things to rectify it. Any suggestions would be hugely appreciated.
Thank you!
You trying to add actual function call as listener here:
let aiGridCells = document.querySelectorAll(".ai-grid__game-cell");
aiGridCells.forEach(cell => {
cell.addEventListener("click", humanPlayer.humanAttack(cell.getAttribute('data-ai'),aiPlayer))
});
So on your event listener initialization you actually call your function instead of passing it as a listener.
You can pass it like this instead:
aiGridCells.forEach(cell => {
cell.addEventListener("click", () => humanPlayer.humanAttack(cell.getAttribute('data-ai'),aiPlayer))
});

How fix warning "Expected to return a value in arrow function array-callback-return"

This is my code:
form.listPrice.map(list => {
if (list.id === listId) {
form.active = true
listPrice = parseInt(list.price)
if (list.offerPrice) {
listofferPrice = parseInt(list.offerPrice)
} else {
listofferPrice = null
}
}
})
And here:
n.listPrice.map(list => {
if (list.id === listPrice) {
valid = true;
n.active = true;
n.showPrice.price = list.price;
n.showPrice.offerPrice = list.offerPrice;
n.ladder = list.ladder;
}
And this output the same warning:
Expected to return a value in arrow function array-callback-return
You are using .map incorrectly. .map should be used only when you want to construct a new array from an old array, but your code is not doing that - you're only carrying out side-effects - the setting of form.active and listofferPrice.
The first step would be to use forEach or for..of instead, eg:
for (const list of form.listPrice) {
if (list.id === listId) {
form.active = true
listPrice = parseInt(list.price)
if (list.offerPrice) {
listofferPrice = parseInt(list.offerPrice)
} else {
listofferPrice = null
}
}
}
But since it looks like you're trying to find a possible single matching value in the array, .find would be more appropriate:
const found = form.listPrice.find(list => list.id === listId);
if (found) {
form.active = true
listPrice = parseInt(found.price)
if (found.offerPrice) {
listofferPrice = parseInt(found.offerPrice)
} else {
listofferPrice = null
}
}
const found = n.listPrice.find(list => list.id === listPrice);
if (found) {
valid = true;
n.active = true;
n.showPrice.price = found.price;
n.showPrice.offerPrice = found.offerPrice;
n.ladder = found.ladder;
}

(Deep?) copy Map in JavaScript [duplicate]

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.

List all possible paths using lodash

I would like to list all paths of object that lead to leafs
Example:
var obj = {
a:"1",
b:{
foo:"2",
bar:3
},
c:[0,1]
}
Result:
"a","b.foo","b.bar", "c[0]","c[1]"
I would like to find simple and readable solution, best using lodash.
Here is a solution that uses lodash in as many ways as I can think of:
function paths(obj, parentKey) {
var result;
if (_.isArray(obj)) {
var idx = 0;
result = _.flatMap(obj, function (obj) {
return paths(obj, (parentKey || '') + '[' + idx++ + ']');
});
}
else if (_.isPlainObject(obj)) {
result = _.flatMap(_.keys(obj), function (key) {
return _.map(paths(obj[key], key), function (subkey) {
return (parentKey ? parentKey + '.' : '') + subkey;
});
});
}
else {
result = [];
}
return _.concat(result, parentKey || []);
}
Edit: If you truly want just the leaves, just return result in the last line.
Doesn't use lodash, but here it is with recursion:
var getLeaves = function(tree) {
var leaves = [];
var walk = function(obj,path){
path = path || "";
for(var n in obj){
if (obj.hasOwnProperty(n)) {
if(typeof obj[n] === "object" || obj[n] instanceof Array) {
walk(obj[n],path + "." + n);
} else {
leaves.push(path + "." + n);
}
}
}
}
walk(tree,"tree");
return leaves;
}
Based on Nick answer, here is a TS / ES6 imports version of the same code
import {isArray,flatMap,map,keys,isPlainObject,concat} from "lodash";
// See https://stackoverflow.com/a/36490174/82609
export function paths(obj: any, parentKey?: string): string[] {
var result: string[];
if (isArray(obj)) {
var idx = 0;
result = flatMap(obj, function(obj: any) {
return paths(obj, (parentKey || '') + '[' + idx++ + ']');
});
} else if (isPlainObject(obj)) {
result = flatMap(keys(obj), function(key) {
return map(paths(obj[key], key), function(subkey) {
return (parentKey ? parentKey + '.' : '') + subkey;
});
});
} else {
result = [];
}
return concat(result, parentKey || []);
}
Feeding that object through this function should do it I think.
recursePaths: function(obj){
var result = [];
//get keys for both arrays and objects
var keys = _.map(obj, function(value, index, collection){
return index;
});
//Iterate over keys
for (var key in keys) {
//Get paths for sub objects
if (typeof obj[key] === 'object'){
var paths = allPaths(obj[key]);
for (var path in paths){
result.push(key + "." + path);
}
} else {
result.push(key);
}
}
return result;
}
Here is my function. It generates all possible paths with dot notation, assuming there are no property names containing spaces
function getAllPathes(dataObj) {
const reducer = (aggregator, val, key) => {
let paths = [key];
if(_.isObject(val)) {
paths = _.reduce(val, reducer, []);
paths = _.map(paths, path => key + '.' + path);
}
aggregator.push(...paths);
return aggregator;
};
const arrayIndexRegEx = /\.(\d+)/gi;
let paths = _.reduce(dataObj, reducer, []);
paths = _.map(paths, path => path.replace(arrayIndexRegEx, '[$1]'));
return paths;
}
Here's my solution. I only did it because I felt the other solutions used too much logic. Mine does not use lodash since I don't think it would add any value. It also doesn't make array keys look like [0].
const getAllPaths = (() => {
function iterate(path,current,[key,value]){
const currentPath = [...path,key];
if(typeof value === 'object' && value != null){
return [
...current,
...iterateObject(value,currentPath)
];
}
else {
return [
...current,
currentPath.join('.')
];
}
}
function iterateObject(obj,path = []){
return Object.entries(obj).reduce(
iterate.bind(null,path),
[]
);
}
return iterateObject;
})();
If you need one where the keys are indexed using [] then use this:
const getAllPaths = (() => {
function iterate(path,isArray,current,[key,value]){
const currentPath = [...path];
if(isArray){
currentPath.push(`${currentPath.pop()}[${key}]`);
}
else {
currentPath.push(key);
}
if(typeof value === 'object' && value != null){
return [
...current,
...iterateObject(value,currentPath)
];
}
else {
return [
...current,
currentPath.join('.')
];
}
}
function iterateObject(obj,path = []){
return Object.entries(obj).reduce(
iterate.bind(null,path,Array.isArray(obj)),
[]
);
}
return iterateObject;
})();
const allEntries = (o, prefix = '', out = []) => {
if (_.isObject(o) || _.isArray(o)) Object.entries(o).forEach(([k, v]) => allEntries(v, prefix === '' ? k : `${prefix}.${k}`, out));
else out.push([prefix, o]);
return out;
};
Array are returned as .0 or .1 that are compatible with _.get of lodash
const getAllPaths = (obj: object) => {
function rKeys(o: object, path?: string) {
if (typeof o !== "object") return path;
return Object.keys(o).map((key) =>
rKeys(o[key], path ? [path, key].join(".") : key)
);
}
return rKeys(obj).toString().split(",").filter(Boolean) as string[];
};
const getAllPaths = (obj) => {
function rKeys(o, path) {
if (typeof o !== "object") return path;
return Object.keys(o).map((key) =>
rKeys(o[key], path ? [path, key].join(".") : key)
);
}
return rKeys(obj).toString().split(",").filter(Boolean);
};
const test = {
a: {
b: {
c: 1
},
d: 2
},
e: 1
}
console.log(getAllPaths(test))

Categories

Resources