I am implementing end of round function for a board game. Each players state is represented with an object that is in turn stored in array playerStates:
const [playerStates, setPlayerStates] = useState(getInitialPlayerStates);
const [playerIndex, setPlayerIndex] = useState(0);
const playerState = playerStates[playerIndex];
function setPlayerState(playerState) {
let tPlayerStates = [...playerStates];
tPlayerStates.splice(playerIndex, 1, playerState);
setPlayerStates(tPlayerStates);
}
End of round function checks whether all players have finished their actions, and if so, resets global state
/** END OF ROUND **/
function handleEndRound() {
let nextPlayerIndex = playerIndex + 1 < GLOBAL_VARS.numOfPlayers ? playerIndex + 1 : 0;
let haveAllFinished = true;
while (playerIndex !== nextPlayerIndex) {
if (!playerStates[nextPlayerIndex].finishedRound) {
haveAllFinished = false;
}
nextPlayerIndex = nextPlayerIndex + 1 < GLOBAL_VARS.numOfPlayers ? nextPlayerIndex + 1 : 0;
}
if (haveAllFinished) {
...
/* reset player states */
let tPlayerStates = [];
for (let i = 0; i < GLOBAL_VARS.numOfPlayers; i++) {
let tPlayerState = {...playerStates[i]};
console.log("RESETTING PLAYER STATE:");
console.log(tPlayerState);
tPlayerState.availableAdventurers = GLOBAL_VARS.adventurers;
/* remove active card */
if (tPlayerState.activeCard !== false) {
tPlayerState.discardDeck.push(tPlayerState.activeCard);
tPlayerState.activeCard = false;
}
/* move cards from hand to discard */
for (let card of tPlayerState.hand) {
tPlayerState = addCardToDiscardDeck(card, tPlayerState);
tPlayerState.hand = [];
}
console.log("tPlayerState before:");
console.log(tPlayerState);
/* draw a new hand */
for (let i = 0; i < GLOBAL_VARS.handSize; i++) {
if (tPlayerState.drawDeck.length === 0) {
console.log("tPlayerState after:");
console.log(tPlayerState);
tPlayerState = addDiscardToDrawDeck(tPlayerState);
}
if (tPlayerState.drawDeck.length > 0) {
tPlayerState = addCardToHand(tPlayerState.drawDeck[0], playerState);
tPlayerState.drawDeck.splice(0, 1);
}
}
...
The problem is that wrong player state is stored, as evidenced by the console outputs:
tPlayerState before: App.js:315
Object { finishedRound: true, ... color: "#FFD41A" }
tPlayerState after: App.js:320
Object { finishedRound: false, ... color: "#2A8CFF" }
The color identifies players, and for some reason state of the first player is exchanged by the state of the second player.
There is a lot going on with deep-nested objects and arrays and that might be the cause - however I do not see why exactly should these two outputs be different. What is the source of the change? How can I prevent it?
The complete code is hosted at https://github.com/faire2/loreHunters/.
Additional information:
The problem is pertinent to JS rather than to React. The problem was related to passing a shallow copy to of a deep object to a function. I still do not understand what exactly was going on: in the end, two strangely fused objects were being pushed by into an array even though I was pushing only one object.
The problem was passing a shallow copy of a deep nested object to a function and back. The problem was resolved by passing a Lodash deep clone of that object:
...
const result = addCardToHand(tPlayerState.drawDeck[0], cloneDeep(tPlayerState));
...
I would still like to understand what was happening, and why the two objects, both dealt with separately in a loop, were being merged into one.
EDIT: so it seems that console.log does not always show the proper state of an object. I used "debugger" to pause the run and check for the real state of the concerned objects, which cleared all the confusion. So the problem itself was caused by a shallow copy, which was remedied by using cloneDeep from Lodash. The correct state can be checked by using the debugger;.
Related
I have a nested JSON, this is structured as follows:
As you can see the JSON is called bwaResult and in this object there are three big groups. These groups are called actBwa, fcBwa and planBwa. These groups contain years and yearlyResults(sums) which also have years. I would like to assign the values to the individual years. That means in 2020 you would have actBwa, fcBwa, planBwa and the yearlyResults values and the same for the other years.
My Code:
const plMonthResults = {};
const plMonth = resp.success ? resp.bwaResult : null; // JSON-DATA
delete resp.success;
if (plMonth && plMonth !== null && Object.keys(plMonth)) {
let count = 0;
for (const bwaType in plMonth) {
const plMonthData = plMonth[bwaType]; // Here I get actBwa, fcBwa, planBwa
if (count === 0) {
for (const yearKey of Object.keys(plMonthData)) {
plMonthResults[yearKey] = {}; // Sort by years
}
}
for (const yearKeyInPlMonth in plMonthData) {
plMonthResults[yearKeyInPlMonth][bwaType] = plMonthData[yearKeyInPlMonth]; // Here then arises the error
}
count += 1;
}
}
When I run the code then I get the following error:
ERROR TypeError: Cannot set properties of undefined (setting 'fcBwa')
I guess since actBwa only has 2020 and the other groups have more years there is a problem finding fcBwa and iterating further or am I seeing this wrong? Can you tell me how to solve this problem?
UPDATE my work in stackblitz: https://stackblitz.com/edit/read-local-json-file-service-udqvck?file=src%2Fapp%2Fapp.component.ts
It seems like you're using count to copy the keys only once, but that means you will only have the keys of the first object (in this case actBwa). You can instead go through the key of each object, but make sure you don't override the existing ones. So remove the count and modify the look like this
for (const yearKey of Object.keys(plMonthData)) {
plMonthResults[yearKey] = plMonthResults[yearKey] ?? {};
}
You can even remove this loop and include the check into the main one:
for (const yearKeyInPlMonth in plMonthData) {
plMonthResults[yearKeyInPlMonth] = plMonthResults[yearKeyInPlMonth] ?? {};
plMonthResults[yearKeyInPlMonth][bwaType] = plMonthData[yearKeyInPlMonth];
}
Let me know if that gives you the result you're after
I have a code that checks indexes of array with some ID's, and increases some order value for items with those ID's. The code looks something like this:
function changeOrder(initialIndex, newIndex, itemsMap, itemIds) {
const requestPayload = [];
itemsMap[itemIds[initialIndex]].order = itemsMap[itemIds[newIndex]].order;
requestPayload.push(itemsMap[itemIds[initialIndex]]);
if (initialIndex > newIndex) {
for (let i = newIndex; i < initialIndex; i++) {
itemsMap[itemIds[i]].order++;
requestPayload.push(itemsMap[itemIds[i]]);
}
}
console.log(requestPayload);
return requestPayload;
}
Now, the issue is when i hover over the requestPayload inside the console.log, order values are correct (0, 1, 2 in my case). But once it does the logging, or returns the requestPayload, values are 1, 2, 3. Function is triggered only once, so it doesn't increment them again. Here is the screenshot, you can see when I manually log the values when breakpoint is set vs logged payload.
EDIT: Solved with the help from #Pointy in the comments:
for (let i = newIndex; i < initialIndex; i++) {
const copy = { ...itemsMap[itemIds[i]] };
copy.order++;
requestPayload.push(copy);
}
I'm trying to solve this kata:
Given an integer N (<1000), return an array of integers 1..N where the sum of each 2 consecutive numbers is a perfect square. If that's not possible, return false.
For example, if N=15, the result should be this array: [9, 7, 2, 14, 11, 5, 4, 12, 13, 3, 6, 10, 15, 1, 8]. Below N=14, there's no answer, so the function should return false.
I thought 'how hard can this be?' and it's been long days in the rabbit hole. I've been programming for just a few months and don't have a background of CS so I'll write what I understand so far of the problem trying to use the proper concepts but please feel free to tell me if any expression is not correct.
Apparently, the problem is very similar to a known problem in graph theory called TSP. In this case, the vertices are connected if the sum of them is a perfect square. Also, I don't have to look for a cycle, just find one Hamiltonian Path, not all.
I understand that what I'm using is backtracking. I build an object that represents the graph and then try to find the path recursively. This is how I build the object:
function buildAdjacentsObject (limit) {
const potentialSquares = getPotentialSquares(limit)
const adjacents = {}
for (let i = 0; i < (limit + 1); i++) {
adjacents[i] = {}
for (let j = 0; j < potentialSquares.length; j++) {
if (potentialSquares[j] > i) {
const dif = potentialSquares[j] - i
if (dif <= limit) {
adjacents[i][dif] = 1
} else {
break
}
}
}
}
return adjacents
}
function getPotentialSquares (limit) {
const maxSum = limit * 2 - 1
let square = 4
let i = 3
const potentialSquares = []
while (square <= maxSum) {
potentialSquares.push(square)
square = i * i
i++
}
return potentialSquares
}
At first I was using a hash table with an array of adjacent nodes on each key. But when my algorithm had to delete vertices from the object, it had to look for elements in arrays several times, which took linear time every time. I made the adjacent vertices hashable and that improved my execution time. Then I look for the path with this function:
function findSquarePathInRange (limit) {
// Build the graph object
const adjacents = buildAdjacentsObject(limit)
// Deep copy the object before making any changes
const adjacentsCopy = JSON.parse(JSON.stringify(adjacents))
// Create empty path
const solution = []
// Recursively complete the path
function getSolution (currentCandidates) {
if (solution.length === limit) {
return solution
}
// Sort the candidate vertices to start with the ones with less adjacent vert
currentCandidates = currentCandidates.sort((a, b) => {
return Object.keys(adjacentsCopy[a]).length -
Object.keys(adjacentsCopy[b]).length
})
for (const candidate of currentCandidates) {
// Add the candidate to the path
solution.push(candidate)
// and delete it from the object
for (const candidateAdjacent in adjacents[candidate]) {
delete adjacentsCopy[candidateAdjacent][candidate]
}
if (getSolution(Object.keys(adjacentsCopy[candidate]))) {
return solution
}
// If not solution was found, delete the element from the path
solution.pop()
// and add it back to the object
for (const candidateAdjacent in adjacents[candidate]) {
adjacentsCopy[candidateAdjacent][candidate] = 1
}
}
return false
}
const endSolution = getSolution(
Array.from(Array(limit).keys()).slice(1)
)
// The elements of the path can't be strings
return (endSolution) ? endSolution.map(x => parseInt(x, 10)) : false
}
My solution works 'fast' but it's not fast enough. I need to pass more than 200 tests in less than 12 seconds and so far it's only passing 150. Probably both my algorithm and my usage of JS can be improved, so, my questions:
Can you see a bottleneck in the code? The sorting step should be the one taking more time but it also gets me to the solution faster. Also, I'm not sure if I'm using the best data structure for this kind of problem. I tried classic looping instead of using for..in and for..of but it didn't change the performance.
Do you see any place where I can save previous calculations to look for them later?
Regarding the last question, I read that there is a dynamic solution to the problem but everywhere I found one, it looks for minimum distance, number of paths or existence of path, not the path itself. I read this everywhere but I'm unable to apply it:
Also, a dynamic programming algorithm of Bellman, Held, and Karp can be used to solve the problem in time O(n2 2n). In this method, one determines, for each set S of vertices and each vertex v in S, whether there is a path that covers exactly the vertices in S and ends at v. For each choice of S and v, a path exists for (S,v) if and only if v has a neighbor w such that a path exists for (S − v,w), which can be looked up from already-computed information in the dynamic program.
I just can't get the idea on how to implement that if I'm not looking for all the paths. I found this implementation of a similar problem in python that uses a cache and some binary but again, I could translate it from py but I'm not sure how to apply those concepts to my algorithm.
I'm currently out of ideas so any hint of something to try would be super helpful.
EDIT 1:
After Photon comment, I tried going back to using a hash table for the graph, storing adjacent vertices as arrays. Also added a separate array of bools to keep track of the remaining vertices.
That improved my efficiency a lot. With these changes I avoided the need to convert object keys to arrays all the time, no need to copy the graph object as it was not going to be modified and no need to loop after adding one node to the path. The bad thing is that then I needed to check that separate object when sorting, to check which adjacent vertices were still available. Also, I had to filter the arrays before passing them to the next recursion.
Yosef approach from the first answer of using an array to store the adjacent vertices and access them by index prove even more efficient. My code so far (no changes to the square finding function):
function square_sums_row (limit) {
const adjacents = buildAdjacentsObject(limit)
const adjacentsCopy = JSON.parse(JSON.stringify(adjacents))
const solution = []
function getSolution (currentCandidates) {
if (solution.length === limit) {
return solution
}
currentCandidates = currentCandidates.sort((a, b) => {
return adjacentsCopy[a].length - adjacentsCopy[b].length
})
for (const candidate of currentCandidates) {
solution.push(candidate)
for (const candidateAdjacent of adjacents[candidate]) {
adjacentsCopy[candidateAdjacent] = adjacentsCopy[candidateAdjacent]
.filter(t => t !== candidate)
}
if (getSolution(adjacentsCopy[candidate])) {
return solution
}
solution.pop()
for (const candidateAdjacent of adjacents[candidate]) {
adjacentsCopy[candidateAdjacent].push(candidate)
}
}
return false
}
return getSolution(Array.from(Array(limit + 1).keys()).slice(1))
}
function buildAdjacentsObject (limit) {
const potentialSquares = getPotentialSquares(limit)
const squaresLength = potentialSquares.length
const adjacents = []
for (let i = 1; i < (limit + 1); i++) {
adjacents[i] = []
for (let j = 0; j < squaresLength; j++) {
if (potentialSquares[j] > i) {
const dif = potentialSquares[j] - i
if (dif <= limit) {
adjacents[i].push(dif)
} else {
break
}
}
}
}
return adjacents
}
EDIT 2:
The code performs fine in most of the cases, but my worst case scenarios suck:
// time for 51: 30138.229ms
// time for 77: 145214.155ms
// time for 182: 22964.025ms
EDIT 3:
I accepted Yosef answer as it was super useful to improve the efficiency of my JS code. Found a way to tweak the algorithm to avoid paths with dead ends using some of the restrictions from this paper A Search Procedure for Hamilton Paths and Circuits..
Basically, before calling another recursion, I check 2 things:
If there is any node with no edges that's not part of the path till now and the path is missing more than 1 node
If there were more than 2 nodes with 1 edge (one can be following node, that had 2 edges before deleting the edge to the current node, and other can be the last node)
Both situations make it impossible to find a Hamiltonian path with the remaining nodes and edges (if you draw the graph it'll be clear why). Following that logic, there's another improvement if you check nodes with only 2 edges (1 way to get in and other to go out). I think you can use that to delete other edges in advance but it was not necessary at least for me.
Now, the algorithm performs worse in most cases, where just sorting by remaining edges was good enough to predict the next node and extra work was added, but it's able to solve the worst cases in a much better time. For example, limit = 77 it's solved in 15ms but limit=1000 went from 30ms to 100ms.
This is a really long post, if you have any edit suggestions, let me know. I don't think posting the final code it's the best idea taking into account that you can't check the solutions in the platform before solving the kata. But the accepted answer and this final edit should be good advice to think about this last part while still learning something. Hope it's useful.
By replacing the object by an array you save yourself from convert the object to an array every time you want to find the length (which you do a lot - in any step of the sort algorithm), or when you want to get the keys for the next candidates. in my tests the code below has been a lot more effective in terms of execution time
(0.102s vs 1.078s for limit=4500 on my machine)
function buildAdjacentsObject (limit) {
const potentialSquares = getPotentialSquares(limit)
const adjacents = [];
for (let i = 0; i < (limit + 1); i++) {
adjacents[i] = [];
for (let j = 0; j < potentialSquares.length; j++) {
if (potentialSquares[j] > i) {
const dif = potentialSquares[j] - i
if (dif <= limit) {
adjacents[i].push(dif)
} else {
break
}
}
}
}
return adjacents
}
function getPotentialSquares (limit) {
const maxSum = limit * 2 - 1
let square = 4
let i = 3
const potentialSquares = []
while (square <= maxSum) {
potentialSquares.push(square)
square = i * i
i++
}
return potentialSquares
}
function findSquarePathInRange (limit) {
// Build the graph object
const adjacents = buildAdjacentsObject(limit)
// Deep copy the object before making any changes
const adjacentsCopy = JSON.parse(JSON.stringify(adjacents))
// Create empty path
const solution = [];
// Recursively complete the path
function getSolution (currentCandidates) {
if (solution.length === limit) {
return solution
}
// Sort the candidate vertices to start with the ones with less adjacent vert
currentCandidates = currentCandidates.sort((a, b) => {
return adjacentsCopy[a].length - adjacentsCopy[b].length
});
for (const candidate of currentCandidates) {
// Add the candidate to the path
solution.push(candidate)
// and delete it from the object
for (const candidateAdjacent of adjacents[candidate]) {
adjacentsCopy[candidateAdjacent] = adjacentsCopy[candidateAdjacent].filter(t=>t!== candidate)
}
if (getSolution(adjacentsCopy[candidate])) {
return solution
}
// If not solution was found, delete the element from the path
solution.pop()
// and add it back to the object
for (const candidateAdjacent of adjacents[candidate]) {
adjacentsCopy[candidateAdjacent].push(candidate);
}
}
return false
}
const endSolution = getSolution(
Array.from(Array(limit).keys()).slice(1)
)
// The elements of the path can't be strings
return endSolution
}
var t = new Date().getTime();
var res = findSquarePathInRange(4500);
var t2 = new Date().getTime();
console.log(res, ((t2-t)/1000).toFixed(4)+'s');
Recently i started working with D3.js to plot a sunburn graph. The data is provided in JSON. For some design stuff i wanted to swap some items (called childrens in D3 doc).
I know in JS arrays are objects...so something like this:
var buffer = myarray[2];
is just a reference. Therefore a buffer for swapping has no effect (?).
Thus i invented a second array (childrens_final) in my code which adopt the items while the swapping process. Its just iterating through every item, a second iteration is looking for an item with the same name to set items with same name in a row. Therefore the swap.
var childrens = response_data.data.data['children'];
var childrens_final = []
for (var child = 0; child < childrens.length; child++) {
var category = childrens[child];
var found = false;
var i = child+1
while (!(found) && (i < childrens.length)) {
if (childrens[i]['name'] == category['name']) {
var childrens = swapArrayElements(childrens, child+1, i);
var one = childrens[child];
var two = childrens[child+1]
found = true;
}
i++;
}
if (found) {
childrens_final.push(one);
childrens_final.push(two);
child++;
}
else {
childrens_final.push(childrens[child])
}
}
response.data.data['children'] = childrens_final;
return response.data.data;
The function swapArrayElements() is just using splice:
function swapArrayElements(list, x, y) {
if (list.length ==1) return list;
list.splice(x, 1, list.splice(y,1, list[x])[0]);
return list;
}
The problem is that there is still no effect from the swap in the graph. But when logging the childrens_final. There is something like that in the console:
Array [ Object, Object, Object, Object, Object, Object, Object, Object, Object ]
The objects are in the right order! But in the array there is still the old order.
Thats actually so basic, but i dont see a solution.
Btw...the code is working under AngularJS.
Found the problem. It's a D3.js problem. D3 is sorting the data itself. You have to set explicitly:
d3.layout.partition.sort(null)
Otherwise every pre sorting process has no effect.
To clarify a bit on the question, I have an array and within the array are a bunch of objects. Can I rearrange the objects in the array based on the key values of each object?
When I'm attempting to do so, it keeps telling me that the variable (in this case, students) is undefined.
When I use the built-in sort function, it works perfectly. However, this is a school assignment and I HAVE to show the breakdown of the quicksort function.
Here is the code I am using:
function swap(student, firstIndex, secondIndex){
var temp = student[firstIndex];
student[firstIndex] = student[secondIndex];
student[secondIndex] = temp;
}
function partition(student, left, right) {
var pivot = student[Math.floor((right + left) / 2)],
i = left,
j = right;
while (i <= j) {
while (student[i] < pivot) {
i++;
}
while (student[j] > pivot) {
j--;
}
if (i <= j) {
swap(student, i, j);
i++;
j--;
}
}
return i;
}
function quickSort(student, left, right) {
var index;
if (student.length > 1) {
left = typeof left != "number" ? 0 : left;
right = typeof right != "number" ? student.length - 1 : right;
index = partition(student, left, right);
if (left < index - 1) {
quickSort(student, left, index - 1);
}
if (index < right) {
quickSort(student, index, right);
}
}
return student;
}
var studentNumbersArray = []
var randomArray = [];
for (i = 0; i < studentsArray.length; i++) {
studentNumbersArray.push(studentsArray[i].studentNumber);
}
function sortStudentNumbers() {
var student = studentNumbersArray.name; // <-- THIS IS WHERE I AM GETTING ERROR
quickSort(student);
var updatedStudentArray = JSON.stringify(studentsArray);
localStorage.setItem("students", updatedStudentArray);
location.reload();
}
After seeing several permutations of your code I think what you are trying to do needs to look a little something like this.
quickSort(arrayToSort, attributeToSortOn, left, right) {
...
}
...
function partition(arrayToSort, attributeToSortOn, left, right) {
var pivot = student[Math.floor((right + left) / 2)][attributeToSortOn]
...
while (arrayToSort[i][attributeToSortOn] < pivot) {
...
}
...
quickSort(studentsArray, 'studentNumber');
quickSort always needs the array to compare values at each position. You can't just pass studentsArray.studentNumber because the attribute to sort on is useless on it's own and the array has no knowledge of the types of object contained within it anyway.
You cannot use a variable defined inside a function, in another function. Every function has its own scope, and in order to carry data between functions (inside the variables) you have to create a variable in the upper (global) scope. That means, you have to create the student and updatedStudentArray outside of the functions and use them without declaring in the functions (sortStudentNumbers() in this case)
var student;
var updatedStudentArray;
function swap(student, firstIndex, secondIndex){
var temp = student[firstIndex];
student[firstIndex] = student[secondIndex];
student[secondIndex] = temp;
}
// ..................................
// ------ removed for clearity
// ..................................
function sortStudentNumbers() {
student = studentNumbersArray.name; // <-- THIS IS WHERE I AM GETTING ERROR
quickSort(student);
updatedStudentArray = JSON.stringify(studentsArray);
localStorage.setItem("students", updatedStudentArray);
location.reload();
}
These the mistakes I found. But still, when I execute this code I get a studentsArray is not defined error on the console. Where is your studentsArray? If you have it somewhere, then it should all work now.
EDIT:
After you created the second question, I had a quick chance to have a look at your modified code. Don't forget to update your question here.
My Answer to your modified code:
You have to set a "student" key in localStorage first, in order to use getItem() for it. You don't have anything in the localStorage, that is why your variable is not filled when you try to get the data from there. Thus, you are getting the error "Cannot read property 'studentNumber' of null".