Skip to content

Commit

Permalink
MONDRIAN: Radically simplify CombiningGenerator (which generates powe…
Browse files Browse the repository at this point in the history
…r sets).

[git-p4: depot-paths = "//open/mondrian/": change = 14672]
  • Loading branch information
julianhyde committed Oct 6, 2011
1 parent c0ea23a commit 32601cf
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 110 deletions.
34 changes: 16 additions & 18 deletions src/main/mondrian/rolap/agg/SegmentLoader.java
Expand Up @@ -9,18 +9,18 @@
*/
package mondrian.rolap.agg;

import mondrian.olap.*;
import mondrian.rolap.*;
import mondrian.rolap.agg.SegmentHeader.ConstrainedColumn;
import mondrian.olap.*;
import mondrian.server.Locus;
import mondrian.util.CombiningGenerator;
import mondrian.util.Pair;

import java.io.Serializable;
import java.sql.*;
import java.util.*;
import java.util.concurrent.*;

import mondrian.server.Locus;
import mondrian.util.CombiningGenerator;
import mondrian.util.Pair;
import org.apache.log4j.Logger;

/**
Expand Down Expand Up @@ -292,7 +292,7 @@ public Boolean call() throws Exception {
// First get a list of all columns that we can turn
// into wildcards.
Set<ConstrainedColumn> columnsToTurnWildcard =
new HashSet<ConstrainedColumn>();
new LinkedHashSet<ConstrainedColumn>();
for (ConstrainedColumn cc
: headerRef.getConstrainedColumns())
{
Expand All @@ -318,20 +318,18 @@ public Boolean call() throws Exception {
continue segLoop;
}

/*
* Now we create a list of all the possible combinations
* of columns being turned into wildcards and search if
* that matches a segment from the caches.
*/
Set<Set<ConstrainedColumn>> combinations =
CombiningGenerator.generate(columnsToTurnWildcard, 1);

