Why are different instances of the same class overwriting each other? - javascript

So my problem lies in playAI and in miniMaxAlgorithm.
To start off, I make a copy of the instance myTicTacToe which I call tempgame afterwards I call miniMaxAlgorithm on that copy. All this to make sure myTicTacToe stays unchanged.
The problem though is myTicTacToe has afterwards the same values as tempgame.
I don't understand why.
/**************************TicTacToe Class**********************/
class TicTacToe {
constructor(playField = [
['E', 'E', 'E'],
['E', 'E', 'E'],
['E', 'E', 'E']
], human = 'X', computer = 'O', gameStatus = "playing", currentPlayer = 'X') {
this.playField = playField;
this.human = human;
this.computer = computer;
this.gameStatus = gameStatus;
this.bestMove;
this.startingPlayer = human;
this.currentPlayer = currentPlayer;
}
reset() {
this.playField = [
['E', 'E', 'E'],
['E', 'E', 'E'],
['E', 'E', 'E']
];
this.gameStatus = 'playing';
this.currentPlayer = this.startingPlayer;
$('#gamestate').text('');
}
checkGameState() {
/******************** Win conditions******************************/
if (this.currentPlayer === 'X' || this.currentPlayer === 'O') {
if (this.winOrNot()) {
$('#gamestate').text('Player ' + this.currentPlayer + ' won.');
this.gameStatus = 'over';
}
//********************** Check if it is a draw***************/
else {
/*which results in a draw*/
let arrayOfFreePlaces = this.freePositions();
if (!(arrayOfFreePlaces.length > 0)) {
$('#gamestate').text('It is a draw');
this.gameStatus = 'over';
}
}
}
}
/***********************/
winOrNot() {
if ((this.playField[0][0] === this.currentPlayer && this.playField[0][1] === this.currentPlayer && this.playField[0][2] === this.currentPlayer) ||
(this.playField[1][0] === this.currentPlayer && this.playField[1][1] === this.currentPlayer && this.playField[1][2] === this.currentPlayer) ||
(this.playField[2][0] === this.currentPlayer && this.playField[2][1] === this.currentPlayer && this.playField[2][2] === this.currentPlayer) ||
(this.playField[0][0] === this.currentPlayer && this.playField[1][0] === this.currentPlayer && this.playField[2][0] === this.currentPlayer) ||
(this.playField[0][1] === this.currentPlayer && this.playField[1][1] === this.currentPlayer && this.playField[2][1] === this.currentPlayer) ||
(this.playField[0][2] === this.currentPlayer && this.playField[1][2] === this.currentPlayer && this.playField[2][2] === this.currentPlayer) ||
(this.playField[0][0] === this.currentPlayer && this.playField[1][1] === this.currentPlayer && this.playField[2][2] === this.currentPlayer) ||
(this.playField[0][2] === this.currentPlayer && this.playField[1][1] === this.currentPlayer && this.playField[2][0] === this.currentPlayer)
) {
return true;
} else {
return false;
}
}
freePositions() {
let emptyPositions = [];
for (let i = 0; i < this.playField.length; i++) {
for (let j = 0; j < this.playField[i].length; j++) {
if (this.playField[i][j] === 'E') {
emptyPositions.push([i, j]);
}
}
}
return emptyPositions;
}
// rate gamestate
rateField() {
// if computer wins +10
if (this.winOrNot(this.computer)) {
return 10;
}
// if human wins -10
else if (this.winOrNot(this.human)) {
return -10;
}
// if no one wins +0, aka drawm or not finished yet
else {
return 0;
}
}
}
//Prototypes of TicTacToe
TicTacToe.prototype.placeSign = function(row, column) {
// check if field is empty
if (this.playField[row][column] === 'E') {
if (this.currentPlayer === "X") {
this.playField[row][column] = 'X';
} else if (this.currentPlayer === "O") {
this.playField[row][column] = 'O';
}
this.checkGameState();
this.currentPlayer === this.computer ? this.currentPlayer = this.human : this.currentPlayer = this.computer;
} else {
console.log("Select an empty field!!");
}
};
/*******************Declarations*******************************/
let myTicTacToe = new TicTacToe();
let tempgame = new TicTacToe();
let bestMove;
/*****************Functions*************************/
// miniMaxAlgorithm
function miniMaxAlgorithm(TicTacToe1) {
/****************base case********************************/
// if the game is over , return rating
if (TicTacToe1.gameStatus === 'over') {
return TicTacToe1.rateField();
}
/******************************************/
//contains the rating of each move
let scores = [];
// containing the equivalent moves
let moves = [];
//fill the scores array
/**************************recursive case*******************************/
// create on array with containing all possible moves of the current tictactoe instance
let freeFields = TicTacToe1.freePositions();
for (let i = 0; i < freeFields.length; i++) {
//make a copy of the current tictactoe instance
let possibleTicTacToe = new TicTacToe(TicTacToe1.playField, TicTacToe1.human, TicTacToe1.computer, TicTacToe1.gameStatus, TicTacToe1.currentPlayer);
//play one of the possible moves
possibleTicTacToe.placeSign(freeFields[i][0], freeFields[i][1]);
// calling the function recursively until game is over
scores.push(miniMaxAlgorithm(possibleTicTacToe));
// adding place sign parameters ass an array inside moves
moves.push([freeFields[i][0], freeFields[i][1]]);
}
// Min Max Calculation
if (TicTacToe1.currentPlayer === TicTacToe1.computer) {
// search for the largest score and save its index in maxScoreIndex
let maxScoreIndex = 0;
for (let j = 1; j < scores.length; j++) {
if (scores[j] > scores[maxScoreIndex]) {
maxScoreIndex = j;
}
}
bestMove = moves[maxScoreIndex];
return scores[maxScoreIndex];
}
// tests best possible opponent moves (human)
else {
//
let minScoreIndex = 0;
for (let j = 1; j < scores.length; j++) {
if (scores[j] < scores[minScoreIndex]) {
minScoreIndex = j;
}
}
bestMove = moves[minScoreIndex];
return scores[minScoreIndex];
}
/**********************************************************************/
}
function updateFields() {
document.getElementById('field1').innerHTML = myTicTacToe.playField[0][0];
document.getElementById('field2').innerHTML = myTicTacToe.playField[0][1];
document.getElementById('field3').innerHTML = myTicTacToe.playField[0][2];
document.getElementById('field4').innerHTML = myTicTacToe.playField[1][0];
document.getElementById('field5').innerHTML = myTicTacToe.playField[1][1];
document.getElementById('field6').innerHTML = myTicTacToe.playField[1][2];
document.getElementById('field7').innerHTML = myTicTacToe.playField[2][0];
document.getElementById('field8').innerHTML = myTicTacToe.playField[2][1];
document.getElementById('field9').innerHTML = myTicTacToe.playField[2][2];
}
/**********************************************************/
//playAI
function playAI() {
//AI miniMaxEnd
tempgame = new TicTacToe (myTicTacToe.playField,myTicTacToe.human,myTicTacToe.computer,myTicTacToe.gameStatus,myTicTacToe.currentPlayer)
console.dir(myTicTacToe);
console.dir(tempgame);
miniMaxAlgorithm(tempgame);
console.dir(myTicTacToe);
console.dir(tempgame);
myTicTacToe.placeSign(bestMove[0],bestMove[1]);
//AI miniMaxEnd
updateFields();
}

