There are many puzzles, which are a variant of the classic puzzle "7 Koenigsberg bridges", where you have to find a route through a set of rooms, without even using the door twice.
Here is an example of one without a solution. 
... and is a slightly modified puzzle that has a solution, as you can see here. 
I am interested in a programmatic approach to solving such problems, and although there are several ways to determine that a particular room and door configuration does not have a solution, I am interested in calculating door lists to visit to solve the puzzle. One way to look at a problem is to convert its configuration to a graph and solve for the Hamiltonian. However, such a problem requires binding to inelegant logic due to restrictions that are prohibited by U-Turns.
I cracked the solution in a few minutes to show the problem. This is a brute force solution that puts โroomsโ in groups, with an added invariant that you cannot move from one โdoorโ to another โdoorโ in one room (since this would entail a U-turn).
I feel that there should be a better abstraction to represent this problem, which does not resort to the following "tricks":
The presence of additional logic for removing doors in the same room as the actual options when the path has just left this room.
Creating a graph that is not isomorphic to the input room configuration.
Filtration of all configurations that do not satisfy the spread limit. (Option No. 1)
Is there an existing body of literature that solves these problems, and if so, what are their findings? Are the problems in the room fundamentally different from the methods used by the most well-known graph algorithms, which require this special logic? If there is a better solution that is not a conversion to a schedule, I would also like to hear about it.
Here the existing code that works, groups represent the first problem, groups that are commented out represent the last problem:
// I renamed "groups" to rooms to make the code more clear. var rooms = { 1: ['A','B','C','D'], //1: ['A','B','C','D','P'], 2: ['E', 'D', 'F', 'G'], 3: ['F','I','J','H'], //3: ['F','I','P','J', 'H'], 4: ['I', 'M', 'N', 'O'], 5: ['C','J','M','L','K'], OUTER: ['A', 'B', 'E', 'G', 'H', 'O', 'N', 'L', 'K'] } class Graph { constructor(rooms) { // This is a map of a door letter to the rooms (rooms) that it belongs to. this.roomKey = {}; // The total number of doors this.totalNodes = 0; this.rooms = rooms; // This is only used to produce the number of rooms, but remains in case // I need to adapt the algorithm for the classical approach. this.vertices = {}; for (var key in rooms) { this.addRoom(key, rooms[key]); } } addRoom(roomName, elements) { for (var from of elements) { if (!this.roomKey[from]) { // initialize this.roomKey[from] = [roomName] } else { this.roomKey[from].push(roomName) } for (var to of elements) { // it doesn't make sense to add a vertex to yourself if (from === to) continue // otherwise add the vertex this.addDoor(from, to) } } } addDoor(name, edge) { // initialize if empty if (!this.vertices[name]) { this.vertices[name] = [] this.totalNodes++ } if (this.vertices[name].indexOf(edge) != -1) { console.log(`${name} already has this edge: ${edge}`) } else { this.vertices[name] = this.vertices[name].concat(edge) } } hamiltonian(current, prevRoom, used) { // Find the rooms that this connects to var kpossible = this.roomKey[current] // Find the rooms that connect to this door, but filter those that are // in the room we just came from, this is the hacky part. var possibleRoom = kpossible.find((room) => room !== prevRoom) // Produce all possible rooms, but if we've already been to a room, remove it. var possibleDoors = this.rooms[possibleRoom].filter((elt) => used.indexOf(elt) == -1) if (used.length == this.totalNodes) { console.log("success!", used) return; } // No more possible rooms, this path is no good. if (!possibleDoors || possibleDoors.length === 0) return; for(var door of possibleDoors) { this.hamiltonian(door, possibleRoom, used.concat(door)) } } }
Doors are marked as follows: 