// Now try each of these combinations and see if they match
// one of the segments we found. We will analyze the
//combinations by distributing the load across threads.
// Now we create a list of all the possible combinations
// of columns being turned into wildcards and search if
// that matches a segment from the caches. We will analyze the
// combinations by distributing the load across threads.
List<Callable<SegmentHeader>> comboTasks =
new ArrayList<Callable<SegmentHeader>>();
for (final Set<ConstrainedColumn> combo : combinations) {
for (final List<ConstrainedColumn> combo
: CombiningGenerator.of(columnsToTurnWildcard))
{
if (combo.size() < 1) {
continue;
}
comboTasks.add(
new Callable<SegmentHeader>() {
public SegmentHeader call() throws Exception {
Expand Down Expand Up @@ -391,7 +389,7 @@ public SegmentHeader call() throws Exception {

private SegmentHeader analyzeCombo(
final SegmentHeader headerRef,
Set<SegmentHeader.ConstrainedColumn> comb,
List<ConstrainedColumn> comb,
Set<SegmentHeader> matchingSegments)
{
List<ConstrainedColumn> newColValues =
Expand Down
177 changes: 85 additions & 92 deletions src/main/mondrian/util/CombiningGenerator.java
Expand Up @@ -3,127 +3,119 @@
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// Copyright (C) 2011 Julian Hyde and others
// Copyright (C) 2011-2011 Julian Hyde and others
// All Rights Reserved.
// You must accept the terms of that agreement to use this software.
//
*/

package mondrian.util;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

import mondrian.olap.Util;

/**
* Combining generator is a utility class that takes a list
* of objects and creates each and every possible combination
* of those objects.
* A list that, given a collection of elements, contains every possible
* combination of those objects (also known as the
* <a href="http://en.wikipedia.org/wiki/Power_set">power set</a> of those
* objects).
*
* @author LBoudreau
* @author jhyde
*/
public class CombiningGenerator {
public class CombiningGenerator<E> extends AbstractList<List<E>> {

private final E[] elements;
private final int size;

private final static ExecutorService executor =
Util.getExecutorService(
4,
"mondrian.util.CombiningGenerator$ExecutorThread");
/**
* Generates all combinations of a list of objects.
* @param seed The list of objects to combine.
* @return A set of all possible combinations.
* Creates a CombiningGenerator.
*
* @param elements Elements to iterate over
*/
public static <E> Set<Set<E>> generate(Collection<E> seed)
{
return generate(seed, 1);
public CombiningGenerator(Collection<E> elements) {
//noinspection unchecked
this.elements = (E[]) elements.toArray(new Object[elements.size()]);
if (elements.size() > 31) {
// No point having a list with more than 2^31 elements; you can't
// address it using a java (signed) int value. I suppose you could
// have an iterator that is larger... but it would take a long time
// to iterate it.
throw new IllegalArgumentException("too many elements");
}
size = 1 << this.elements.length;
}

/**
* Generates all combinations of a list of objects.
* @param seed The list of objects to combine.
* @param minLength The minimum number of elements
* per combination in order for a combination
* to be valid.
* @return A set of all possible combinations.
* Creates a CombiningGenerator, inferring the type from the argument.
*
* @param elements Elements to iterate over
* @param <T> Element type
* @return Combing generator containing the power set
*/
public static <E> Set<Set<E>> generate(
Collection<E> seed,
int minLength)
{
final Set<Set<E>> result =
Collections.synchronizedSet(
new HashSet<Set<E>>());
final List<FutureTask<Boolean>> tasks =
Collections.synchronizedList(
new ArrayList<FutureTask<Boolean>>());
final List<E> seedList =
Collections.synchronizedList(
new ArrayList<E>());
final AtomicInteger countLatch =
new AtomicInteger(1);

seedList.addAll(seed);
public static <T> CombiningGenerator<T> of(Collection<T> elements) {
return new CombiningGenerator<T>(elements);
}

generateRecursive(countLatch, tasks, seedList, result, minLength);
@Override
public List<E> get(final int index) {
final int size = Integer.bitCount(index);
return new AbstractList<E>() {
public E get(int index1) {
if (index1 < 0 || index1 >= size) {
throw new IndexOutOfBoundsException();
}
int i = nth(index1, index);
return elements[i];
}

while (true) {
if (countLatch.get() == 0) {
return result;
public int size() {
return size;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// no op .
};
}

/**
* Returns the position of the {@code seek}th bit set in {@code b}.
* For example,
* nth(0, 1) returns 0;
* nth(0, 2) returns 1;
* nth(0, 4) returns 2;
* nth(7, 255) returns 7.
*
* <p>Careful. If there are not that many bits set, this method never
* returns.</p>
*
* @param seek Bit to seek
* @param b Integer in which to seek bits
* @return Ordinal of the given set bit
*/
private static int nth(int seek, int b) {
int i = 0, c = 0;
for (;;) {
if ((b & 1) == 1) {
if (c++ == seek) {
return i;
}
}
++i;
b >>= 1;
}
}

private static <E> void generateRecursive(
final AtomicInteger countLatch,
final List<FutureTask<Boolean>> tasks,
final List<E> seed,
final Set<Set<E>> combined,
final int minLength)
{
if (seed.size() < minLength) {
countLatch.getAndDecrement();
return;
}
combined.add(new HashSet<E>(seed));
if (seed.size() == 1) {
combined.add(new HashSet<E>(seed));
countLatch.getAndDecrement();
return;
}
for (int i = 0; i < seed.size(); i++) {
final int position = i;
final FutureTask<Boolean> task = new FutureTask<Boolean>(
new Callable<Boolean>() {
public Boolean call() throws Exception {
List<E> subList =
new ArrayList<E>(seed.subList(0, position));
subList.addAll(
seed.subList(position + 1, seed.size()));
generateRecursive(
countLatch, tasks, subList, combined, minLength);
return true;
}
});
countLatch.getAndIncrement();
tasks.add(task);
executor.execute(task);
}
countLatch.getAndDecrement();
public int size() {
return size;
}

/**
* Ad hoc test. See also UtilTest.testCombiningGenerator.
*
* @param args ignored
*/
public static void main(String[] args) {
List<Object> seed = new ArrayList<Object>();
List<String> seed = new ArrayList<String>();
for (int i = 0; i < 8; i++) {
seed.add(String.valueOf(i));
}
Set<Set<Object>> result = CombiningGenerator.generate(seed, 1);
for (Set<Object> i : result) {
List<List<String>> result = new CombiningGenerator<String>(seed);
for (List<String> i : result) {
for (Object o : i) {
System.out.print("|");
System.out.print(String.valueOf(o));
Expand All @@ -132,4 +124,5 @@ public static void main(String[] args) {
}
}
}
// End CombiningGenerator.java

// End CombiningGenerator.java
54 changes: 54 additions & 0 deletions testsrc/main/mondrian/olap/UtilTestCase.java
Expand Up @@ -1025,6 +1025,60 @@ public void testLcid() {
assertEquals("fr", Util.lcidToLocale((short) 0x040c).toString());
assertEquals("en_GB", Util.lcidToLocale((short) 2057).toString());
}

public void testCombiningGenerator() {
assertEquals(
1,
new CombiningGenerator<String>(Collections.<String>emptyList())
.size());
assertEquals(
1,
CombiningGenerator.of(Collections.<String>emptyList())
.size());
assertEquals(
"[[]]",
CombiningGenerator.of(Collections.<String>emptyList()).toString());
assertEquals(
"[[], [a]]",
CombiningGenerator.of(Collections.singletonList("a")).toString());
assertEquals(
"[[], [a], [b], [a, b]]",
CombiningGenerator.of(Arrays.asList("a", "b")).toString());
assertEquals(
"[[], [a], [b], [a, b], [c], [a, c], [b, c], [a, b, c]]",
CombiningGenerator.of(Arrays.asList("a", "b", "c")).toString());

final List<Integer> integerList =
Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8);
int i = 0;
for (List<Integer> integers : CombiningGenerator.of(integerList)) {
switch (i++) {
case 0:
assertTrue(integers.isEmpty());
break;
case 1:
assertEquals(Arrays.asList(0), integers);
break;
case 6:
assertEquals(Arrays.asList(1, 2), integers);
break;
case 131:
assertEquals(Arrays.asList(0, 1, 7), integers);
break;
}
}
assertEquals(512, i);

// Check that can iterate over 2^20 (~ 1m) elements in reasonable time.
i = 0;
for (List<String> xx
: CombiningGenerator.of(Collections.nCopies( 20, "x")))
{
Util.discard(xx);
i++;
}
assertEquals(1 << 20, i);
}
}

// End UtilTestCase.java

0 comments on commit 32601cf

Please sign in to comment.