I have an idea as to what may be causing this issue.
In javascript arrays (or lists) are passed by reference rather than value.
What this means that if you pass a function an array as a parameter it will pass a pointer to the list rather than a copy of the list.
In your TicTacToe class you have a variable called playField that is a list.
You create a clone of the TicTacToe object like this:
tempgame = new TicTacToe (myTicTacToe.playField, ... )
Here you are passing a reference to the existing playField list rather than cloning the list. i.e. Passing the address of the list rather than its contents.
Both your myTicTacToe and tempgame will use the same copy of the playField data. A change in one will cause a change in the other.
In order to create a new copy of the array it is standard to use the javascript slice operation:
tempgame = new TicTacToe (myTicTacToe.playField.slice(0), ... )
This operation will create a fresh copy of the array starting at the first element (0).
More information on using the slice operation to clone an array can be found here:
https://davidwalsh.name/javascript-clone-array

Related

Cannot get minimax function to work for tic tac toe game

const grabEmptySquares = (array) => {
var emptyGameSquares = [];
for (i = 0; i < 9; i++) {
if (!array[i]) emptyGameSquares.push(i);
}
return emptyGameSquares;
};
function findBestMove(board) {
var bestMove = {
index: null,
evaluation: null,
};
var availableMoves = grabEmptySquares(board);
availableMoves.forEach((move) => {
const simulGameboard = JSON.parse(JSON.stringify(board));
simulGameboard[move] = "o";
const evaluation = minimax(simulGameboard, 1, false);
const moveDetails = {
index: move,
evaluation: evaluation,
};
console.log(moveDetails)
if (evaluation > bestMove.evaluation || bestMove.evaluation === null) {
bestMove.index = move;
bestMove.evaluation = evaluation;
}
});
return bestMove.index;
}
function evaluate(board, isMaximizingPlayer, depth) {
var gameStatus = isGameOver(board);
if (gameStatus[0] != true) return;
if (gameStatus[1] === "win")
return isMaximizingPlayer ? +10 - depth : -10 + depth;
if (gameStatus[1] === "tie") return 0;
}
function minimax(board, depth, isMaximizingPlayer) {
var gameStatus = isGameOver(board);
if (gameStatus[0] == true) {
const evaluation = evaluate(board, !isMaximizingPlayer, depth);
return evaluation;
}
var simulGameboard = JSON.parse(JSON.stringify(board));
var availableMoves = grabEmptySquares(simulGameboard);
if (isMaximizingPlayer) {
bestVal = -Infinity;
availableMoves.forEach((move) => {
depth % 2 === 0
? (simulGameboard[move] = "o")
: (simulGameboard[move] = "x");
value = minimax(simulGameboard, depth + 1, false);
bestVal = Math.max(bestVal, value);
const moveDetails = {
index: move,
evaluation: bestVal,
depth: depth,
};
console.log(moveDetails);
});
return bestVal;
} else {
bestVal = Infinity;
availableMoves.forEach((move) => {
depth % 2 === 0
? (simulGameboard[move] = "o")
: (simulGameboard[move] = "x");
value = minimax(simulGameboard, depth + 1, true);
bestVal = Math.min(bestVal, value);
const moveDetails = {
index: move,
evaluation: bestVal,
depth: depth,
};
console.log(moveDetails);
});
return bestVal;
}
}
function isGameOver(array) {
var gameOver = false;
if (
(array[0] && array[0] === array[1] && array[0] === array[2]) ||
(array[3] && array[3] === array[4] && array[3] === array[5]) ||
(array[6] && array[6] === array[7] && array[6] === array[8])
) {
return (gameOver = [true, "win"]);
}
if (
(array[0] && array[0] === array[4] && array[0] === array[8]) ||
(array[2] && array[2] === array[4] && array[2] === array[6])
) {
return (gameOver = [true, "win"]);
}
if (
(array[1] && array[1] === array[4] && array[4] === array[7]) ||
(array[0] && array[0] === array[3] && array[3] === array[6]) ||
(array[2] && array[2] === array[5] && array[5] === array[8])
) {
return (gameOver = [true, "win"]);
}
if ([...array].every((index) => index)) {
return (gameOver = [true, "tie"]);
}
return (gameOver = [false, null]);
}
I followed https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-3-tic-tac-toe-ai-finding-optimal-move/ for direction, and as far as I can see, the logic is the same.
Still, my code doesn't come up with the correct moves. The evaluation my minimiax function gives to each move is wrong. And it is sooo wrong I cannot even begin to figure out where the code is off. Please help. I've spent the last two weeks working on this.
Ex:
var gameboard = [ null, "o", null, "x", "x", null, null, null, null ]
If I run findBestMove(gameboard), the expected output should be
bestMove = {index: 5,
evaluation: 0}
What I get instead is
bestMove = {index: 1,
evaluation: -8}.
In fact, every single move has the same evaluation.
This isn't the easist code to read, but AFAICT the minimax function copies the game board state once and then loops through possible moves with availableMoves.forEach. This means that when evaluating each possible move, it acts as if each previously considered move had been made. Move the copy inside the forEach and things should make somewhat more sense.
You already have this in the findBestMove function. I'd strongly suggest unifying findBestMove and minimax (and the sides of the isMaximizingPlayer branch inside minimax). Having very similar code in multiple places makes it hard to remember where you have and haven't fixed things.
I'd also suggest replacing the isMaximizingPlayer and depth%2 logic with a player variable that can be either "x" or "o", and multiplying goodness scores by -1 as needed. It'll be easier to keep track of.

