RosettaCodeData/Task/Graph-colouring/JavaScript/graph-colouring.js

223 lines
11 KiB
JavaScript

// Equivalent to:
// #include <vector>
// #include <string>
const allColours = ["PINK", "ORANGE", "CYAN", "YELLOW", "RED", "GREEN", "BLUE"];
// Equivalent to:
// class Node { ... };
class Node {
constructor(id, saturation, colour) {
this.id = id; // Equivalent to int32_t id
this.saturation = saturation; // Equivalent to int32_t saturation
this.colour = colour; // Equivalent to std::string colour
this.excludedFromSearch = false; // Equivalent to bool excluded_from_search
}
// The default constructor Node() : id(0), ... is handled implicitly in how we create nodes in the Map.
// If a key doesn't exist, Map.get() returns undefined, we explicitly use Map.set() to create nodes.
}
// Equivalent to:
// int main() { ... }
function main() {
// Equivalent to:
// std::map<int32_t, Node, decltype([](const int32_t& a, const int32_t& b){ return a < b; })> graph;
// In JavaScript, Map keys are ordered by insertion or can be iterated over,
// but there's no built-in custom comparator for sorted access like std::map.
// However, the algorithm iterates through *all* map entries to find the max saturation node,
// so the map's internal order doesn't affect the result.
let graph = new Map(); // Map<number, Node>
// Equivalent to:
// std::map<int32_t, std::set<int32_t, decltype([](const int32_t& a, const int32_t& b) { return a < b; })>, decltype([](const int32_t& a, const int32_t& b) { return a < b; })> neighbours;
// Similar to graph, Set doesn't guarantee element order like std::set,
// but neighbor order isn't relevant to the coloring logic.
let neighbours = new Map(); // Map<number, Set<number>>
// Equivalent to:
// const std::vector<std::string> graph_representations = { ... };
const graphRepresentations = [
"0-1 1-2 2-0 3",
"1-6 1-7 1-8 2-5 2-7 2-8 3-5 3-6 3-8 4-5 4-6 4-7",
"1-4 1-6 1-8 3-2 3-6 3-8 5-2 5-4 5-8 7-2 7-4 7-6",
"1-6 7-1 8-1 5-2 2-7 2-8 3-5 6-3 3-8 4-5 4-6 4-7"
];
// Equivalent to:
// for ( const std::string& graph_representation : graph_representations ) { ... }
for (const graphRepresentation of graphRepresentations) {
graph.clear();
neighbours.clear();
// Equivalent to:
// std::stringstream stream(graph_representation);
// std::string element;
// while ( stream >> element ) { ... }
const elements = graphRepresentation.split(/\s+/); // Split string by one or more whitespace characters
for (const element of elements) {
if (element === "") continue; // Skip empty strings that might result from multiple spaces
// Equivalent to: if ( element.find("-") != std::string::npos ) { ... }
if (element.includes("-")) {
const parts = element.split("-");
// Equivalent to: const int32_t id1 = element[0] - '0';
const id1 = parseInt(parts[0], 10);
// Equivalent to: const int32_t id2 = element[element.length() - 1] - '0';
const id2 = parseInt(parts[parts.length - 1], 10); // Use last part, robust for potential future formats like 10-11
// Ensure nodes exist in the graph map.
// Equivalent to: if ( ! graph.contains(id1) ) { graph[id1] = Node(id1, 0, "NO_COLOUR"); }
if (!graph.has(id1)) {
graph.set(id1, new Node(id1, 0, "NO_COLOUR"));
}
// C++ `Node node1 = graph[id1];` gets a *copy*. In JS, graph.get(id1) gets a *reference*.
// We don't need to store the reference here just yet as we only needed to ensure the node exists.
// Equivalent to: if ( ! graph.contains(id2) ) { graph[id2] = Node(id2, 0, "NO_COLOUR"); }
if (!graph.has(id2)) {
graph.set(id2, new Node(id2, 0, "NO_COLOUR"));
}
// C++ `Node node2 = graph[id2];`
// Ensure neighbor sets exist and add neighbors.
// Equivalent to: neighbours[id1].emplace(id2);
if (!neighbours.has(id1)) {
neighbours.set(id1, new Set()); // Equivalent to std::set<int32_t>
}
neighbours.get(id1).add(id2); // Equivalent to emplace
// Equivalent to: neighbours[id2].emplace(id1);
if (!neighbours.has(id2)) {
neighbours.set(id2, new Set());
}
neighbours.get(id2).add(id1);
} else { // Handle isolated nodes (e.g., "3")
// Equivalent to: const int32_t id = element[0] - '0';
const id = parseInt(element, 10); // Parse the whole element as an ID
// Equivalent to: if ( ! graph.contains(id) ) { graph[id] = Node(id, 0, "NO_COLOUR"); }
if (!graph.has(id)) {
graph.set(id, new Node(id, 0, "NO_COLOUR"));
}
}
}
// Graph Coloring Loop (DSatur-like heuristic)
// Equivalent to: for ( uint64_t i = 0; i < graph.size(); ++i ) { ... }
// This loop structure implies that each node is processed exactly once
// because one node is marked excluded_from_search in each iteration.
for (let i = 0; i < graph.size; ++i) {
let maxNodeId = -1;
let maxSaturation = -1;
// Find the node with the maximum saturation among those not yet colored.
// Equivalent to: for ( const auto& [key, value] : graph ) { ... }
for (const [key, node] of graph) {
// Equivalent to: if ( ! value.excluded_from_search && value.saturation > max_saturation ) { ... }
// If multiple nodes have the same max saturation, the one encountered first in the map iteration order is chosen.
if (!node.excludedFromSearch && node.saturation > maxSaturation) {
maxSaturation = node.saturation;
maxNodeId = key;
}
}
// If maxNodeId is still -1, it means no unexcluded node was found.
// This shouldn't happen until all nodes are processed due to the loop count.
if (maxNodeId === -1) {
// Should only happen if the graph was empty or already fully excluded,
// which the outer loop count should prevent prematurely.
continue; // Skip this iteration if no node found.
}
// Find colours used by neighbours of the selected node.
// Equivalent to: std::unordered_set<std::string> colours_used;
const coloursUsed = new Set(); // Use Set for efficient `has` check
// Equivalent to: for ( const int32_t& neighbour : neighbours[max_node_id] ) { ... }
// Get the set of neighbor IDs for the selected node. Handle case where node has no neighbors entry.
const neighborIdsOfSelected = neighbours.get(maxNodeId) || new Set();
for (const neighbourId of neighborIdsOfSelected) {
// Get the neighbor node object from the graph map
const neighbourNode = graph.get(neighbourId);
// Check if the neighbor node exists in the graph and add its color to the set
if (neighbourNode) {
coloursUsed.add(neighbourNode.colour);
}
}
// Find the smallest available colour.
// Equivalent to: std::string min_colour;
let minColour = "";
// Equivalent to: for ( const std::string& colour : all_colours ) { ... }
for (const colour of allColours) {
// Equivalent to: if ( ! colours_used.contains(colour) ) { ... }
if (!coloursUsed.has(colour)) {
minColour = colour;
break; // Found the first available colour, stop searching.
}
}
// Assign colour to the selected node and mark it as excluded.
// Equivalent to: graph[max_node_id].excluded_from_search = true;
// Equivalent to: graph[max_node_id].colour = min_colour;
const selectedNode = graph.get(maxNodeId); // Get the reference to the node object
selectedNode.excludedFromSearch = true;
selectedNode.colour = minColour;
// Update saturation of neighbours.
// Equivalent to: for ( int32_t neighbour : neighbours[max_node_id] ) { ... }
for (const neighbourId of neighborIdsOfSelected) {
const neighbourNode = graph.get(neighbourId);
// Equivalent to: if ( graph[neighbour].colour == "NO_COLOUR" ) { ... }
// C++ logic: only update saturation if the neighbour is still uncolored.
// Set saturation to the number of *distinct colors* used by the *selected node's* neighbors
// *at the moment the selected node was processed*.
if (neighbourNode && neighbourNode.colour === "NO_COLOUR") {
neighbourNode.saturation = coloursUsed.size; // Uses the size calculated above
}
}
// Note: This saturation update logic is a direct translation of the C++ code.
// A more standard DSatur might update saturation differently (e.g., incrementing
// the saturation of *all* uncolored neighbors if the newly assigned color is new to *their* neighborhood).
// We adhere to the C++ code's exact implementation.
}
// Output the results for the colored graph.
// Equivalent to: std::unordered_set<std::string> graph_colours;
const graphColours = new Set(); // Use Set to count unique colors
// Equivalent to: for ( const auto& [key, value] : graph ) { ... }
// Iterate over the graph map which now contains the final colored nodes.
for (const [key, node] of graph) {
// Equivalent to: graph_colours.emplace(value.colour);
graphColours.add(node.colour);
// Equivalent to: std::cout << "Node " << key << ": colour = " + value.colour;
let output = `Node ${key}: colour = ${node.colour}`;
// Equivalent to: if ( ! neighbours[key].empty() ) { ... }
const neighborIds = neighbours.get(key) || new Set(); // Get neighbors for the current node key
if (neighborIds.size > 0) {
// Equivalent to: std::cout << std::string(8 - value.colour.length(), ' ') << "neighbours = ";
// Calculate padding for alignment based on C++ padding (8 - color length)
const padding = ' '.repeat(Math.max(0, 8 - node.colour.length));
output += `${padding}neighbours = `;
// Equivalent to: for ( const int32_t& neighbour : neighbours[key] ) { std::cout << neighbour << " "; }
// Convert the Set of neighbor IDs to an Array and join them with spaces.
const neighborList = Array.from(neighborIds).join(" ");
output += neighborList;
}
// Equivalent to: std::cout << std::endl;
console.log(output);
}
// Equivalent to: std::cout << "Number of colours used: " << graph_colours.size() << std::endl << std::endl;
console.log(`Number of colours used: ${graphColours.size}`);
console.log(""); // Add an extra newline as in the C++ output
}
}
// Execute the main function
main();