Improve Generation Performance: Store states in HashMap and don't update the GraphData incrementally

This commit is contained in:
2025-08-30 15:39:07 +02:00
parent 4738e5f2d4
commit 478fa10afd
6 changed files with 90 additions and 52 deletions

View File

@ -56,7 +56,7 @@ export const reset_graph_view = (graph) => {
export const highlight_node = (states, graph, current_state) => {
graph.nodeColor((node) => {
if (states_are_equal(states[node.id], current_state)) {
if (states_are_equal(states.get(node.id), current_state)) {
return "#FF0000";
}

View File

@ -26,14 +26,49 @@ let graph = null;
// Helpers
//
export const key_of = (arr) => JSON.stringify(arr.board);
const clear_states = () => {
states = {
_map: new Map(),
add(arr) {
const k = key_of(arr);
if (!this._map.has(k)) this._map.set(k, arr);
return this;
},
has(arr) {
return this._map.has(key_of(arr));
},
get(k) {
return this._map.get(k);
},
delete(arr) {
return this._map.delete(key_of(arr));
},
values() {
return [...this._map.values()];
},
get length() {
return this._map.size;
},
};
};
const clear_graph = () => {
document.getElementById("graph").innerHTML = "";
clear_visualization();
model.visualize(initial_state);
states = [initial_state];
clear_states();
states.add(initial_state);
current_state = initial_state;
data = {
nodes: [{ id: 0 }],
nodes: [{ id: key_of(current_state) }],
links: [],
};
graph = null;
@ -61,9 +96,10 @@ window.onload = () => {
document.getElementById("state_name").innerHTML = initial_state.name;
clear_visualization();
model.visualize(initial_state);
states = [initial_state];
clear_states();
states.add(initial_state);
data = {
nodes: [{ id: 0 }],
nodes: [{ id: key_of(initial_state) }],
links: [],
};
graph = generate_graph(data, node_click_view_state);
@ -76,7 +112,7 @@ window.onload = () => {
const node_click_view_state = (node, graph) => {
clear_visualization();
current_state = states[node.id];
current_state = states.get(node.id);
model.visualize(current_state);
highlight_node(states, graph, current_state);
@ -162,7 +198,8 @@ document.getElementById("select_model_button").addEventListener("click", () => {
initial_state = model.initial_states[current_initial_state];
selected_element = null;
current_state = initial_state;
states = [initial_state];
clear_states();
states.add(initial_state);
graph = null;
graph = generate_graph(data, node_click_view_state);
model.visualize(initial_state);
@ -178,7 +215,8 @@ document.getElementById("select_state_button").addEventListener("click", () => {
initial_state = model.initial_states[current_initial_state];
current_state = initial_state;
selected_element = null;
states = [initial_state];
clear_states();
states.add(initial_state);
graph = null;
graph = generate_graph(data, node_click_view_state);
model.visualize(initial_state);

View File

@ -18,6 +18,7 @@ import {
} from "./state.js";
import { DIRECTIONS, LEFT, RIGHT, UP, DOWN } from "../../constants.js";
import { HORIZONTAL, VERTICAL } from "../../constants.js";
import { key_of } from "../../main.js";
const initial_states = [
single,
@ -35,6 +36,8 @@ const generate = (states, initial_state, graph, previous_move) => {
let last_print = 0;
let data = graph.graphData();
while (stack.length > 0) {
const [current_state, prev_move] = stack.pop();
@ -45,7 +48,7 @@ const generate = (states, initial_state, graph, previous_move) => {
const states_before = states.length;
const [new_state, new_block] = move(
states,
graph,
data,
current_state,
block,
direction,
@ -62,6 +65,8 @@ const generate = (states, initial_state, graph, previous_move) => {
}
}
}
graph.graphData(data);
};
const select = (state, offsetX, offsetY) => {
@ -84,7 +89,7 @@ const select = (state, offsetX, offsetY) => {
return null;
};
const move = (states, graph, state, block, direction) => {
const move = (states, data, state, block, direction) => {
if (
block[1] === HORIZONTAL &&
(arrays_are_equal(direction, DIRECTIONS[UP]) ||
@ -112,33 +117,29 @@ const move = (states, graph, state, block, direction) => {
// insert_block(new_state, block);
let new_state = structuredClone(state);
delete new_state.name;
let new_block = move_block(block, direction);
move_state_block(new_state, block, direction);
// TODO: Make states into a hashmap?
let index = index_of_state(states, new_state);
let new_link = null;
let new_node = null;
if (index !== null) {
if (states.has(new_state)) {
// We already had this state, just generate a link
new_link = {
source: index_of_state(states, state), // We're coming from this state...
target: index, // ...and ended up here, at a previous state.
source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a previous state.
};
} else {
states.push(new_state);
states.add(new_state);
new_node = {
id: states.length - 1,
id: key_of(new_state),
};
new_link = {
source: index_of_state(states, state), // We're coming from this state...
target: states.length - 1, // ...and ended up here, at a new state.
source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a new state.
};
}
const data = graph.graphData();
// TODO: Faster without this?
const has_link = (data, link) => {
// for (let l of data.links) {
@ -154,15 +155,10 @@ const move = (states, graph, state, block, direction) => {
};
if (new_node !== null) {
graph.graphData({
nodes: [...data.nodes, new_node],
links: [...data.links, new_link],
});
data.nodes.push(new_node);
data.links.push(new_link);
} else if (!has_link(data, new_link)) {
graph.graphData({
nodes: data.nodes,
links: [...data.links, new_link],
});
data.links.push(new_link);
}
return [new_state, new_block];

View File

@ -8,6 +8,7 @@ import {
HORIZONTAL,
VERTICAL,
} from "../../constants.js";
import { key_of } from "../../main.js";
export const arrays_are_equal = (array, other_array) => {
if (array.length != other_array.length) {
@ -41,6 +42,8 @@ export const state_contains_block = (state, block) => {
};
export const states_are_equal = (state, other_state) => {
return key_of(state) === key_of(other_state);
if (state.board.length != other_state.board.length) {
return false;
}
@ -76,6 +79,7 @@ export const index_of_state = (states, state) => {
export const remove_block = (state, block) => {
let new_state = structuredClone(state);
delete new_state.name;
new_state.board.splice(block[0], 1);

View File

@ -13,6 +13,7 @@ import {
insert_block,
move_state_block,
} from "./state.js";
import { key_of } from "../../main.js";
const initial_states = [single, two, three, four, small_klotski_like, klotski];
@ -21,6 +22,8 @@ const generate = (states, initial_state, graph, previous_move) => {
let last_print = 0;
let data = graph.graphData();
while (stack.length > 0) {
const [current_state, prev_move] = stack.pop();
@ -31,7 +34,7 @@ const generate = (states, initial_state, graph, previous_move) => {
const states_before = states.length;
const [new_state, new_block] = move(
states,
graph,
data,
current_state,
block,
direction,
@ -48,6 +51,8 @@ const generate = (states, initial_state, graph, previous_move) => {
}
}
}
graph.graphData(data);
};
const select = (state, offsetX, offsetY) => {
@ -70,7 +75,7 @@ const select = (state, offsetX, offsetY) => {
return null;
};
const move = (states, graph, state, block, direction) => {
const move = (states, data, state, block, direction) => {
if (!block_is_movable(state, block, direction)) {
return [null, null];
}
@ -82,6 +87,7 @@ const move = (states, graph, state, block, direction) => {
let new_state;
try {
new_state = structuredClone(state);
delete new_state.name; // Only need this for the initial state
} catch (e) {
console.log(e);
return [null, null];
@ -89,30 +95,25 @@ const move = (states, graph, state, block, direction) => {
let new_block = move_block(block, direction);
move_state_block(new_state, block, direction);
// TODO: Make states into a hashmap?
let index = index_of_state(states, new_state);
let new_link = null;
let new_node = null;
if (index !== null) {
if (states.has(new_state)) {
// We already had this state, just generate a link
new_link = {
source: index_of_state(states, state), // We're coming from this state...
target: index, // ...and ended up here, at a previous state.
source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a previous state.
};
} else {
states.push(new_state);
states.add(new_state);
new_node = {
id: states.length - 1,
id: key_of(new_state),
};
new_link = {
source: index_of_state(states, state), // We're coming from this state...
target: states.length - 1, // ...and ended up here, at a new state.
source: key_of(state), // We're coming from this state...
target: key_of(new_state), // ...and ended up here, at a new state.
};
}
const data = graph.graphData();
// TODO: Faster without this?
const has_link = (data, link) => {
// for (let l of data.links) {
@ -128,15 +129,10 @@ const move = (states, graph, state, block, direction) => {
};
if (new_node !== null) {
graph.graphData({
nodes: [...data.nodes, new_node],
links: [...data.links, new_link],
});
data.nodes.push(new_node);
data.links.push(new_link);
} else if (!has_link(data, new_link)) {
graph.graphData({
nodes: data.nodes,
links: [...data.links, new_link],
});
data.links.push(new_link);
}
return [new_state, new_block];

View File

@ -6,6 +6,7 @@ import {
DIRECTIONS,
invert_direction,
} from "../../constants.js";
import { key_of } from "../../main.js";
export const arrays_are_equal = (array, other_array) => {
if (array.length != other_array.length) {
@ -39,6 +40,8 @@ export const state_contains_block = (state, block) => {
};
export const states_are_equal = (state, other_state) => {
return key_of(state) === key_of(other_state);
if (state.board.length != other_state.board.length) {
return false;
}
@ -74,6 +77,7 @@ export const index_of_state = (states, state) => {
export const remove_block = (state, block) => {
let new_state = structuredClone(state);
delete new_state.name;
new_state.board.splice(block[0], 1);