How to sort based on incomplete criteria?

First I tried passing my own function to Array.sort, but it doesn't sort correctly. Notice how 'c' comes before 'a' in the result, even though the case if (b == 'a' && a == 'c') is handled correctly.
These data are just for example. My actual data is not to be alphabetically sorted. It must use the logic illustrated in the a_before_b and b_before_a functions.
Since I only have conditions to determine the relative ordering of SOME (NOT all) pairs of elements, there may be multiple valid orderings of elements. I just need to produce ANY valid ordering, where valid means does not contradict any of my conditions (which are defined in the a_before_b and b_before_a functions).
const sorted = ['a', 'b', 'c', 'd']; // I do NOT have access to this
const unsorted = ['c', 'd', 'a', 'b'];
const a_before_b = (a, b) => {
if (a == 'a' && b == 'd') return true;
if (a == 'b' && b == 'c') return true;
}
const b_before_a = (a, b) => {
if (b == 'a' && a == 'c') return true;
if (b == 'b' && a == 'c') return true;
}
const mySortingFunction = (a, b) => {
if (a_before_b(a, b)) return -1;
if (b_before_a(a, b)) return 1;
return 0;
}
// doesn't produce correct sorting
console.log(unsorted.sort(mySortingFunction)); // [ 'c', 'a', 'd', 'b' ]
Then I tried writing my own sort from scratch. But it enters an infinite loop and I don't know why.
const sorted = ['a', 'b', 'c', 'd'];
const unsorted = ['c', 'd', 'a', 'b'];
const a_before_b = (a, b) => {
if (a == 'a' && b == 'd') return true;
if (a == 'b' && b == 'c') return true;
}
const b_before_a = (a, b) => {
if (b == 'a' && a == 'c') return true;
if (b == 'b' && a == 'c') return true;
}
const findAnUnsortedElement = array => {
for (let [i, element] of Object.entries(array)) {
i = +i;
const a = element;
const b = array[i + 1];
if (b === undefined) return 'SORTING_COMPLETE';
if (!a_before_b(a, b)) console.log(a, 'should not be before', b);
if (b_before_a(a, b)) console.log(b, 'should be before', a);
if (!a_before_b(a, b) || b_before_a(a, b)) return a;
}
}
// from w3schools
function move(arr, old_index, new_index) {
while (old_index < 0) {
old_index += arr.length;
}
while (new_index < 0) {
new_index += arr.length;
}
if (new_index >= arr.length) {
var k = new_index - arr.length;
while ((k--) + 1) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
return arr;
}
// enters infinite loop, never returns
const myCustomSort = array => {
while (findAnUnsortedElement(array) != 'SORTING_COMPLETE') {
const element = findAnUnsortedElement(array);
const index = array.findIndex(el => el == element);
console.log('moving', element);
array = move(array, index, index + 1);
console.log(array);
}
return array;
}
console.log(myCustomSort(unsorted));
const unsorted = ['c', 'd', 'a', 'b'];
const sorted = unsorted.sort();
It should work I'm not sure what's your issue.
The algorithm in the answer I gave earlier on, and which you (first) accepted, is really based on a heuristic.
For a sorted output to be guaranteed to not have any violations, you could treat this problem as a graph problem. Whenever two values can make a comparison that gives true (with either comparator function), then that pair represents an edge in the graph.
If the order is consistent, then there must be one value that is the least among the others, otherwise you would have a cycle.
So with that knowledge we can determine for each node in the graph how long the longest path is to such a least node. When you find the longest distance to such a least node, you can use the length of that path as an absolute order indication.
Here is an implementation:
class Node {
constructor(value) {
this.value = value;
this.prev = new Set;
this.order = 0; // No order yet
}
orderWith(other) {
if (other === this) return;
if (a_before_b(this.value, other.value) || b_before_a(other.value, this.value)) {
other.prev.add(this);
} else if (a_before_b(other.value, this.value) || b_before_a(this.value, other.value)) {
this.prev.add(other);
}
}
setOrder(path = new Set) {
// Use recursion to find length of longest path to "least" node.
if (this.order) return; // already done
if (path.has(this)) throw "cycle detected";
let order = 1;
for (let prev of this.prev) {
prev.setOrder(path.add(this));
order = Math.max(order, prev.order + 1);
}
this.order = order; // If order is 1, it is a "least" node
}
}
const a_before_b = (a, b) => {
if (a == 'a' && b == 'd') return true;
if (a == 'b' && b == 'c') return true;
}
const b_before_a = (a, b) => {
if (b == 'a' && a == 'c') return true;
if (b == 'b' && a == 'c') return true;
}
function mySort(arr) {
// Create a graph: first the nodes
let nodes = {}; // keyed by values in arr
for (let value of arr) nodes[value] = nodes[value] || new Node(value);
// Then the edges...
for (let i = 0; i < arr.length; i++) {
for (let j = i+1; j < arr.length; j++) {
nodes[arr[i]].orderWith(nodes[arr[j]]);
}
}
// Set absolute order, using the longest path from a node to a "least" node.
for (let node of Object.values(nodes)) node.setOrder();
// Sort array by order:
return arr.sort((a, b) => nodes[a].order - nodes[b].order);
}
const sorted = ['a', 'b', 'c', 'd'];
const unsorted = ['c', 'd', 'a', 'b'];
console.log(mySort(unsorted));
Maybe something like this
const sorted = ['a', 'b', 'c', 'd']; // I do NOT have access to this
const unsorted = ['c', 'd', 'a', 'b'];
const a_before_b = (a, b) => {
if (a == 'a' && b == 'd') return true;
if (a == 'b' && b == 'c') return true;
if (a == 'a' && b == 'c') return true;
}
const b_before_a = (a, b) => {
if (b == 'a' && a == 'c') return true;
if (b == 'b' && a == 'c') return true;
}
const mySortingFunction = (a, b) => {
if (a_before_b(a, b)) return -1;
if (b_before_a(a, b)) return 1;
return 0;
}
// doesn't produce correct sorting
console.log(unsorted.sort(mySortingFunction));

How to understand returning values from recursive function calls?

I am trying to recursively solve a maze using Javascript, how do I return my solution from my recursive function call?
I am attempting to create a maze solver algorithm using recursion, in Javascript. My maze shall follow the following pattern:
let rawMaze =
[
[0, 1, 3],
[0, 1, 0],
[2, 1, 0]
],
Where
0: wall
1: valid path
2: start
3: end
I create an object from the source array,
let maze = []
constructMaze() {
for (let i = 0; i < 3; i++) {
maze[i] = [];
for (let j = 0; j < 3; j++) {
const Cell = {
x: j,
y: i,
state: rawMaze[i][j],
id: uniqueId()
};
this.maze[i].push(Cell);
}
}
console.table(this.maze);
}
I also use a helper function to get the neighbours of any given cell,
getNeighbours(x, y) {
let maze = this.maze;
let neighbours = [];
maze.forEach(row => {
row.forEach(cell => {
if (
(cell.x == x && cell.y == y + 1) ||
(cell.x == x && cell.y == y - 1) ||
(cell.y == y && cell.x == x + 1) ||
(cell.y == y && cell.x == x - 1)
) {
neighbours.push(cell);
}
});
});
return neighbours;
}
The main logic happens in my checkNeighbours function, where I determine the next possible moves and follow them up,
checkNeighbours(neighbours, path, visited) {
let validMoves = [];
neighbours.forEach(potentialMove => {
if (visited.indexOf(potentialMove.id) < 0) {
if (potentialMove.state !== 0) {
validMoves.push(potentialMove);
}
}
});
if (validMoves.length === 0) {
return;
} else {
let finish = validMoves.filter(cell => cell.state === 3);
console.log(finish);
if (finish.length === 1) {
return path;
}
}
validMoves.forEach(validMove => {
path.push(validMove);
visited.push(validMove.id);
this.checkNeighbours(
this.getNeighbours(validMove.x, validMove.y),
path,
visited
);
});
}
I then proceed to try and put this all together and solve the maze,
initSolve(maze) {
let maze = maze;
let start = [];
let paths = [];
let visited = [];
let current = null;
maze.forEach(row => {
row.forEach(cell => {
// Is start?
if ((start.length == 0) & (cell.state == 2)) {
start.push(cell);
visited.push(cell.id);
current = cell;
}
});
});
let result = this.checkNeighbours(
this.getNeighbours(current.x, current.y),
paths,
visited
);
console.log("test", result);
}
My question is the following. Using this very contrived and simple maze configuration, I have stepped through the code and can confirm that my
checkNeighbours()
function will recursively arrive at the end. At that point, the function has an array (the variable path) that contains the correct steps through the maze. How do I return this branch, if you will, from the recursive call? What happens when there are multiple branches?
The only thing I can think of is using a global variable, but I feel this can not be correct.
This is ripped from a React frontend , here is runnable code:
let rawMaze = [
[0, 1, 3],
[0, 1, 0],
[2, 1, 0]
]
let maze = []
function constructMaze() {
let counter = 0
for (let i = 0; i < 3; i++) {
maze[i] = [];
for (let j = 0; j < 3; j++) {
const Cell = {
x: j,
y: i,
state: rawMaze[i][j],
id: counter
};
maze[i].push(Cell);
counter++
}
}
}
function getNeighbours(x, y) {
let maze = this.maze;
let neighbours = [];
maze.forEach(row => {
row.forEach(cell => {
if (
(cell.x == x && cell.y == y + 1) ||
(cell.x == x && cell.y == y - 1) ||
(cell.y == y && cell.x == x + 1) ||
(cell.y == y && cell.x == x - 1)
) {
neighbours.push(cell);
}
});
});
return neighbours;
}
function checkNeighbours(neighbours, path, visited) {
let validMoves = [];
neighbours.forEach(potentialMove => {
if (visited.indexOf(potentialMove.id) < 0) {
if (potentialMove.state !== 0) {
validMoves.push(potentialMove);
}
}
});
if (validMoves.length === 0) {
return;
} else {
let finish = validMoves.filter(cell => cell.state === 3);
console.log(finish);
if (finish.length === 1) {
return path;
}
}
validMoves.forEach(validMove => {
path.push(validMove);
visited.push(validMove.id);
this.checkNeighbours(
this.getNeighbours(validMove.x, validMove.y),
path,
visited
);
});
}
function initSolve() {
let maze = constructMaze()
let start = [];
let paths = [];
let visited = [];
let current = null;
maze.forEach(row => {
row.forEach(cell => {
// Is start?
if ((start.length == 0) & (cell.state == 2)) {
start.push(cell);
visited.push(cell.id);
current = cell;
}
});
});
let result = this.checkNeighbours(
this.getNeighbours(current.x, current.y),
paths,
visited
);
console.log("test", result);
}
Might I recommend adding another class:
function Path() {
this.isValidPath = false;
this.pathArray = [];
}
And also reworking the checkNeighbours function to rename/include these parameters?
checkNeighbours(neighbours, paths, currentPathIndex, visited)
This way, paths could contain an array of Path classes, and you could set the isValidPath flag to true when you found a valid path (assuming you want to also include invalid and valid paths in the array). This would allow you to return all paths (branches). Each branch would be in the paths array at position currentPathIndex, which you'd increment in the code once one path is complete and you want to start searching for another path.
Also, currently the checkNeighbours function appears to do a breadth first search for valid moves. Perhaps if you reworked it into more of a depth-first traversal, then you could add each valid path (and exclude any invalid paths) to the paths array you return.

Counting Elements in an Array, Adding them to 2 Objects within an Array

var arr = ['cat','cat','dog','penguin','chicken','chicken']
function orgAnimals(input)
var obj = {};
for (var i = 0 ; i < input.length; i++) {
obj[input[i]] = obj[input[i]] || 0;
obj[input[i]]++
}
return obj;
}
So this gives me {cat:2, dog:1, penguin:1, chicken:2,}
I want to split the object up into 2 different objects and put it in an array so it looks like
[{cat:2, dog:1} {penguin:1, chicken:2 }]
I tried an if statement to make that work but it's not.
if (input[i]==='cat'||'dog') still gives me the same output as before.
Any hints? Am I using the operator incorrectly?
Golf time, I suppose
var arr = ['cat','cat','dog','penguin','chicken','chicken']
function orgAnimals(input) {
return input.reduce((a, b) => {
var i = ['cat','dog'].indexOf(b) === -1 ? 1 : 0;
return b in a[i] ? a[i][b]++ : a[i][b] = 1, a
}, [{},{}]);
}
console.log( orgAnimals(arr) );
Same logic, but a little more readable, where you check if the iterated value is either cat or dog and insert into the array based on a condition
var arr = ['cat', 'cat', 'dog', 'penguin', 'chicken', 'chicken']
function orgAnimals(input) {
var arr = [{}, {}];
for (var i = 0; i < input.length; i++) {
if ( input[i] === 'dog' || input[i] === 'cat') {
if ( input[i] in arr[0] ) {
arr[0][ input[i] ] = arr[0][ input[i] ] + 1;
} else {
arr[0][ input[i] ] = 1;
}
} else {
if ( input[i] in arr[1] ) {
arr[1][ input[i] ] = arr[1][ input[i] ] + 1;
} else {
arr[1][ input[i] ] = 1;
}
}
}
return arr;
}
console.log( orgAnimals(arr) )
You would want to create 2 separate objects and then combine them in your return statement. So something like this:
if ((input[i] == 'cat') || (input[i] == 'dog'))){
obj1[input[i]] = obj1[input[i]] || 0;
obj1[input[i]]++
}
else if ((input[i] == 'penguin') || (input[i] == 'chicken')){
obj2[input[i]] = obj2[input[i]] || 0;
obj2[input[i]]++
}
return {obj1, obj2}

