N个有序元素的组合

时间:2014-02-27 14:43:10

标签: java algorithm hadoop mapreduce

我有一组K元素,我需要创建一个N有序元素的组合 例如,如果K = 1并且我有{X1,空集}和n = 2那么我有一个有序的对我需要这样做:

例1:

 ({},{})  
 ({X1},{}), ({},{X1})  
 ({X1},{X1})  

请注意,我需要按此顺序获取元素:首先将0节点的元素作为两个对的总和,第二个元素为1,ecc

我的想法是制作初始集的部分集合,在时间添加元素,但我正在失去理智。有什么建议?我需要在java中执行此操作。

编辑1: 换句话说,我需要创建一个Hasse图: http://en.wikipedia.org/wiki/Hasse_diagram

其中每个节点都是部件集的一个元素,而部分排序函数包含在所有子集上,如下所示:

示例2:

ni =(S1i,S2i)C nj =(S1j,S2j)仅当S1i C S1j和S21 C s2j

EDIT2:@RONALD:

如果我有一个S = {1,2}和n = 2的K = 2,我需要这个输出:

 level0: ({}, {})  
 level1: ({1}, {}); ({2}, {}); ({}, {1}); ({}, {2})  
 level2: ({1,2}, {}); ({1}, {1}); ({1}, {2}); ({2}, {1}); ({2}, {2}); ({}, {1,2});   
 [..]

级别之间的顺序很重要,例如:

如果在level1我有

 ({1}, {}); ({2}, {}); ({}, {1}); ({}, {2})  

OR

 ({}, {2}); ({}, {1}); ({2}, {}); ({1}, {}); 

是一回事。但重要的是,在第2级我拥有level2的所有超集,并且在示例2

中解释了超集

EDIT3:
如果我的集合是S = {x,y,z}并且我每个节点只有一个集合,那么结果(从底部开始)是这样的: http://upload.wikimedia.org/wikipedia/commons/e/ea/Hasse_diagram_of_powerset_of_3.svg

如果我有S = {1,2}且每个点头两套,结果就是这个(感谢Ronald的图表):
http://www.independit.de/Downloads/hasse.pdf

EDIT4:

因为是一个超指数问题,我的想法是:我计算一个级别(在有序模式下!)并且通过一些规则我修剪一个节点和他的所有超集。另一个停止规则可能是停在某个水平。对于此规则,必须直接以有序的方式计算组合而不是来计算所有然后重新排序。

EDIT5:

Marco13的代码工作正常,我已经做了一些修改:

  • 使用函数PowerSet,因为它有助于使一组S的K元素的所有组合(我只需要获取powerset的第一个tot元素)。

现在算法做了所有,但我需要加快它。有没有办法并行计算?这种方式使用Map Reduce(Apache hadoop实现)范例?

