I’m working on an optimal solver for Loyd’s 15 Puzzle, and I came across a technique that uses a 7-8 disjoint pattern database (Korf, Richard E. and Felner, Ariel 2001, Disjoint Pattern Database Heuristics, Artificial Intelligence Jan 2002).
The paper that described the technique stated that each entry, which includes a unique state of a modified version of the puzzle and a short integer heuristic, took one byte or less to store in a database.
Which leads me to my question: How do I store each unique permutation of 8 numbers in 16 possible places and a short integer all in one byte? Or am I on the wrong track and should be storing something different?
Thank you in advance!
2
Answers
By the pidgin-hole principle, you are clearly on the wrong track.
The pidgin-hole principle says that you cannot put more pidgins than you have holes into holes without putting multiple pidgins in one hole. (There is also an infinite version where an infinite number of pidgins in a finite number of holes means that one hole winds up with an infinite number of pidgins.)
In this case there are 518918400 unique permutations of 8 numbers in 16 possible places. A byte can only store 256 unique things. It isn’t going to fit.
You need to somehow store less information or cheat in how it is stored. For example you could store an array with 4 GB of addresses. Just read the position of the array element in base 16 to get what permutation it is for, and the contents of the array element tells you what information you associated with that permutation.
The article is not very explicit, but the technique is reasonably clear. The two disjoint databases consist of all positions of 7 and 8 tiles (respectively) associated with a small integer. Since all positions are stored, it is not necessary to actually store a key in the database. It is sufficient to be able to compute the index of a particular key in the universe of all keys. Then a “database” is simply a vector of small integers, whose size is the total number of possible keys (16!/9! and 16!/8!, respectively). The key is not stored at all, since it is implicit.
The value stored in the database will always fit in a byte (apparently) but later in the paper there is a suggestion for ways to reduce the maximum magnitude of the datum. If you could reduce the maximum value to 15, for example, you could store two entries in a single byte, reducing the size of the database by a factor of 2.
Here’s a reasonably fast algorithm for producing a unique index for a given permutation of a k-subset of values in
[0, n)
. The index it generates is not based on the lexicographical ordering of the permutation, but rather on the generation of the permutation using the Fisher-Yates shuffle algorithm.The FY shuffle produces a uniformly-distributed random permutation of its input vector; it can be modified to produce a uniformly-distributed random permutation of a k-subset of its input vector by simply stopping the shuffle after k steps and truncating the output to k elements. (Stopping the shuffle early is “just” an optimization.)
A FY shuffle requires a sequence of random numbers:
rnd(n), rnd(n-1), ..., rnd(1)
, wherernd(x)
produces a uniformly-distributed random number in the range(0, x]
. Each distinct sequence generates a distinct permutatation, so the permutations can be enumerated by enumerating the possible sequences. A given sequence can be mapped onto a unique integer using the factorial base system.Again, we enumerate k-length permutations by truncating to length-k numbers; instead of using the n weights
n!
,(n-1)!
, …,1!
, we use the firstk
weightsn!
…(n-k+1)!
, renormalizing by dividing each weight by(n - k + 1)!
. In practice, no division is necessary because the number is computed using Horner’s method, so that the index corresponding to d0, d1, …, dk-1 is computed as:To deduce the index from a permutation, we need to deduce the sequence for the FY algorithms by observing where each value in the permutation ends up. The code below is based on a left-to-right FY, which is the opposite of the algorithm in the linked Wikipedia article. (Left-to-right makes truncating the result easier.) So the algorithm to generate a random permutation would be:
The important feature of that algorithm is that step
i
definitely produces the final value ofVec[i]
; subsequent steps will not change Vec[i]. So to reverse the algorithm, we can deduce the value of j at step i by finding where the value atVec[i]
was at the previous step. That’s easy to do if we also track the location for each value.Here’s the actual code, in C99, which might be easier to follow than the above explanation: