blob: 94d2c83fa41a1aeb347c70cb811dcefd3f74c355 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.examples.dancing;
import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A generic solver for tile laying problems using Knuth's dancing link
* algorithm. It provides a very fast backtracking data structure for problems
* that can expressed as a sparse boolean matrix where the goal is to select a
* subset of the rows such that each column has exactly 1 true in it.
*
* The application gives each column a name and each row is named after the
* set of columns that it has as true. Solutions are passed back by giving the
* selected rows' names.
*
* The type parameter ColumnName is the class of application's column names.
*/
public class DancingLinks<ColumnName> {
private static final Log LOG =
LogFactory.getLog(DancingLinks.class.getName());
/**
* A cell in the table with up/down and left/right links that form doubly
* linked lists in both directions. It also includes a link to the column
* head.
*/
private static class Node<ColumnName> {
Node<ColumnName> left;
Node<ColumnName> right;
Node<ColumnName> up;
Node<ColumnName> down;
ColumnHeader<ColumnName> head;
Node(Node<ColumnName> l, Node<ColumnName> r, Node<ColumnName> u,
Node<ColumnName> d, ColumnHeader<ColumnName> h) {
left = l;
right = r;
up = u;
down = d;
head = h;
}
Node() {
this(null, null, null, null, null);
}
}
/**
* Column headers record the name of the column and the number of rows that
* satisfy this column. The names are provided by the application and can
* be anything. The size is used for the heuristic for picking the next
* column to explore.
*/
private static class ColumnHeader<ColumnName> extends Node<ColumnName> {
ColumnName name;
int size;
ColumnHeader(ColumnName n, int s) {
name = n;
size = s;
head = this;
}
ColumnHeader() {
this(null, 0);
}
}
/**
* The head of the table. Left/Right from the head are the unsatisfied
* ColumnHeader objects.
*/
private ColumnHeader<ColumnName> head;
/**
* The complete list of columns.
*/
private List<ColumnHeader<ColumnName>> columns;
public DancingLinks() {
head = new ColumnHeader<ColumnName>(null, 0);
head.left = head;
head.right = head;
head.up = head;
head.down = head;
columns = new ArrayList<ColumnHeader<ColumnName>>(200);
}
/**
* Add a column to the table
* @param name The name of the column, which will be returned as part of
* solutions
* @param primary Is the column required for a solution?
*/
public void addColumn(ColumnName name, boolean primary) {
ColumnHeader<ColumnName> top = new ColumnHeader<ColumnName>(name, 0);
top.up = top;
top.down = top;
if (primary) {
Node<ColumnName> tail = head.left;
tail.right = top;
top.left = tail;
top.right = head;
head.left = top;
} else {
top.left = top;
top.right = top;
}
columns.add(top);
}
/**
* Add a column to the table
* @param name The name of the column, which will be included in the solution
*/
public void addColumn(ColumnName name) {
addColumn(name, true);
}
/**
* Get the number of columns.
* @return the number of columns
*/
public int getNumberColumns() {
return columns.size();
}
/**
* Get the name of a given column as a string
* @param index the index of the column
* @return a string representation of the name
*/
public String getColumnName(int index) {
return columns.get(index).name.toString();
}
/**
* Add a row to the table.
* @param values the columns that are satisfied by this row
*/
public void addRow(boolean[] values) {
Node<ColumnName> prev = null;
for(int i=0; i < values.length; ++i) {
if (values[i]) {
ColumnHeader<ColumnName> top = columns.get(i);
top.size += 1;
Node<ColumnName> bottom = top.up;
Node<ColumnName> node = new Node<ColumnName>(null, null, bottom,
top, top);
bottom.down = node;
top.up = node;
if (prev != null) {
Node<ColumnName> front = prev.right;
node.left = prev;
node.right = front;
prev.right = node;
front.left = node;
} else {
node.left = node;
node.right = node;
}
prev = node;
}
}
}
/**
* Applications should implement this to receive the solutions to their
* problems.
*/
public interface SolutionAcceptor<ColumnName> {
/**
* A callback to return a solution to the application.
* @param value a List of List of ColumnNames that were satisfied by each
* selected row
*/
void solution(List<List<ColumnName>> value);
}
/**
* Find the column with the fewest choices.
* @return The column header
*/
private ColumnHeader<ColumnName> findBestColumn() {
int lowSize = Integer.MAX_VALUE;
ColumnHeader<ColumnName> result = null;
ColumnHeader<ColumnName> current = (ColumnHeader<ColumnName>) head.right;
while (current != head) {
if (current.size < lowSize) {
lowSize = current.size;
result = current;
}
current = (ColumnHeader<ColumnName>) current.right;
}
return result;
}
/**
* Hide a column in the table
* @param col the column to hide
*/
private void coverColumn(ColumnHeader<ColumnName> col) {
LOG.debug("cover " + col.head.name);
// remove the column
col.right.left = col.left;
col.left.right = col.right;
Node<ColumnName> row = col.down;
while (row != col) {
Node<ColumnName> node = row.right;
while (node != row) {
node.down.up = node.up;
node.up.down = node.down;
node.head.size -= 1;
node = node.right;
}
row = row.down;
}
}
/**
* Uncover a column that was hidden.
* @param col the column to unhide
*/
private void uncoverColumn(ColumnHeader<ColumnName> col) {
LOG.debug("uncover " + col.head.name);
Node<ColumnName> row = col.up;
while (row != col) {
Node<ColumnName> node = row.left;
while (node != row) {
node.head.size += 1;
node.down.up = node;
node.up.down = node;
node = node.left;
}
row = row.up;
}
col.right.left = col;
col.left.right = col;
}
/**
* Get the name of a row by getting the list of column names that it
* satisfies.
* @param row the row to make a name for
* @return the list of column names
*/
private List<ColumnName> getRowName(Node<ColumnName> row) {
List<ColumnName> result = new ArrayList<ColumnName>();
result.add(row.head.name);
Node<ColumnName> node = row.right;
while (node != row) {
result.add(node.head.name);
node = node.right;
}
return result;
}
/**
* Find a solution to the problem.
* @param partial a temporary datastructure to keep the current partial
* answer in
* @param output the acceptor for the results that are found
* @return the number of solutions found
*/
private int search(List<Node<ColumnName>> partial, SolutionAcceptor<ColumnName> output) {
int results = 0;
if (head.right == head) {
List<List<ColumnName>> result = new ArrayList<List<ColumnName>>(partial.size());
for(Node<ColumnName> row: partial) {
result.add(getRowName(row));
}
output.solution(result);
results += 1;
} else {
ColumnHeader<ColumnName> col = findBestColumn();
if (col.size > 0) {
coverColumn(col);
Node<ColumnName> row = col.down;
while (row != col) {
partial.add(row);
Node<ColumnName> node = row.right;
while (node != row) {
coverColumn(node.head);
node = node.right;
}
results += search(partial, output);
partial.remove(partial.size() - 1);
node = row.left;
while (node != row) {
uncoverColumn(node.head);
node = node.left;
}
row = row.down;
}
uncoverColumn(col);
}
}
return results;
}
/**
* Generate a list of prefixes down to a given depth. Assumes that the
* problem is always deeper than depth.
* @param depth the depth to explore down
* @param choices an array of length depth to describe a prefix
* @param prefixes a working datastructure
*/
private void searchPrefixes(int depth, int[] choices,
List<int[]> prefixes) {
if (depth == 0) {
prefixes.add(choices.clone());
} else {
ColumnHeader<ColumnName> col = findBestColumn();
if (col.size > 0) {
coverColumn(col);
Node<ColumnName> row = col.down;
int rowId = 0;
while (row != col) {
Node<ColumnName> node = row.right;
while (node != row) {
coverColumn(node.head);
node = node.right;
}
choices[choices.length - depth] = rowId;
searchPrefixes(depth - 1, choices, prefixes);
node = row.left;
while (node != row) {
uncoverColumn(node.head);
node = node.left;
}
row = row.down;
rowId += 1;
}
uncoverColumn(col);
}
}
}
/**
* Generate a list of row choices to cover the first moves.
* @param depth the length of the prefixes to generate
* @return a list of integer arrays that list the rows to pick in order
*/
public List<int[]> split(int depth) {
int[] choices = new int[depth];
List<int[]> result = new ArrayList<int[]>(100000);
searchPrefixes(depth, choices, result);
return result;
}
/**
* Make one move from a prefix
* @param goalRow the row that should be choosen
* @return the row that was found
*/
private Node<ColumnName> advance(int goalRow) {
ColumnHeader<ColumnName> col = findBestColumn();
if (col.size > 0) {
coverColumn(col);
Node<ColumnName> row = col.down;
int id = 0;
while (row != col) {
if (id == goalRow) {
Node<ColumnName> node = row.right;
while (node != row) {
coverColumn(node.head);
node = node.right;
}
return row;
}
id += 1;
row = row.down;
}
}
return null;
}
/**
* Undo a prefix exploration
* @param row
*/
private void rollback(Node<ColumnName> row) {
Node<ColumnName> node = row.left;
while (node != row) {
uncoverColumn(node.head);
node = node.left;
}
uncoverColumn(row.head);
}
/**
* Given a prefix, find solutions under it.
* @param prefix a list of row choices that control which part of the search
* tree to explore
* @param output the output for each solution
* @return the number of solutions
*/
public int solve(int[] prefix, SolutionAcceptor<ColumnName> output) {
List<Node<ColumnName>> choices = new ArrayList<Node<ColumnName>>();
for(int i=0; i < prefix.length; ++i) {
choices.add(advance(prefix[i]));
}
int result = search(choices, output);
for(int i=prefix.length-1; i >=0; --i) {
rollback(choices.get(i));
}
return result;
}
/**
* Solve a complete problem
* @param output the acceptor to receive answers
* @return the number of solutions
*/
public int solve(SolutionAcceptor<ColumnName> output) {
return search(new ArrayList<Node<ColumnName>>(), output);
}
}