Javascript: Function to fill an object with the vowels as the keys and the count as the values

As before, I have looked all over for the answer and I'm just a beginner and I am trying to learn from this and not just be handed the answer.
var voweler = function (str) {
var strArr = str.split('')
var obj = {};
for (var i = 0; i < strArr.length; i++) {
if (strArr[i] == 'a') {
obj.a = 0;
obj.a++;
} else if (strArr[i] == 'e') {
obj.e = 0;
obj.e++;
} else if (strArr[i] == 'i') {
obj.i = 0;
obj.i++;
} else if (strArr[i] == 'o') {
obj.o = 0;
obj.o++;
} else if (strArr[i] == 'u') {
obj.u = 0;
obj.u++;
}
};
return obj;
}
voweler("This is a test")
//returns this which is wrong. Object {i: 1, a: 1, e: 1}
Your code for updating the counts is wrong. Every vowel encountered, you run obj.<vowel> = 0 which resets the count! To remedy this, set the counts before you enter the for loop and then in the for loop, only increment the counter.
If you'd prefer to only have an entry if the vowel exists, you can conditionally increment:
if(strArr[i] == <some_vowel>){
if(obj.<some_vowel> === undefined)obj.<some_vowel> = 1;
else obj.<some_vowel> ++;
}
Couple of hints:
Your loop will assign the property value for the key to 0 every time the character is a vowel, before incrementing.
You can use toLowerCase() if you want to find upper and lower case vowels.
You can use indexOf. It will return -1 if it cannot find the argument in a string.
var voweler = function (str) {
var strArr = str.toLowerCase().split('');
var obj = {};
strArr.forEach(function(ch) {
if ('aeiou'.indexOf(ch) !== -1) {
obj[ch] = (obj[ch] || 0 ) + 1;
}
});
return obj;
}
console.log(voweler("This is a test"));
// Object {i: 2, a: 1, e: 1}
You might prefer something like this:
function voweler(input) {
var result = {
a: 0, e: 0, i: 0, o: 0, u: 0
};
for (var i = 0; i < input.length; i++) {
var char = input.charAt(i).toLowerCase();
if (result.hasOwnProperty(char)) {
result[char] ++;
}
}
return result;
}
Just because we can… and DTing has your answer already.
function countVowels(s) {
var vowels = /[aeiou]/,
o = {};
s.toLowerCase().split('').forEach(function(c){
if (vowels.test(c)) o.hasOwnProperty(c)? ++o[c] : o[c] = 1;
});
return o;
}
console.log(JSON.stringify(countVowels('hi there')));
There is also:
function countVowels(s) {
return (s.toLowerCase().match(/[aeiou]/g) || []).reduce(function(o, c) {
o[c] = (o[c] || 0) + 1;
return o;
}, {});
}

Categories

Resources