package utilis;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class HasseDiagramTest4
{
    public static void main(String[] args)
    {
        int numberOfSetsPerNode = 3;

        List<Integer> set = Arrays.asList(1, 2, 3, 4, 5,6);
        List<Set<Integer>> powerSet = computePowerSet(set);
        powerSet = KPowerSet(powerSet, 3);

        List<List<Set<Integer>>> prunedNodes = 
            new ArrayList<List<Set<Integer>>>();
        List<Set<Integer>> prunedNode = new ArrayList<Set<Integer>>();

        HashSet<Integer> s = new HashSet<Integer>();
        HashSet<Integer> s_vuoto = new HashSet<Integer>();
        s.add(1);
        s.add(2);
        prunedNode.add(s);
        prunedNode.add(s_vuoto);
        prunedNode.add(s);

       prunedNodes.add(prunedNode);

        compute(ordina(powerSet), numberOfSetsPerNode, prunedNodes);
    }

    private static <T> HashMap<Integer, List<Set<T>>> ordina(List<Set<T>> powerSet) {

        HashMap<Integer, List<Set<T>>> hs = new HashMap<Integer, List<Set<T>>>();

        for(Set<T> l: powerSet)
        {
            List<Set<T>> lput = new  ArrayList<Set<T>>();
            if(hs.containsKey(l.size()))
            {
                lput = hs.get(l.size());
                lput.add(l);
                hs.put(l.size(), lput);
            }
            else
            {
                lput.add(l);
                hs.put(l.size(), lput); 
            }

        }

        return hs;
    }

    private static <T> List<Set<T>> KPowerSet(List<Set<T>> powerSet, int k)
    {
        List<Set<T>> result = new ArrayList<Set<T>>();

        for(Set<T>s:powerSet)
        {
            if(s.size() <= k)
            {
                result.add(s);
            }
        }
        return result;
    }

    private static <T> List<Set<T>> computePowerSet(List<T> set)
    {
        List<Set<T>> result = new ArrayList<Set<T>>();
        int numElements = 1 << set.size();
        for (int j=0; j<numElements; j++)
        {
            Set<T> element = new HashSet<T>();
            for (int i = 0; i < set.size(); i++)
            {
                long b = 1 << i;
                if ((j & b) != 0)
                {
                    element.add(set.get(i));
                }
            }
            result.add(element);
        }
        return result;
    }

    private static List<Integer> createList(int numberOfElements)
    {
        List<Integer> list = new ArrayList<Integer>();
        for (int i=0; i<numberOfElements; i++)
        {
            list.add(i+1);
        }
        return list;
    }

    private static <T> void compute(
            HashMap<Integer, List<Set<T>>> powerSet, int numberOfSetsPerNode,
        List<List<Set<T>>> prunedNodes)
    {
        Set<List<Set<T>>> level0 = createLevel0(numberOfSetsPerNode);
        System.out.println("Level 0:");
        print(level0);

        Set<List<Set<T>>> currentLevel = level0;
        int level = 0;
        while (true)
        {
            Set<List<Set<T>>> nextLevel = 
                createNextLevel(currentLevel, powerSet, prunedNodes);
            if (nextLevel.size() == 0)
            {
                break;
            }

            System.out.println("Next level: "+nextLevel.size()+" nodes");
            print(nextLevel);

            currentLevel = nextLevel;
            level++;
        }
    }

    private static <T> Set<List<Set<T>>> createLevel0(int numberOfSetsPerNode)
    {
        Set<List<Set<T>>> level0 = 
            new LinkedHashSet<List<Set<T>>>();
        List<Set<T>> level0element = new ArrayList<Set<T>>();
        for (int i=0; i<numberOfSetsPerNode; i++)
        {
            level0element.add(new LinkedHashSet<T>());
        }
        level0.add(level0element);
        return level0;
    }

    private static <T> List<Set<T>> getNext(Set<T> current, HashMap<Integer, List<Set<T>>> powerSet)
    {
        ArrayList<Set<T>> ritorno = new ArrayList<Set<T>>(); 
        int level = current.size();
        List<Set<T>> listnext = powerSet.get(level+1);

        if(listnext != null)
        {
            for(Set<T> next: listnext)
            {
                if(next.containsAll(current))
                {
                    ritorno.add(next);
                }
            }
        }

        return ritorno;
    }


    private static <T> Set<List<Set<T>>> createNextLevel(
        Set<List<Set<T>>> currentLevel, HashMap<Integer, List<Set<T>>> powerSet,
        List<List<Set<T>>> prunedNodes)
    {
        Set<List<Set<T>>> nextLevel = new LinkedHashSet<List<Set<T>>>();

        //Per ogni nodo del livello corrente
        for (List<Set<T>> currentLevelElement : currentLevel)
        {
            //Per ogni insieme del nodo preso in considerazione
            for (int i=0; i<currentLevelElement.size(); i++)
            {
                List<Set<T>> listOfnext = getNext (currentLevelElement.get(i), powerSet);


                for (Set<T> element : listOfnext)
                {
                    List<Set<T>> nextLevelElement = copy(currentLevelElement);
                    Set<T> next = element;
                    nextLevelElement.remove(i);
                    nextLevelElement.add(i, next);

                    boolean pruned = false;
                    for (List<Set<T>> prunedNode : prunedNodes)
                    {
                        if (isSuccessor(prunedNode, nextLevelElement))
                        {
                            pruned = true;
                        }
                    }
                    if (!pruned)
                    {
                        nextLevel.add(nextLevelElement);
                    }
                    else
                    {
                        System.out.println("Pruned "+nextLevelElement+ " due to "+prunedNodes);
                    }
                }
            }
        }
        return nextLevel;
    }

    private static <T> boolean isSuccessor(
        List<Set<T>> list, List<Set<T>> successor)
    {
        for (int i=0; i<list.size(); i++)
        {
            Set<T> set = list.get(i);
            Set<T> successorSet = successor.get(i);
            //System.out.println("Successor:" + successorSet + "pruned:" + set);
            if (!successorSet.containsAll(set))
            {
                return false;
            }
        }
        return true;
    }

    private static <T> List<Set<T>> copy(List<Set<T>> list)
    {
        List<Set<T>> result = new ArrayList<Set<T>>();
        for (Set<T> element : list)
        {
            result.add(new LinkedHashSet<T>(element));
        }
        return result;
    }

    private static <T> void print(
        Iterable<? extends Collection<? extends Collection<T>>> sequence)
    {
        for (Collection<? extends Collection<T>> collections : sequence)
        {
            System.out.println("    "+collections);
        }
    }


}

