diff --git a/2x2x2.def.tables b/2x2x2.def.tables new file mode 100644 index 0000000..60ede0b Binary files /dev/null and b/2x2x2.def.tables differ diff --git a/ksolve.exe b/ksolve.exe index 194d93c..3435269 100644 Binary files a/ksolve.exe and b/ksolve.exe differ diff --git a/readme.txt b/readme.txt index 6f18a33..9f9cea2 100644 --- a/readme.txt +++ b/readme.txt @@ -1,4 +1,4 @@ - ksolve+ v1.0 + ksolve+ v1.3 (c) 2007-2013 by Kare Krig and Michael Gottlieb @@ -24,6 +24,7 @@ * MaxDepth * Slack * QTM and HTM + * Using Comments * God's Algorithm * Details and Tricks * Pruning Tables @@ -60,7 +61,7 @@ The Name command just says the name of the puzzle you are describing. This is no Set [set_name] [number_of_pieces] [number_of_orientations] -The Set command defines one type of piece in your puzzle; there can be as many types as you want. Pieces in a set should be able to move into each others' position. After the name of the set, you will include the number of pieces of that type in your puzzle, and the number of orientations each piece has. +The Set command defines one type of piece in your puzzle; there can be as many types as you want. Pieces in a set should be able to move into each others' position. After the name of the set, you will include the number of pieces of that type in your puzzle, and the number of orientations each piece has. You must define all the Sets at the start of the definition file, before the solved state, moves, or Ignore command. -- Solved -- @@ -71,7 +72,7 @@ Solved ... End -The Solved command defines the solved state of your puzzle. Of course, you will usually want the puzzle to end up with every piece in its original position and unoriented, but you have the option of solving to a different state. You need to define a permutation for each set in your puzzle. The orientation vector is not required; if you leave it out, ksolve+ will set all of the missing orientations to 0. +The Solved command defines the solved state of your puzzle. Of course, you will usually want the puzzle to end up with every piece in its original position and unoriented, but you have the option of solving to a different state. For each piece type you include, you must give the permutation, but you can leave orientation out (in which case it will be set to all 0s). If you leave out an entire piece type, ksolve+ will give you a permutation of 1 2 ... N and an orientation of all 0s. Permutation vectors and orientation vectors simply describe where every piece in a group is and how they are oriented. A permutation vector is a list of the numbers from 1 to n in some order, such as 2 3 1 4 5 6. This describes where each piece goes - for instance, the 2 in the first spot means that piece number 1 is in spot 2. An orientation vector is a list of n numbers from 0 up to the maximum orientation, such as 0 0 0 1 1 0. A piece marked with a 0 is unoriented, and a piece with an orientation of 1, 2, etc. is oriented by that much. For example, 3x3x3 edges have two orientations each, so with that type of piece your orientation vector will only have 0s and 1s. @@ -84,7 +85,7 @@ Move [move_name] ... End -The Move command defines one of the possible moves and how it affects the pieces in your puzzle. Again, you need to define a permutation for each set in your puzzle, and you can either give the orientation or (to have it set to all 0s) leave it out. +The Move command defines one of the possible moves and how it affects the pieces in your puzzle. Again, for each piece type you include, you must give the permutation, but you can leave orientation out (in which case it will be set to all 0s). If you leave out an entire piece type, ksolve+ will give you a permutation of 1 2 ... N and an orientation of all 0s. ksolve+ will not just understand this move, but also all powers of it. For example, if your puzzle is a 3x3x3 and you define a move of the right face which you call R, ksolve+ will also create moves called R2 and R' automatically. You do not need to define those moves separately. @@ -216,6 +217,12 @@ For instance, a move limit of "F2 1" means that there can be at most one F2 move Like with Slack, QTM, etc. this command will apply to all scrambles until the next MoveLimits command or until the end of the file. If you want to clear all the limits just include a command with no lines between MoveLimits and End. +-- Using Comments -- + +# [string] + +To make a comment, simply type a # at the beginning of the line. ksolve+ will ignore the rest of the line no matter what you write there. These are useful for writing yourself notes about the scrambles or keeping track of which numbers correspond to which pieces. + ###### God's Algorithm ###### ksolve+ can also compute God's Algorithm tables. That is, for each N, it will compute the number of positions that can be solved in N moves but no fewer. You only need a .def file for this. To compute a God's Algorithm table in HTM (Half Turn Metric), use this command: @@ -223,9 +230,9 @@ ksolve+ can also compute God's Algorithm tables. That is, for each N, it will co To compute a God's Algorithm table in QTM (Quarter Turn Metric), use this command: ksolve puzzle.def !q -After finishing the computation of a God's Algorithm table, ksolve+ will print out up to 5 antipodes. These are puzzle positions that require the maximum number of moves to solve. +After finishing the computation of a God's Algorithm table, ksolve+ will print out up to 5 antipodes, with an optimal move sequence for each one. These are puzzle positions that require the maximum possible number of moves to solve. -Currently there is a limitation on the complexity of the puzzle. If the puzzle has more than about 9 * 10^18 possible states (counting all permutation and orientation cases whether or not they can be achieved by the puzzle), ksolve+ will not try to compute these tables. In the future I may remove this restriction, but there will be a speed and memory tradeoff since it is generally more difficult to store and manipulate numbers larger than 64 bits. +ksolve+ uses a few slightly different techniques to store the information here, depending on the complexity of the puzzle (the number of possible states, including positions prevented by Blocks or parity constraints). A larger puzzle may be slower, and also take a bit more memory, per position. ###### Details and Tricks ###### @@ -268,6 +275,12 @@ It is possible to define all of the centers together as one piece group in this ###### Version History ###### (ksolve+) +1.3 Optimized indexing code for non-unique permutations + Changed data structure for moves, speeds everything up + God's Alg tables can be generated for puzzles with > 2^63 positions, although it's slower + God's Alg command prints algs for antipode positions + Comments no longer require a space after the #, and they work in scramble files too + In .def file, can omit parts of a move/solved state 1.2 More gcc optimization = code runs way faster. Who knew? Other optimizations to speed up puzzles with Blocks or permutation-only piece types Various code improvements diff --git a/source/data.h b/source/data.h index 126b975..ec0a3ca 100644 --- a/source/data.h +++ b/source/data.h @@ -70,21 +70,22 @@ struct subprune{ typedef std::string string; typedef std::map Position; typedef std::map > Block; -typedef std::pair MovePair; +typedef std::pair MovePair; typedef std::map PruneTable; typedef std::map PieceTypes; // all the information needed to describe a possible move struct fullmove { string name; - string parentMove; + int id; + int parentID; int qtm; Position state; }; // info about a particular move limit struct MoveLimit { - string name; // name of move (or parent move) to limit + int move; // ID of move (or parent move) to limit int limit; // maximum number of moves of this type bool moveGroup; // is this a group of moves, or just one? Block owned; // pieces that can only be affected by these moves @@ -101,6 +102,6 @@ struct ScrambleDef { std::vector moveLimits; }; -typedef std::map MoveList; +typedef std::map MoveList; #endif diff --git a/source/god.h b/source/god.h index 4fb00dd..5ab951d 100644 --- a/source/god.h +++ b/source/god.h @@ -82,25 +82,32 @@ static bool godTable(Position& solved, MoveList& moves, PieceTypes& datasets, st totalSize *= iter2->second; } - if (logSize >= 63*log(2)) { - std::cerr << "Puzzle is too big for a state to fit in a long long int\n"; - exit(-1); - } - bool using_blocks; if (blocks.size() == 0) using_blocks = false; else using_blocks = true; - // initialize an array of sufficient size and set all to -1 - bool usingMap = false; - signed char* distance = new (std::nothrow) signed char[(std::size_t) totalSize]; - std::map distMap; + // try to initialize an array of sufficient size and set all to -1 + int dataStructure = 0; // 0 = array, 1 = map, + // 2 = map,char> + signed char* distance = NULL; + if (logSize < 31*log(2)) { // just don't bother trying to get >2^31 bytes + distance = new (std::nothrow) signed char[(std::size_t) totalSize]; + } + std::map distMap1; + std::map, signed char> distMap2; long long i; + if (distance == NULL) { std::cout << "Could not allocate array of size " << totalSize << "\n"; - usingMap = true; + if (logSize >= 63*log(2)) { + std::cout << "Puzzle cannot fit in a long long int.\n"; + dataStructure = 2; + } else { + std::cout << "Puzzle can fit in a long long int.\n"; + dataStructure = 1; + } } else { std::cout << "Allocated array of size " << totalSize << "\n"; for (i=0; ifirst] = newSubstate(iter3->second.size); } MoveList::iterator moveIter; - if (usingMap) { - distMap[packPosition(solved, subSizes, datasets)] = 0; - } else { + if (dataStructure==0) { distance[packPosition(solved, subSizes, datasets)] = 0; + } else if (dataStructure==1) { + distMap1[packPosition(solved, subSizes, datasets)] = 0; + } else if (dataStructure==2) { + distMap2[packPosition2(solved, subSizes, datasets)] = 0; } std::cout << "Moves\tPositions\n"; std::cout << depth << "\t" << cnt[depth] << "\n"; // Loop through depths - if (!usingMap) { + if (dataStructure==0) { while (1) { // look for positions at this depth for (i=0; i::iterator mapIter; - for (mapIter = distMap.begin(); mapIter != distMap.end(); mapIter++) { + for (mapIter = distMap1.begin(); mapIter != distMap1.end(); mapIter++) { if (mapIter->second == depth) { temp1 = unpackPosition(mapIter->first, subSizes, datasets, solved); // try all possible moves and see if that position hasn't been visited + for (moveIter = moves.begin(); moveIter != moves.end(); moveIter++){ if (using_blocks) // see if the blocks will prevent this move if (!blockLegal(temp1, blocks, moveIter->second.state)) @@ -185,18 +195,60 @@ static bool godTable(Position& solved, MoveList& moves, PieceTypes& datasets, st long long packTemp = packPosition(temp2, subSizes, datasets); if (metric == 0) { // HTM - if (distMap.find(packTemp) == distMap.end()) { // not visited yet + if (distMap1.find(packTemp) == distMap1.end()) { // not visited yet + cnt[depth+1]++; + distMap1[packTemp] = depth+1; + } + } else if (metric == 1) { // QTM + int newDepth = depth + moveIter->second.qtm; + if (distMap1.find(packTemp) == distMap1.end()) { + cnt[newDepth]++; + distMap1[packTemp] = newDepth; + } else if (distMap1[packTemp] > newDepth) { + cnt[newDepth]++; + distMap1[packTemp] = newDepth; + } + } + } + } + } + + // increment depth and print + depth++; + if (cnt[depth] == 0) break; + std::cout << depth << "\t" << cnt[depth] << "\n"; + } + } else if (dataStructure==2) { + while (1) { + // look for positions at this depth + std::map, signed char>::iterator mapIter; + for (mapIter = distMap2.begin(); mapIter != distMap2.end(); mapIter++) { + if (mapIter->second == depth) { + temp1 = unpackPosition2(mapIter->first, subSizes, datasets, solved); + // try all possible moves and see if that position hasn't been visited + for (moveIter = moves.begin(); moveIter != moves.end(); moveIter++){ + if (using_blocks) // see if the blocks will prevent this move + if (!blockLegal(temp1, blocks, moveIter->second.state)) + continue; + + // apply move and pack new position + applyMove(temp1, temp2, moveIter->second.state, datasets); + std::vector packTemp = packPosition2(temp2, subSizes, datasets); + + if (metric == 0) { // HTM + if (distMap2.find(packTemp) == distMap2.end()) { // not visited yet cnt[depth+1]++; - distMap[packTemp] = depth+1; + distMap2[packTemp] = depth+1; } } else if (metric == 1) { // QTM int newDepth = depth + moveIter->second.qtm; - if (distMap.find(packTemp) == distMap.end()) { + if (distMap2.find(packTemp) == distMap2.end()) { cnt[newDepth]++; - distMap[packTemp] = newDepth; - } else if (distMap[packTemp] > newDepth) { + distMap2[packTemp] = newDepth; + } else if (distMap2[packTemp] > newDepth) { + cnt[distMap2[packTemp]]--; cnt[newDepth]++; - distMap[packTemp] = newDepth; + distMap2[packTemp] = newDepth; } } } @@ -222,34 +274,156 @@ static bool godTable(Position& solved, MoveList& moves, PieceTypes& datasets, st if (cnt[depth-1] < 5) antipodes = cnt[depth-1]; std::cout << "\nPrinting " << antipodes << " antipodes:\n\n"; int antiCnt = 0; - if (usingMap) { + if (dataStructure==0) { + for (i=0; i 0) { + // try all moves to see which leads to the lowest depth + int minDepth = curDepth; + int minIndex = -1; + for (iter3 = solved.begin(); iter3 != solved.end(); iter3++){ + nextPos[iter3->first] = newSubstate(iter3->second.size); + } + for (moveIter = moves.begin(); moveIter != moves.end(); moveIter++){ + if (using_blocks) // see if the blocks will prevent this move + if (!blockLegal(curPos, blocks, moveIter->second.state)) + continue; + + applyMove(curPos, nextPos, moveIter->second.state, datasets); + int nextDepth = distance[packPosition(nextPos, subSizes, datasets)]; + if (nextDepth < minDepth) { + minDepth = nextDepth; + minIndex = moveIter->first; + } + } + + // apply best move + applyMove(curPos, nextPos, moves[minIndex].state, datasets); + curPos = nextPos; + curDepth = minDepth; + std::cout << " " << moves[minIndex].name; + } + + std::cout << ":\n"; + printPosition(temp1); + std::cout << "\n"; + antiCnt++; + if (antiCnt >= antipodes) break; + } + } + delete []distance; + } else if (dataStructure==1) { std::map::iterator mapIter; - for (mapIter = distMap.begin(); mapIter != distMap.end(); mapIter++) { + for (mapIter = distMap1.begin(); mapIter != distMap1.end(); mapIter++) { if (mapIter->second == depth - 1) { + // found an antipode! temp1 = unpackPosition(mapIter->first, subSizes, datasets, solved); + Position curPos = temp1; + Position nextPos; + Position::iterator iter3; + + // find a solution + std::cout << "Antipode solved by"; + int curDepth = depth - 1; + + while (curDepth > 0) { + // try all moves to see which leads to the lowest depth + int minDepth = curDepth; + int minIndex = -1; + for (iter3 = solved.begin(); iter3 != solved.end(); iter3++){ + nextPos[iter3->first] = newSubstate(iter3->second.size); + } + for (moveIter = moves.begin(); moveIter != moves.end(); moveIter++){ + if (using_blocks) // see if the blocks will prevent this move + if (!blockLegal(curPos, blocks, moveIter->second.state)) + continue; + + applyMove(curPos, nextPos, moveIter->second.state, datasets); + int nextDepth = distMap1[packPosition(nextPos, subSizes, datasets)]; + if (nextDepth < minDepth) { + minDepth = nextDepth; + minIndex = moveIter->first; + } + } + + // apply best move + applyMove(curPos, nextPos, moves[minIndex].state, datasets); + curPos = nextPos; + curDepth = minDepth; + std::cout << " " << moves[minIndex].name; + } + + std::cout << ":\n"; printPosition(temp1); std::cout << "\n"; antiCnt++; if (antiCnt >= antipodes) break; } } - } else { - for (i=0; i, signed char>::iterator mapIter; + for (mapIter = distMap2.begin(); mapIter != distMap2.end(); mapIter++) { + if (mapIter->second == depth - 1) { + // found an antipode! + temp1 = unpackPosition2(mapIter->first, subSizes, datasets, solved); + Position curPos = temp1; + Position nextPos; + Position::iterator iter3; + + // find a solution + std::cout << "Antipode solved by"; + int curDepth = depth - 1; + + while (curDepth > 0) { + // try all moves to see which leads to the lowest depth + int minDepth = curDepth; + int minIndex = -1; + for (iter3 = solved.begin(); iter3 != solved.end(); iter3++){ + nextPos[iter3->first] = newSubstate(iter3->second.size); + } + for (moveIter = moves.begin(); moveIter != moves.end(); moveIter++){ + if (using_blocks) // see if the blocks will prevent this move + if (!blockLegal(curPos, blocks, moveIter->second.state)) + continue; + + applyMove(curPos, nextPos, moveIter->second.state, datasets); + int nextDepth = distMap2[packPosition2(nextPos, subSizes, datasets)]; + if (nextDepth < minDepth) { + minDepth = nextDepth; + minIndex = moveIter->first; + } + } + + // apply best move + applyMove(curPos, nextPos, moves[minIndex].state, datasets); + curPos = nextPos; + curDepth = minDepth; + std::cout << " " << moves[minIndex].name; + } + + std::cout << ":\n"; printPosition(temp1); std::cout << "\n"; antiCnt++; if (antiCnt >= antipodes) break; } } + + distMap2.clear(); } - if (usingMap) { - distMap.clear(); - } else { - delete []distance; - } delete []cnt; return true; } @@ -280,6 +454,31 @@ static long long packPosition(Position& position, std::map packPosition2(Position& position, std::map, long long> subSizes, PieceTypes& datasets) { + std::vector packed (subSizes.size()); + int i = 0; + std::map, long long>::iterator iter; + for (iter = subSizes.begin(); iter != subSizes.end(); iter++) { + // then, add a number corresponding to that subSize's part + if (iter->first.second == 0) { + packed[i] = oparVector2Index(position[iter->first.first].orientation, position[iter->first.first].size, datasets[iter->first.first].omod); + } else if (iter->first.second == 1) { + packed[i] = oVector2Index(position[iter->first.first].orientation, position[iter->first.first].size, datasets[iter->first.first].omod); + } else if (iter->first.second == 2) { + packed[i] = pVector2Index(position[iter->first.first].permutation, position[iter->first.first].size); + } else if (iter->first.second == 3) { + packed[i] = pVector3Index(position[iter->first.first].permutation, position[iter->first.first].size); + } else { + std::cerr << "Something wrong with these subSizes!\n"; + exit(-1); + } + i++; + } + + return packed; +} + // "Unpack" a full-puzzle position - convert it from a number into a position static Position unpackPosition(long long position, std::map, long long> subSizes, PieceTypes& datasets, Position& solved) { // construct a new Position with blank versions of everything @@ -316,4 +515,43 @@ static Position unpackPosition(long long position, std::map position, std::map, long long> subSizes, PieceTypes& datasets, Position& solved) { + // construct a new Position with blank versions of everything + Position unpacked; + PieceTypes::iterator iter2; + for (iter2 = datasets.begin(); iter2 != datasets.end(); iter2++) { + substate blankState; + blankState.size = iter2->second.size; + unpacked.insert(std::pair (iter2->first, blankState)); + } + + std::map, long long>::reverse_iterator iter; + int i = position.size() - 1; + for (iter = subSizes.rbegin(); iter != subSizes.rend(); iter++) { + // get the current index + long long curIndex = position[i]; + i--; + + // now convert it into a permutation or orientation + int size = unpacked[iter->first.first].size; + if (iter->first.second == 0) { + unpacked[iter->first.first].orientation = oparIndex2Array(curIndex, size, datasets[iter->first.first].omod); + } else if (iter->first.second == 1) { + unpacked[iter->first.first].orientation = oIndex2Array(curIndex, size, datasets[iter->first.first].omod); + } else if (iter->first.second == 2) { + unpacked[iter->first.first].permutation = pIndex2Array(curIndex, size); + } else if (iter->first.second == 3) { + unpacked[iter->first.first].permutation = pIndex3Array(curIndex, solved[iter->first.first].permutation, solved[iter->first.first].size); + } else { + std::cerr << "Something wrong with these subSizes!\n"; + exit(-1); + } + } + + position.clear(); + + return unpacked; +} + #endif diff --git a/source/indexing.h b/source/indexing.h index d8527a2..425b542 100644 --- a/source/indexing.h +++ b/source/indexing.h @@ -22,25 +22,13 @@ #ifndef INDEXING_H #define INDEXING_H +// Convert vector of orientations into an index static int oVector2Index(std::vector orientations, int omod) { - /* this loop should be unnecessary - for (unsigned int i = 0; i < orientations.size(); i++) { - orientations[i] = orientations[i] % omod; - }*/ - - int tmp = 0; - for (unsigned int i = 0; i < orientations.size(); i++){ - tmp = tmp*omod + orientations[i]; - } - return tmp; + return oVector2Index(orientations.data(), orientations.size(), omod); } +// Convert array of orientations into an index static int oVector2Index(int orientations[], int size, int omod) { - /* this loop should be unnecessary - for (int i = 0; i < size; i++) { - orientations[i] = orientations[i] % omod; - }*/ - int tmp = 0; for (int i = 0; i < size; i++){ tmp = tmp*omod + orientations[i]; @@ -48,13 +36,8 @@ static int oVector2Index(int orientations[], int size, int omod) { return tmp; } -// version of oVector2Index for orientation with parity constraint +// Convert array of orientations (with parity constraint) into an index static int oparVector2Index(int orientations[], int size, int omod) { - /* this loop should be unnecessary - for (int i = 0; i < size; i++) { - orientations[i] = orientations[i] % omod; - }*/ - int tmp = 0; for (int i = 0; i < size - 1; i++){ tmp = tmp*omod + orientations[i]; @@ -62,6 +45,7 @@ static int oparVector2Index(int orientations[], int size, int omod) { return tmp; } +// Convert orientation index into a vector static std::vector oIndex2Vector(int index, int size, int omod) { std::vector orientations; orientations.resize(size); @@ -72,7 +56,7 @@ static std::vector oIndex2Vector(int index, int size, int omod) { return orientations; } -// Non-vector version of oIndex2Vector +// Convert orientation index into an array static int* oIndex2Array(int index, int size, int omod) { int* orientation = new int[size]; for (int i = size - 1; i >= 0; i--){ @@ -82,7 +66,7 @@ static int* oIndex2Array(int index, int size, int omod) { return orientation; } -// version of oIndex2Array for orientation with parity constraint +// Convert orientation index (with parity constraint) into an array static int* oparIndex2Array(int index, int size, int omod) { int* orientation = new int[size]; orientation[size - 1] = 0; @@ -95,18 +79,12 @@ static int* oparIndex2Array(int index, int size, int omod) { return orientation; } +// Convert permutation vector (unique) into an index static int pVector2Index(std::vector permutation) { - int t = 0; - int size = permutation.size(); - for (int i = 0; i < size - 1; i++){ - t *= (size - i); - for (int j = i+1; j permutation[j]) - t++; - } - return t; + return pVector2Index(permutation.data(), permutation.size()); } +// Convert permutation array (unique) into an index static int pVector2Index(int permutation[], int size) { int t = 0; for (int i = 0; i < size - 1; i++){ @@ -118,21 +96,7 @@ static int pVector2Index(int permutation[], int size) { return t; } -static std::vector pIndex2Vector(int index, int size) { - std::vector permutation; - permutation.resize(size); - permutation[size-1] = 1; - for (int i = size - 2; i >= 0; i--){ - permutation[i] = 1 + (index % (size-i)); - index /= (size - i); - for (int j = i+1; j < size; j++) - if (permutation[j] >= permutation[i]) - permutation[j]++; - } - return permutation; -} - -// Non-vector version of pIndex2Vector +// Convert index into a permutation array (unique) static int* pIndex2Array(int index, int size) { int* permutation = new int[size]; permutation[size-1] = 1; @@ -146,128 +110,105 @@ static int* pIndex2Array(int index, int size) { return permutation; } +// Convert permutation vector (non-unique) into an index static long long pVector3Index(std::vector permutation) { - if (permutation.size() == 1) - return 0; - long long index = 0; - std::vector temp_vec; - std::vector::iterator iter; - for (int i = 1; i < permutation[0]; i++){ - temp_vec = permutation; - - iter = find(temp_vec.begin(), temp_vec.end(), i); - if (iter != temp_vec.end()){ - temp_vec.erase(iter); - index += combinations(temp_vec); - } - } - temp_vec = permutation; - temp_vec.erase(temp_vec.begin()); - index += pVector3Index(temp_vec); - return index; + return pVector3Index(permutation.data(), permutation.size()); } -static long long pVector3Index(int permutation[], int size) { - // Quick and ugly, but it works for now - std::vector temp_vec (size); - for (int i = 0; i < size; i++) - temp_vec[i] = permutation[i]; +// Convert permutation array (non-unique) into an index +static long long pVector3Index(int permutation[], unsigned int size) { + if (size < 2) return 0; + int index = 0; - return pVector3Index(temp_vec); -} - -static std::vector pIndex3Vector(long long index, std::vector solved) { - unsigned int vec_length = solved.size(); - std::vector vec; - std::vector temp_vec1, temp_vec2; - std::vector::iterator iter; - vec.resize(vec_length); - int max = solved[0]; - unsigned int i; - int j; - for (i = 0; i < solved.size(); i++) - if (max < solved[i]) - max = solved[i]; - - temp_vec1 = solved; - for (i = 0; i < vec_length; i++){ - for (j = 0; j <= max; j++){ - temp_vec2 = temp_vec1; - iter = find(temp_vec2.begin(), temp_vec2.end(), j); - if (iter != temp_vec2.end()){ - temp_vec2.erase(iter); - int num = combinations(temp_vec2); - if (num <= index) - index -= num; - else - break; + // compute number of times each element appears + std::map counts; + for (unsigned int i = 0; i < size; i++){ + if (counts.find(permutation[i]) == counts.end()) + counts[permutation[i]] = 1; + else + counts[permutation[i]]++; + } + + // compute combinations + long long comb = factorial(size); + if (comb == -1){ // Too big :( + return -1; + } + std::map::iterator iter; + for (iter = counts.begin(); iter != counts.end(); iter++) + comb /= factorial(iter->second); + + unsigned int vecsize = size; + for (unsigned int ptr = 0; ptr < size; ptr++) { + for (int i=1; i < permutation[ptr]; i++) { + if (counts[i] > 0) { // i still in permutation + // add the number of combinations of our permutation without one i + index += (comb * counts[i])/vecsize; } } - vec[i] = j; - iter = find(temp_vec1.begin(), temp_vec1.end(), j); - temp_vec1.erase(iter); + // "remove" the first element of the permutation + comb = (comb * counts[permutation[ptr]])/vecsize; + vecsize--; + counts[permutation[ptr]]--; } - return vec; + + return index; +} + +// Convert index into a permutation array (non-unique) +static int* pIndex3Array(long long index, std::vector solved) { + return pIndex3Array(index, solved.data(), solved.size()); } -// Non-vector version of pIndex3Vector +// Convert index into a permutation array (non-unique) static int* pIndex3Array(long long index, int* solved, int size) { - int i, j; - - // Determine maximum value in solved array - int max = solved[0]; - for (int i = 0; i < size; i++) - if (max < solved[i]) - max = solved[i]; - int* vec = new int[size]; - std::vector temp_vec1 (size), temp_vec2; - std::vector::iterator iter; - for (i = 0; i counts; + std::map::iterator iter; + for (int i = 0; i < size; i++){ + if (counts.find(solved[i]) == counts.end()) + counts[solved[i]] = 1; + else + counts[solved[i]]++; } - for (i = 0; i < size; i++){ - for (j = 0; j <= max; j++){ - temp_vec2 = temp_vec1; - iter = find(temp_vec2.begin(), temp_vec2.end(), j); - if (iter != temp_vec2.end()){ - temp_vec2.erase(iter); - int num = combinations(temp_vec2); - if (num <= index) - index -= num; - else - break; + // compute combinations + long long comb = factorial(size); + int combsize = size; + if (comb == -1){ // Too big, WTF? + return solved; + } + for (iter = counts.begin(); iter != counts.end(); iter++) + comb /= factorial(iter->second); + + // now build vec + for (int i=0; i < size; i++) { + // loop over each thing in solved + for (iter = counts.begin(); iter != counts.end(); iter++) { + // if this thing is still in our permutation + if (iter->second > 0) { + // get the number of combinations of the permutation without one thing + long long num = (comb * iter->second)/combsize; + // if we can subtract it from index, do so; otherwise we found the ith thing + if (num <= index) + index -= num; + else + break; } } - vec[i] = j; - iter = find(temp_vec1.begin(), temp_vec1.end(), j); - temp_vec1.erase(iter); + // store this thing, and "remove" the first element of solved + vec[i] = iter->first; + comb = (comb * iter->second)/combsize; + combsize--; + counts[iter->first]--; } return vec; } static long long combinations(std::vector vec) { - std::map counter; - std::map::iterator iter; - for (unsigned int i = 0; i < vec.size(); i++){ - if (counter.find(vec[i]) == counter.end()) - counter[vec[i]] = 1; - else - counter[vec[i]]++; - } - - int sum = 0; - for (iter = counter.begin(); iter != counter.end(); iter++){ - sum += iter->second; - } - long long comb = factorial(sum); - if (comb == -1){ // Too big to compute - return -1; - } - for (iter = counter.begin(); iter != counter.end(); iter++) - comb /= factorial(iter->second); - return comb; + return combinations(vec.data(), vec.size()); } static long long combinations(int vec[], int size) { @@ -280,11 +221,7 @@ static long long combinations(int vec[], int size) { counter[vec[i]]++; } - int sum = 0; - for (iter = counter.begin(); iter != counter.end(); iter++){ - sum += iter->second; - } - long long comb = factorial(sum); + long long comb = factorial(size); if (comb == -1){ // Too big to compute return -1; } @@ -306,17 +243,7 @@ static long long factorial(long long x) { } static std::vector packVector(std::vector vec){ - unsigned int size = vec.size(); - std::vector result (1 + size/8); - - for (unsigned int i = 0; i < size; i += 8) { - long long element = 0; - for (unsigned int j = 0; j < 8; j++) - if (i+j < size) element += ((long long)vec[i+j]) << (8*j); - result[i/8] = element; - } - - return result; + return packVector(vec.data(), vec.size()); } static std::vector packVector(int vec[], int size){ @@ -348,4 +275,17 @@ static std::vector unpackVector(std::vector vec){ return result; } +// find out if a permutation is even +/* +static bool isEven(int[] vec, int size) { + // silly O(n^2) alg + int transpositions = 0; + for (int i=0; ivec[j]) transpositions++; + } + } + return (transpositions%2==0); +}*/ + #endif diff --git a/source/main.cpp b/source/main.cpp index 6cd5038..d06e9bd 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -73,7 +73,7 @@ struct ksolve { int i = 0; MoveList::iterator moveIter; for (moveIter = moves.begin(); moveIter != moves.end(); moveIter++) { - if (moveIter->second.name != moveIter->second.parentMove) { + if (moveIter->first != moveIter->second.parentID) { if (i>0) std::cout << ", "; i++; std::cout << moveIter->second.name; @@ -115,7 +115,6 @@ struct ksolve { int depth = 0; string temp_a, temp_b; temp_a = " "; - temp_b = "."; std::cout << "\nSolving \"" << scramble.name << "\"\n"; @@ -155,7 +154,7 @@ struct ksolve { // The tree-search for the solution(s) int usedSlack = 0; while(1) { - boolean foundSolution = treeSolve(scramble.state, solved, moves, datasets, tables, forbidden, scramble.ignore, blocks, depth, scramble.metric, scramble.moveLimits, temp_a, temp_b); + boolean foundSolution = treeSolve(scramble.state, solved, moves, datasets, tables, forbidden, scramble.ignore, blocks, depth, scramble.metric, scramble.moveLimits, temp_a, -1); if (foundSolution || usedSlack > 0) { usedSlack++; if (usedSlack > scramble.slack) break; diff --git a/source/move.h b/source/move.h index 5cb88df..1109744 100644 --- a/source/move.h +++ b/source/move.h @@ -142,7 +142,23 @@ static substate newSubstate(int size) { // does this limit apply to this move? static bool limitMatches(MoveLimit& limit, fullmove& move) { - return limit.name == (limit.moveGroup ? move.parentMove : move.name); + return limit.move == (limit.moveGroup ? move.parentID : move.id); +} + +static int getMoveID(string name, MoveList& moves) { + MoveList::iterator iter; + for (iter = moves.begin(); iter != moves.end(); iter++) { + if (iter->second.name == name) return iter->first; + } + return -1; +} + +static bool moveIn(string name, MoveList& moves) { + MoveList::iterator iter; + for (iter = moves.begin(); iter != moves.end(); iter++) { + if (iter->second.name == name) return true; + } + return false; } static void processMoveLimits(MoveList& moves, std::vector limits) { diff --git a/source/pruning.h b/source/pruning.h index a71965a..ff65d31 100644 --- a/source/pruning.h +++ b/source/pruning.h @@ -414,7 +414,7 @@ static std::vector buildCompletePermutationPruningTable(std::vector s for (int p = 0; p < tablesize; p++){ if (table[p] == len){ for (iter = moves.begin(); iter != moves.end(); iter++){ - int q = pVector2Index(applySubmoveP(pIndex2Vector(p, vector_size), iter->second.state[setname].permutation, iter->second.state[setname].size)); + int q = pVector2Index(applySubmoveP(pIndex2Array(p, vector_size), iter->second.state[setname].permutation, vector_size), vector_size); if (table[q] == -1){ table[q] = len + 1; c++; @@ -433,7 +433,7 @@ static std::vector buildCompletePermutationPruningTable(std::vector s c = 0; for (int i = 0; i < tablesize; i++){ if (table[i] != -1){ - std::vector tmp_p = pIndex2Vector(i, vector_size); + int* tmp_p = pIndex2Array(i, vector_size); bool solved_pos = true; for (int j = 0; j < vector_size; j++) if (ignore[j] == 0 && tmp_p[j] != solved[j]) @@ -444,6 +444,7 @@ static std::vector buildCompletePermutationPruningTable(std::vector s } else table[i] = -1; + delete tmp_p; } } std::cout << c << " solved positions.\n"; @@ -455,7 +456,7 @@ static std::vector buildCompletePermutationPruningTable(std::vector s for (int p = 0; p < tablesize; p++){ if (table[p] == len){ for (iter = moves.begin(); iter != moves.end(); iter++){ - int q = pVector2Index(applySubmoveP(pIndex2Vector(p, vector_size), iter->second.state[setname].permutation, iter->second.state[setname].size)); + int q = pVector2Index(applySubmoveP(pIndex2Array(p, vector_size), iter->second.state[setname].permutation, vector_size), vector_size); if (table[q] == -1){ table[q] = len + 1; c++; @@ -497,7 +498,7 @@ static std::vector buildCompletePermutationPruningTable3(std::vector if (table[p] == len){ for (iter = moves.begin(); iter != moves.end(); iter++){ // FIX, assumes that inverses to all moves are also one move - int q = pVector3Index(applySubmoveP(pIndex3Vector(p, solved), iter->second.state[setname].permutation, iter->second.state[setname].size)); + int q = pVector3Index(applySubmoveP(pIndex3Array(p, solved), iter->second.state[setname].permutation, vector_size), vector_size); // FIX if (table[q] == -1){ table[q] = len + 1; @@ -515,7 +516,7 @@ static std::vector buildCompletePermutationPruningTable3(std::vector c = 0; for (int i = 0; i < tablesize; i++){ if (table[i] != -1){ - std::vector tmp_p = pIndex3Vector(i, solved); + int* tmp_p = pIndex3Array(i, solved); bool solved_pos = true; for (int j = 0; j < vector_size; j++) if (ignore[j] == 0 && tmp_p[j] != solved[j]) @@ -526,6 +527,7 @@ static std::vector buildCompletePermutationPruningTable3(std::vector } else table[i] = -1; + delete tmp_p; } } std::cout << c << " solved positions.\n"; @@ -539,7 +541,7 @@ static std::vector buildCompletePermutationPruningTable3(std::vector if (table[p] == len){ for (iter = moves.begin(); iter != moves.end(); iter++){ // FIX, assumes that inverses to all moves are also one move - int q = pVector3Index(applySubmoveP(pIndex3Vector(p, solved), iter->second.state[setname].permutation, iter->second.state[setname].size)); + int q = pVector3Index(applySubmoveP(pIndex3Array(p, solved), iter->second.state[setname].permutation, vector_size), vector_size); // FIX if (table[q] == -1){ table[q] = len + 1; diff --git a/source/readdef.h b/source/readdef.h index 0d78d01..5ff6a97 100644 --- a/source/readdef.h +++ b/source/readdef.h @@ -25,6 +25,7 @@ class Rules { public: Rules(string filename){ + moveid = 0; std::ifstream fin(filename.c_str()); if (!fin.good()){ std::cout << "Can't open puzzle definition!\n"; @@ -44,7 +45,11 @@ class Rules { if (datasets.find(setname) != datasets.end()) { std::cerr << "Set " << setname << " declared more than once.\n"; exit(-1); - } + } + if (moves.size() > 0 || solved.size() > 0 || ignore.size() > 0) { + std::cerr << "You must define all sets first!\n"; + exit(-1); + } fin >> datasets[setname].size; if (fin.fail() || datasets[setname].size < 1){ std::cerr << "Set " << setname << " does not have positive size.\n"; @@ -62,21 +67,22 @@ class Rules { else if (command == "Move"){ string movename, setname; fin >> movename; - if (moves.find(movename) != moves.end()) { + if (moveIn(movename, moves)) { std::cerr << "Move " << movename << " declared more than once.\n"; exit(-1); } fullmove newMove; newMove.name = movename; - newMove.parentMove = movename; + newMove.id = moveid; + newMove.parentID = moveid; newMove.qtm = 1; newMove.state = readPosition(fin, true, false, "move "+movename); - - parentMoves.push_back(movename); - addPowers(newMove, datasets); + parentMoves.push_back(moveid); + moves[moveid] = newMove; + moveid++; + addPowers(newMove, moveid-1, datasets); adjustOParity(datasets, newMove.state); - moves[movename] = newMove; } else if (command == "Solved"){ solved = readPosition(fin, false, true, "solved state"); @@ -89,7 +95,7 @@ class Rules { std::cerr << "Error reading forbidden pairs.\n"; exit(-1); } - if (moves.find(movename1) == moves.end()){ + if (moveIn(movename1, moves)){ std::cerr << "Move " << movename1 << " used in forbidden pairs is not previously declared.\n"; exit(-1); } @@ -99,12 +105,13 @@ class Rules { std::cerr << "Error reading forbidden pairs.\n"; exit(-1); } - if (moves.find(movename2) == moves.end()){ + if (moveIn(movename2, moves)){ std::cerr << "Move " << movename2 << " used in forbidden pairs is not previously declared.\n"; exit(-1); } - forbidden.insert(MovePair(movename1, movename2)); + forbidden.insert(MovePair(getMoveID(movename1, moves), + getMoveID(movename2, moves))); fin >> movename1; } } @@ -118,27 +125,27 @@ class Rules { } else if (command == "ForbiddenGroups"){ string line, move1; - std::vector group; + std::vector group; getline(fin, line); getline(fin, line); std::istringstream input(line); input >> move1; while(move1 != "End"){ - if (moves.find(move1) == moves.end()){ + if (!moveIn(move1, moves)){ std::cerr << "Move " << move1 << " used in ForbiddeGroups is not previously declared.\n"; exit(-1); } group.clear(); - group.push_back(move1); + group.push_back(getMoveID(move1, moves)); while(!input.eof()){ input >> move1; if (!input.fail()){ - if (moves.find(move1) == moves.end()){ + if (!moveIn(move1, moves)){ std::cerr << "Move " << move1 << " used in ForbiddenGroups is not previously declared.\n"; exit(-1); } - group.push_back(move1); + group.push_back(getMoveID(move1, moves)); } } @@ -202,7 +209,7 @@ class Rules { fin >> newmove; } } - else if (command == "#"){ // Comment + else if (command.at(0) == '#'){ // Comment char buff[500]; fin.getline(buff,500); } @@ -283,20 +290,20 @@ class Rules { private: string name; // The name of the puzzle + int moveid; // move ID (serial) PieceTypes datasets; // Size and properties of the state-data Position solved; Position ignore; // 0 = solve piece, 1 = don't solve piece MoveList moves; // Possible moves of the puzzle - std::vector parentMoves; // Names of parent moves + std::vector parentMoves; // IDs of parent moves std::set forbidden; std::vector blocks; - std::map > moveGroups; std::map moveLimits; // limits on # of moves - // Add all powers of this move, and keep track of them in moveGroups - void addPowers(fullmove move, PieceTypes& datasets) { - std::vector moveGroup; - moveGroup.push_back(move.name); + // Add all powers of this move + void addPowers(fullmove move, int parentid, PieceTypes& datasets) { + std::vector moveGroup; + moveGroup.push_back(parentid); // Find order of move Position fixedState; // fix state to remove weird orientations @@ -340,13 +347,15 @@ class Rules { string newName = ss.str(); // add updated move to moveGroup and list of moves - moveGroup.push_back(newName); + moveGroup.push_back(moveid); fullmove newMove; newMove.name = newName; - newMove.parentMove = move.name; + newMove.parentID = parentid; + newMove.id = moveid; newMove.qtm = qtm; newMove.state.insert(move2.state.begin(), move2.state.end()); - moves[newName] = newMove; + moves[moveid] = newMove; + moveid++; } // Forbid all pairs @@ -355,8 +364,6 @@ class Rules { forbidden.insert(MovePair(moveGroup[i], moveGroup[j])); } } - - moveGroups[move.name] = moveGroup; } // determine which moves are parallel, and process them @@ -378,12 +385,12 @@ class Rules { // if so, forbid any move with parent i followed by any move with parent j for (iter1 = moves.begin(); iter1 != moves.end(); iter1++) { - if (0 == iter1->second.parentMove.compare(parentMoves[i])) { + if (iter1->second.parentID == parentMoves[i]) { for (iter2 = moves.begin(); iter2 != moves.end(); iter2++) { - if (0 == iter2->second.parentMove.compare(parentMoves[j])) { + if (iter2->second.parentID == parentMoves[j]) { // if we have not already forbidden the [j,i] move pair, forbid the [i,j] one - if (forbidden.find(MovePair(iter2->second.name, iter1->second.name)) == forbidden.end()) { - forbidden.insert(MovePair(iter1->second.name, iter2->second.name)); + if (forbidden.find(MovePair(iter2->first, iter1->first)) == forbidden.end()) { + forbidden.insert(MovePair(iter1->first, iter2->first)); } } } @@ -467,6 +474,35 @@ class Rules { // get next setname fin >> setname; } + + // add "solved" permutations for all undeclared positions! + PieceTypes::iterator pieceIter; + for (pieceIter = datasets.begin(); pieceIter != datasets.end(); pieceIter++) { + string setname = pieceIter->first; + if (newPosition.find(setname) == newPosition.end()) { // piece not included + newPosition[setname] = newSubstate(datasets[setname].size); + if (newPosition[setname].permutation == NULL || newPosition[setname].orientation == NULL){ + std::cerr << "Could not allocate memory in " << title << ".\n"; + exit(-1); + } + for (i = 0; i < datasets[setname].size; i++){ + newPosition[setname].orientation[i] = 0; + } + if (title == "Ignore command") { // ignore-type, permutation should be all 0's + for (i = 0; i < datasets[setname].size; i++){ + newPosition[setname].permutation[i] = 0; + } + } else { // permutation-type, permutation should be 1 2 3 ... + for (i = 0; i < datasets[setname].size; i++){ + newPosition[setname].permutation[i] = i+1; + } + if (setUnique) { + datasets[setname].uniqueperm = true; + } + } + } + } + return newPosition; } }; diff --git a/source/readscramble.h b/source/readscramble.h index b7800ae..e32742b 100644 --- a/source/readscramble.h +++ b/source/readscramble.h @@ -201,17 +201,18 @@ class Scramble } // apply move called movename to solved, if possible - if (moves.find(movename) == moves.end()) { + if (!moveIn(movename, moves)) { std::cerr << "Move " << movename << " in scramble " << name << " is unknown.\n"; exit(-1); } + if (blocks.size() != 0) { - if (!blockLegal(state, blocks, moves[movename].state)) { + if (!blockLegal(state, blocks, moves[getMoveID(movename, moves)].state)) { std::cerr << "Move " << movename << " in scramble " << name << " is blocked.\n"; exit(-1); } } - applyMove(state, new_state, moves[movename].state, datasets); + applyMove(state, new_state, moves[getMoveID(movename, moves)].state, datasets); // copy new_state to state for (iter = datasets.begin(); iter != datasets.end(); iter++) { @@ -364,7 +365,7 @@ class Scramble if (ml.moveGroup) movename = movename.substr(0, movename.size()-1); - if (moves.find(movename) == moves.end()) { + if (!moveIn(movename, moves)) { std::cerr << "Move " << movename << " used in move list is not previously declared.\n"; exit(-1); } @@ -373,7 +374,7 @@ class Scramble std::cerr << "Error reading move limits.\n"; exit(-1); } - ml.name = movename; + ml.move = getMoveID(movename, moves); ml.limit = limit; // calculate block @@ -409,7 +410,11 @@ class Scramble fin >> movename; } } - else if (command == ""){} // To avoid trouble with extra rows on the end. + else if (command.at(0) == '#'){ // Comment + char buff[500]; + fin.getline(buff,500); + } + else if (command == "") {} // Empty line else { std::cerr << "Unknown command \"" << command << "\" in scramble file.\n"; exit(-1); diff --git a/source/search.h b/source/search.h index 8711e37..9121554 100644 --- a/source/search.h +++ b/source/search.h @@ -22,7 +22,7 @@ #ifndef SEARCH_H #define SEARCH_H -static bool treeSolve(Position state, Position& solved, MoveList& moves, PieceTypes& datasets, PruneTable& prunetables, std::set& forbiddenPairs, Position& ignore, std::vector& blocks, int depth, int metric, std::vector& moveLimits, string sequence, string old_move){ +static bool treeSolve(Position state, Position& solved, MoveList& moves, PieceTypes& datasets, PruneTable& prunetables, std::set& forbiddenPairs, Position& ignore, std::vector& blocks, int depth, int metric, std::vector& moveLimits, string sequence, int old_move){ // if we ran out of depth, it's either solved or not if (depth <= 0) { if (isSolved(state, solved, ignore, datasets)){ @@ -100,7 +100,7 @@ static bool treeSolve(Position state, Position& solved, MoveList& moves, PieceTy } // recurse! - if (treeSolve(new_state, solved, moves, datasets, prunetables, forbiddenPairs, ignore, blocks, newDepth, metric, moveLimits, sequence + " " + iter->first, iter->first)) + if (treeSolve(new_state, solved, moves, datasets, prunetables, forbiddenPairs, ignore, blocks, newDepth, metric, moveLimits, sequence + " " + iter->second.name, iter->first)) success = true; // clean up modified move limits