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) => { export const highlight_node = (states, graph, current_state) => {
graph.nodeColor((node) => { graph.nodeColor((node) => {
if (states_are_equal(states[node.id], current_state)) { if (states_are_equal(states.get(node.id), current_state)) {
return "#FF0000"; return "#FF0000";
} }

View File

@ -26,14 +26,49 @@ let graph = null;
// Helpers // 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 = () => { const clear_graph = () => {
document.getElementById("graph").innerHTML = ""; document.getElementById("graph").innerHTML = "";
clear_visualization(); clear_visualization();
model.visualize(initial_state); model.visualize(initial_state);
states = [initial_state]; clear_states();
states.add(initial_state);
current_state = initial_state; current_state = initial_state;
data = { data = {
nodes: [{ id: 0 }], nodes: [{ id: key_of(current_state) }],
links: [], links: [],
}; };
graph = null; graph = null;
@ -61,9 +96,10 @@ window.onload = () => {
document.getElementById("state_name").innerHTML = initial_state.name; document.getElementById("state_name").innerHTML = initial_state.name;
clear_visualization(); clear_visualization();
model.visualize(initial_state); model.visualize(initial_state);
states = [initial_state]; clear_states();
states.add(initial_state);
data = { data = {
nodes: [{ id: 0 }], nodes: [{ id: key_of(initial_state) }],
links: [], links: [],
}; };
graph = generate_graph(data, node_click_view_state); graph = generate_graph(data, node_click_view_state);
@ -76,7 +112,7 @@ window.onload = () => {
const node_click_view_state = (node, graph) => { const node_click_view_state = (node, graph) => {
clear_visualization(); clear_visualization();
current_state = states[node.id]; current_state = states.get(node.id);
model.visualize(current_state); model.visualize(current_state);
highlight_node(states, graph, 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]; initial_state = model.initial_states[current_initial_state];
selected_element = null; selected_element = null;
current_state = initial_state; current_state = initial_state;
states = [initial_state]; clear_states();
states.add(initial_state);
graph = null; graph = null;
graph = generate_graph(data, node_click_view_state); graph = generate_graph(data, node_click_view_state);
model.visualize(initial_state); model.visualize(initial_state);
@ -178,7 +215,8 @@ document.getElementById("select_state_button").addEventListener("click", () => {
initial_state = model.initial_states[current_initial_state]; initial_state = model.initial_states[current_initial_state];
current_state = initial_state; current_state = initial_state;
selected_element = null; selected_element = null;
states = [initial_state]; clear_states();
states.add(initial_state);
graph = null; graph = null;
graph = generate_graph(data, node_click_view_state); graph = generate_graph(data, node_click_view_state);
model.visualize(initial_state); model.visualize(initial_state);

View File

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

View File

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

View File

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

View File

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