3 个答案:

答案 0 :(得分:1)

因此,如果您有一个基本集S = {1, 2},则K = 2和S的子集集合为{{}, {1}, {2}, {1,2}}。假设 n 仍为2.那么您的输出将类似于

({}, {})
({1}, {}); ({2}, {}); ({}, {1}); ({}, {2})
({1,2}, {});          ({}, {1,2})
({1}, {1}); ({1}, {2}); ({2}, {1}); ({2}, {2})
({1}, {1,2}); ({1,2}, {1}); ({2}, {1,2}); ({1,2}, {2})
({1,2}, {1,2})

正确?输出的排序有点困难,因为结果没有完全排序。但它仍然归结为计数。不像我最初想的那样,(K + 1) - 但是更多(2 ^ K)-ary。

为了确定一个集合是否是另一个集合的子集,使用素数可能是一个想法。 您为原始集的每个元素分配素数。在我的例子中,那将是2和3.可以通过生成素数的所有乘积来构建子集集。在我的例子中,{1 /* empty set */, 2, 3, 6}。 如果你有两套,由你的产品代表,很容易测试包含:

if (a % b == 0) then b is a subset of a

这只是一堆想法,但它们可能会帮助您找到解决方案。当然,主要技巧仅适用于原始集合中相对较少数量的元素,但只要K和N增长,您就会遇到问题。 (输出中的元素数量为(2^K)^N = 2^(NK)。如果K == N == 5,您将拥有2^(5 * 5) = 2^25,大约3200万个输出元素。而这里的主要思想仍然有效。

编辑:我写了一个小的Java程序来展示我的想法。

  1. 将其保存到Hasse.java
  2. 编译它:javac Hasse.java
  3. 运行它:java Hasse > hasse.dot
  4. run dot:dot -Tpdf -ohasse.pdf hasse.dot
  5. 查看它:acroread hasse.pdf
  6. 源代码:

    import java.lang.*;
    import java.util.*;
    
    public class Hasse {
    
            private static int K[] = { 1, 2, 3 };
            private static int N   = 2;
            private static int prime[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97 };
            //
            // PK[0][] is the array of "subsets"
            // PK[1][] is the array of number of elements of K participating in the subset
            //
            private static int PK[][];
    
            // some constants; the initialization is clear enough
            private static final long twoNK = pow(2, N * K.length);
            private static final int twoK = (int) pow(2, K.length);
            private static final int  NK = N * K.length;
            private static final long NKf = fac(NK);
    
            //
            // this power function isn't suitable for large powers
            // but in the range we are working, it's OK
            //
            public static long pow(int b, int p)
            {
                    long result = 1;
                    for (int i = 0; i < p; ++i)
                            result *= b;
    
                    return result;
            }
    
            // fac calculates n! (needed for the a over b calculation)
            public static long fac(int n)
            {
                    long result = 1;
                    for (int i = n; i > 0; --i) result *= i;
    
                    return result;
            }
    
            //
            // constructPK builds the set of subsets of K
            // a subset is represented by a product of primes
            // each element k_i of K has an associated prime p_i
            // since the prime factorization of a number is unique,
            // the product can be translated into a subset and vice versa
            //
            public static void constructPK()
            {
                    int i, cnt;
                    int numElms = twoK;
                    PK = new int[2][numElms];
    
                    for (i = 0; i < numElms; ++i) {
                            int j = i;
                            cnt = 0;
                            PK[0][i] = 1;
                            PK[1][i] = 0;
                            while (j > 0) {
                                    if (j % 2 == 1) {
                                            PK[0][i] *= prime[cnt];
                                            PK[1][i]++;
                                    }
                                    cnt++;
                                    j /= 2;
                            }
                    }
            }
    
            // we have a k-ary number (that is: binary if k == 2, octal if k == 8
            // and so on
            // the addOne() function calculates the next number based on the input
            public static void addOne(int kAry[])
            {
                    int i = 0;
    
                    kAry[i] += 1;
                    while (kAry[i] >= twoK) {
                            kAry[i] = 0;
                            ++i;
                            kAry[i] += 1;
                    }
            }
    
            // the addN() function is similar to the addOne() function
            // with the difference that it add n to the input, not just 1
            public static void addN(int kAry[], int n)
            {
                    int i = 0;
    
                    kAry[i] += n;
                    for (i = 0; i < N - 1; ++i) {
                            while (kAry[i] >= twoK) {
                                    kAry[i] -= twoK;
                                    kAry[i+1] += 1;
                            }
                    }
            }
    
            // from the k-ary number, which represents a node in the graph,
            // the "level" is calculated.
            public static int getLevel(int kAry[])
            {
                    int level = 0;
    
                    for (int i = 0; i < N; ++i) {
                            level += PK[1][kAry[i]];
                    }
                    return level;
            }
    
            // output function for a node
            public static String renderNode(int kAry[])
            {
                    StringBuffer sb = new StringBuffer();
                    String sep = "";
    
                    sb.append("(");
                    for (int i = 0; i < N; ++i) {
                            String setSep = "";
                            int p = PK[0][kAry[i]];
                            sb.append(sep);
                            sb.append("{");
                            for (int j = 0; j < K.length; ++j) {
                                    if (p % prime[j] == 0) {
                                            sb.append(setSep + K[j]);
                                            setSep = ", ";
                                    }
                            }
                            sb.append("}");
                            sep = ", ";
                    }
                    sb.append(")");
    
                    return sb.toString();
            }
    
            // This function calculates the numerical representation
            // of a node, addressed by its level and position within the level,
            // in the k-ary number system
            // if there's a more elegant way of finding the node, it would 
            // largely speed up the calculation, since this function is needed
            // for calculating the edges
            public static int[] getKAry(int level, int node)
            {
                    int kAry[] = new int[N];
                    int nodesSoFar = 0;
    
                    for (int i = 0; i < N; ++i) kAry[i] = 0;
    
                    for (int cnt = 0; cnt < twoNK; ++cnt) {
                            if (getLevel(kAry) == level) {
                                    if (nodesSoFar == node) {
                                            return kAry;
                                    } else
                                            nodesSoFar++;
                            }
                            if (cnt + 1 < twoNK)
                                    addOne(kAry);
                    }
                    return null;
            }
    
            // this function converts the decimal nodeNumber to
            // its k-ary representation
            public static int[] getKAry(int nodeNumber)
            {
                    int kAry[] = new int[N];
    
                    for (int i = 0; i < N; ++i) kAry[i] = 0;
    
                    addN(kAry, nodeNumber);
    
                    return kAry;
            }
    
            public static String getLabel(int level, int node)
            {
                    int kAry[] = getKAry(level, node);
    
                    return (kAry == null ? "Oops!" : renderNode(kAry));
            }
    
            public static void printPK()
            {
                    System.out.println("# Number of elements: " + PK[0].length);
                    for (int i = 0; i < PK[0].length; ++i) {
                            System.out.println("# PK[0][" + i + "] = " + PK[0][i] + ",\tPK[1][" + i + "] = " + PK[1][i]);
                    }
            }
    
            public static void printPreamble()
            {
                    System.out.println("digraph G {");
                    System.out.println("ranksep = 3");
                    System.out.println();
            }
    
            public static void printEnd()
            {
                    System.out.println("}");
            }
    
            public static void printNodes()
            {
                    int numNodes;
    
                    for (int i = 0; i <= NK; ++i) {
                            int level = i + 1;
                            numNodes = (int) (NKf / (fac(i) * fac(NK - i)));
                            for (int j = 0; j < numNodes; ++j) {
                                    System.out.println("level_" + level + "_" + (j+1) + " [shape=box,label=\"" + getLabel(i, j) + "\"];");
                            }
                            System.out.println();
                    }
                    System.out.println();
            }
    
            // having two vectors of "sets", this function determines
            // if each set in the ss (small set) vector is a subset of
            // the corresponding set in the ls (large set) vector
            public static boolean isSubset(int ss[], int ls[])
            {
                    for (int i = 0; i < N; ++i)
                            if (PK[0][ls[i]] % PK[0][ss[i]] != 0) return false;
                    return true;
            }
    
            // this function finds and prints the edges
            // it is called about twoNK times (once for each node)
            // therefore performance optimizations have to be done here
            public static void printEdges(int level, int node, int nodeNumber)
            {
                    int kAry[] = getKAry(node);
                    int nlAry[];
                    int numNodes = (int) (NKf / (fac(level + 1) * fac(NK - level - 1)));
                    String myNode = "level_" + (level + 1) + "_" + (node + 1);
    
                    for (int i = 0; i < numNodes; ++i) {
                            nlAry = getKAry(level + 1, i);
                            if (nlAry == null) System.exit(1);
                            if (isSubset(kAry, nlAry)) {
                                    System.out.println(myNode + " -> level_" + (level + 2) + "_" + (i + 1));
                            }
                    }
            }
    
            // this function renders the dot file
            // first some initial text (preamble),
            // then the nodes and the edges
            // and finally the closing brace
            public static void renderDot()
            {
                    int numNodes;
                    int nodeNumber = 0;
    
                    printPreamble();
                    printNodes();
                    for (int level = 0; level < NK; ++level) {
                            numNodes = (int) (NKf / (fac(level) * fac(NK - level)));
                            for (int node = 0; node < numNodes; ++node) {
                                    // find the edges to the nodes on the next level
                                    printEdges(level, node, nodeNumber);
                                    ++nodeNumber;
                            }
                            System.out.println();
                    }
                    printEnd();
            }
    
            public static void main (String argv[])
            {
                    constructPK();
                    renderDot();
            }
    }
    

答案 1 :(得分:1)

正如评论中所提到的,我很确定实际应该做什么的形式化要么不清楚,要么明显错误。比较“节点”的标准与示例不匹配。但是,一旦指定了排序标准(以Comparator的形式),这应该很容易实现。

这里,比较两个“节点”的标准是节点中所有集合的大小的总和,它与给出的示例相匹配(虽然它直观地没有意义,因为它不对应任何真实的子集关系....)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class HasseDiagramTest
{
    public static void main(String[] args)
    {
        List<Integer> set = Arrays.asList(1, 2);
        List<List<Integer>> powerSet = computePowerSet(set);
        List<List<List<Integer>>> combinations = 
            computeCombinations(powerSet, 2);
        Comparator<List<List<Integer>>> comparator = createComparator();
        Collections.sort(combinations, comparator);
        List<List<List<List<Integer>>>> levels = createLevels(combinations);
        for (List<List<List<Integer>>> level : levels)
        {
            System.out.println(level);
        }
    }

    private static <T> List<List<List<List<T>>>> createLevels(
        List<List<List<T>>> sortedCombinations)
    {
        List<List<List<List<T>>>> levels = new ArrayList<List<List<List<T>>>>();
        int previousTotalSize = -1;
        List<List<List<T>>> currentLevel = null;
        for (int i=0; i<sortedCombinations.size(); i++)
        {
            List<List<T>> combination = sortedCombinations.get(i);
            int totalSize = totalSize(combination);
            if (previousTotalSize != totalSize)
            {
                previousTotalSize = totalSize;
                currentLevel = new ArrayList<List<List<T>>>();
                levels.add(currentLevel);
            }
            currentLevel.add(combination);
        }
        return levels;
    }


    private static <T> Comparator<List<List<T>>> createComparator()
    {
        return new Comparator<List<List<T>>>()
        {
            @Override
            public int compare(List<List<T>> list0, List<List<T>> list1)
            {
                return Integer.compare(totalSize(list0), totalSize(list1));
            }
        };
    }

    private static <T> int totalSize(List<List<T>> lists)
    {
        int totalSize = 0;
        for (List<T> list : lists)
        {
            totalSize += list.size();
        }
        return totalSize;
    }


    private static <T> List<List<T>> computePowerSet(List<T> set)
    {
        List<List<T>> result = new ArrayList<List<T>>();
        int numElements = 1 << set.size();
        for (int j=0; j<numElements; j++)
        {
            List<T> element = new ArrayList<T>();
            for (int i = 0; i < set.size(); i++)
            {
                long b = 1 << i;
                if ((j & b) != 0)
                {
                    element.add(set.get(i));
                }
            }
            result.add(element);
        }
        return result;
    }

    private static <T> List<List<T>> computeCombinations(List<T> list, int sampleSize)
    {
         int numElements = (int) Math.pow(list.size(), sampleSize);
         int chosen[] = new int[sampleSize];
         List<List<T>> result = new ArrayList<List<T>>();
         for (int current = 0; current < numElements; current++)
         {
             List<T> element = new ArrayList<T>(sampleSize);
             for (int i = 0; i < sampleSize; i++)
             {
                 element.add(list.get(chosen[i]));
             }
             result.add(element);
             increase(chosen, list.size());
         }
         return result;
    }

    private static void increase(int chosen[], int inputSize)
    {
        int index = chosen.length - 1;
        while (index >= 0)
        {
            if (chosen[index] < inputSize - 1)
            {
                chosen[index]++;
                return;
            }
            chosen[index] = 0;
            index--;
        }
    }
}

答案 2 :(得分:1)

经过4次EDIT和大量讨论之后,这个应用程序的目标正逐渐变得清晰起来。实际上,人们必须考虑适当的形式化,但最终似乎并不那么困难。

与我的第一个答案(https://stackoverflow.com/a/22092523)相反,这个新答案会迭代地计算上一级别的下一级别(而createNextLevel的核心,只有10行代码)。< / p>

compute方法中,“EDIT4”中要求的修剪可以集成到while循环中。

编辑:评论中还有更多请求。整合它们。但这变得荒谬可笑。 Um den Rest kannst du dichselbstkümmern。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class HasseDiagramTest2
{
    public static void main(String[] args)
    {
        int numberOfElements = 2;
        int numberOfSetsPerNode = 2;

        List<Integer> list = createList(numberOfElements);

        List<List<Set<Integer>>> prunedNodes = 
            new ArrayList<List<Set<Integer>>>();
        List<Set<Integer>> prunedNode = new ArrayList<Set<Integer>>();
        prunedNode.add(Collections.singleton(1));
        prunedNode.add(Collections.singleton(1));
        prunedNodes.add(prunedNode);

        compute(list, numberOfSetsPerNode, prunedNodes);
    }

    private static List<Integer> createList(int numberOfElements)
    {
        List<Integer> list = new ArrayList<Integer>();
        for (int i=0; i<numberOfElements; i++)
        {
            list.add(i+1);
        }
        return list;
    }

    private static <T> void compute(
        List<T> elements, int numberOfSetsPerNode,
        List<List<Set<T>>> prunedNodes)
    {
        Set<List<Set<T>>> level0 = createLevel0(numberOfSetsPerNode);
        System.out.println("Level 0:");
        print(level0);

        Set<List<Set<T>>> currentLevel = level0;
        int level = 0;
        while (true)
        {
            Set<List<Set<T>>> nextLevel = 
                createNextLevel(currentLevel, elements, prunedNodes);
            if (nextLevel.size() == 0)
            {
                break;
            }

            System.out.println("Next level: "+nextLevel.size()+" nodes");
            print(nextLevel);

            currentLevel = nextLevel;
            level++;
        }
    }

    private static <T> Set<List<Set<T>>> createLevel0(int numberOfSetsPerNode)
    {
        Set<List<Set<T>>> level0 = 
            new LinkedHashSet<List<Set<T>>>();
        List<Set<T>> level0element = new ArrayList<Set<T>>();
        for (int i=0; i<numberOfSetsPerNode; i++)
        {
            level0element.add(new LinkedHashSet<T>());
        }
        level0.add(level0element);
        return level0;
    }

    private static <T> Set<List<Set<T>>> createNextLevel(
        Set<List<Set<T>>> currentLevel, List<T> elements,
        List<List<Set<T>>> prunedNodes)
    {
        Set<List<Set<T>>> nextLevel = new LinkedHashSet<List<Set<T>>>();
        for (List<Set<T>> currentLevelElement : currentLevel)
        {
            for (int i=0; i<currentLevelElement.size(); i++)
            {
                for (T element : elements)
                {
                    List<Set<T>> nextLevelElement = copy(currentLevelElement);
                    Set<T> next = nextLevelElement.get(i);
                    boolean changed = next.add(element);
                    if (!changed)
                    {
                        continue;
                    }
                    boolean pruned = false;
                    for (List<Set<T>> prunedNode : prunedNodes)
                    {
                        if (isSuccessor(prunedNode, nextLevelElement))
                        {
                            pruned = true;
                        }
                    }
                    if (!pruned)
                    {
                        nextLevel.add(nextLevelElement);
                    }
                    else
                    {
//                        System.out.println(
//                            "Pruned "+nextLevelElement+
//                            " due to "+prunedNodes);
                    }
                }
            }
        }
        return nextLevel;
    }

    private static <T> boolean isSuccessor(
        List<Set<T>> list, List<Set<T>> successor)
    {
        for (int i=0; i<list.size(); i++)
        {
            Set<T> set = list.get(i);
            Set<T> successorSet = successor.get(i);
            if (!successorSet.containsAll(set))
            {
                return false;
            }
        }
        return true;
    }

    private static <T> List<Set<T>> copy(List<Set<T>> list)
    {
        List<Set<T>> result = new ArrayList<Set<T>>();
        for (Set<T> element : list)
        {
            result.add(new LinkedHashSet<T>(element));
        }
        return result;
    }

    private static <T> void print(
        Iterable<? extends Collection<? extends Collection<T>>> sequence)
    {
        for (Collection<? extends Collection<T>> collections : sequence)
        {
            System.out.println("    "+collections);
        }
    }


}