组合算法思想:

本程序的思路是开一个数组,其下标表示1到m个数,数组元素的值为1表示其下标 代表的数被选中,为0则没选中。    
首先初始化,将数组前n个元素置1,表示第一个组合为前n个数。然后从左到右扫描数组元素值的“10”组合,找到第一个“10”组合后将其变为 “01”组合,同时将其左边的所有“1”全部移动到数组的最左端。 当第一个“1”移动到数组的m-n的位置,即n个“1”全部移动到最右端时,就得到了最后一个组合。

该思想是基于数值编码原理的,在Java中可以使用BitSet来代替一个数组,用来表示某个字符串的某些位是否被占用,如果被占用,则为true,并根据上述思想构造的BitSet来确定某一个组合。

例如,对字符串12345进行5取2组合:

11100 -> ***45
11010 -> **3*5
10110 -> *2**5
01110
11001 -> **34*
10101 -> *2*4*
01101 -> 1**4*
10011 -> *23**
10100 -> 1*3**
00111 -> 12***

通过分析可知,如果需要将遇到10后左侧的1进行调整,那么此时0一定位于最左侧,也就是说,最左侧可能存在1个0或者连续个0。

根据上述分析,使用单线程,对每一个字符串创建一个线程进行组合处理,用Java实现组合过程:

首先创建一个能够对单独字符串进行组合的单线程SplitterThread类,在该线程类中实现单个字符串的组合逻辑实现;

然后将其作为Splitter的内部类,在Splitter中提供每个线程类所需要的公共变量,例如星号字符的个数,字符串的集合(需要对各个线程进行同步)。

最后等待所有的线程都执行完成,合并组合的结果集。

代码如下所示:

package org.shirdrn;
import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;/**
 * 组合拆分类
 * 
 * 根据从复式投注字符串经过拆分过滤后得到的单式投注字符串集合,对其中每一个单式投注字符串进行组合<br>
 * 例如:输入集合{31311133,33113330},Splitter类会遍历该集合,对每个单式投注注字符串,创建一个SplitterThread<br>
 * 线程来处理,如果是2串1组合,即starCount=8-2=6,经过线程处理得到类似******33,*****1*3等结果
 * 
 * @author shirdrn
 *
 */
 public class Splitter {

 private int starCount;
 private boolean duplicate;
 private Collection<String> filteredContainer;

 public Collection<String> getFilteredContainer() {
    return filteredContainer;
 }

 /**
 * 构造一个Spilitter实例
 * 
 * @param container 从复式投注字符串经过拆分过滤后得到的单式投注字符串集合
 * @param starCount 如果对于N长比赛,进行M组合过关,则starCount=N-M
 * @param duplicate 是否去重
 */
 public Splitter(Collection<String> container, int starCount, boolean duplicate) {
    this.duplicate = duplicate;
    this.starCount = starCount;
    if(this.duplicate) { // 根据指定是否去重的选择,选择创建容器
     filteredContainer = Collections.synchronizedSet(new HashSet<String>());
    }
    else {
     filteredContainer = Collections.synchronizedList(new ArrayList<String>());
    }
    Iterator<String> it = container.iterator();
    while(it.hasNext()) {
     new Thread(new SplitterThread(it.next().trim())).start();
    }
    try {
     Thread.sleep(50);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
 }

 /**
 * 对一个指定的N场比赛的长度为N的单式投注字符串进行组合<br>
 * 输入单式投注注字符串string,例如31311133,组合得到类似******33,*****1*3,... ...结果的集合
 * 
 * @author shirdrn
 *
 */
 class SplitterThread implements Runnable {   private char[] charArray;
    private int len;   private List<String> container = new ArrayList<String>();
    private BitSet startBitSet; // 比特集合起始状态
    private BitSet endBitSet; // 比特集合终止状态,用来控制循环   public SplitterThread(String string) {
     this.charArray = string.toCharArray();
     this.len = string.length();
     this.startBitSet = new BitSet(len);
     this.endBitSet = new BitSet(len);
    
     // 初始化startBitSet,左侧占满*符号
     for (int i = 0; i < starCount; i++) {
      this.startBitSet.set(i, true);
     }
     // 初始化endBit,右侧占满*符号
     for (int i = this.len - 1; i > this.len - starCount - 1; i--) {
      this.endBitSet.set(i, true);
     }
     // 根据起始startBitSet,构造带*的组合字符串并加入容器
     char[] charArrayClone = this.charArray.clone();
     for (int i = 0; i < len - 1; i++) {
      if (this.startBitSet.get(i)) {
       charArrayClone[i] = '*';
      }
     }
     this.container.add(new String(charArrayClone));
    }   public void run() {
     this.split();
     synchronized(filteredContainer) {
      filteredContainer.addAll(this.container);
     }
    }   public void split() {
     while (!this.startBitSet.equals(this.endBitSet)) {
      int zeroCount = 0; // 统计遇到10后,左边0的个数
      int oneCount = 0; // 统计遇到10后,左边1的个数
      int pos = 0; // 记录当前遇到10的索引位置
      char[] charArrayClone = this.charArray.clone();
     
      // 遍历startBitSet来确定10出现的位置
      for (int i = 0; i < len - 1; i++) {
       if (!this.startBitSet.get(i)) {
        zeroCount++;
       }
       if (this.startBitSet.get(i) && !this.startBitSet.get(i + 1)) {
        pos = i;
        oneCount = i - zeroCount;
        this.startBitSet.set(i, false);
        this.startBitSet.set(i + 1, true);
        break;
       }
      }
      // 将遇到10后,左侧的1全部移动到最左侧
      int count = Math.min(zeroCount, oneCount);
      int startIndex = 0;
      int endIndex = pos - 1;
      for (int i = 0; i < count; i++) {
       this.startBitSet.set(startIndex, true);
       this.startBitSet.set(endIndex, false);
       startIndex++;
       endIndex--;
      }
      // 将遇到1的位置用*替换
      for (int i = 0; i < len; i++) {
       if (this.startBitSet.get(i)) {
        charArrayClone[i] = '*';
       }
      }
      this.container.add(new String(charArrayClone));
     }
    }
 }
 }测试用例如下所示:
package org.shirdrn;
import java.util.ArrayList;
 import java.util.Collection;import junit.framework.TestCase;
public class TestSplitter extends TestCase {
private Splitter splitter;

 public void setSplitter(Collection<String> container, int starCount, boolean duplicate) {
    this.splitter = new Splitter(container, starCount, duplicate);
 }

 // 5取3组合
 public void testSplitter1() {
    Collection<String> container = new ArrayList<String>();
    container.add("12345");
    int starCount = 3;
    boolean duplicate = false;
    this.setSplitter(container, starCount, duplicate);
    System.out.println(this.splitter.getFilteredContainer());
    assertEquals(10, this.splitter.getFilteredContainer().size());
 }

 // 9取4组合
 public void testSplitter2() {
    Collection<String> container = new ArrayList<String>();
    container.add("333301110");
    container.add("310131300");
    container.add("301100111");
    int starCount = 4;
    boolean duplicate = false;
    this.setSplitter(container, starCount, duplicate);
    System.out.println(this.splitter.getFilteredContainer());
    assertEquals(126*3, this.splitter.getFilteredContainer().size());
 }
 }结果如下所示:
[***45, **3*5, *2**5, 1***5, **34*, *2*4*, 1**4*, *23**, 1*3**, 12***]
 [****00111, ***1*0111, **1**0111, *0***0111, 3****0111, ***10*111, **1*0*111, *0**0*111, 3***0*111, **11**111, *0*1**111, 3**1**111, *01***111, 3*1***111, 30****111, ***100*11, **1*00*11, *0**00*11, 3***00*11, **11*0*11, *0*1*0*11, 3**1*0*11, *01**0*11, 3*1**0*11, 30***0*11, **110**11, *0*10**11, 3**10**11, *01*0**11, 3*1*0**11, 30**0**11, *011***11, 3*11***11, 30*1***11, 301****11, ***1001*1, **1*001*1, *0**001*1, 3***001*1, **11*01*1, *0*1*01*1, 3**1*01*1, *01**01*1, 3*1**01*1, 30***01*1, **110*1*1, *0*10*1*1, 3**10*1*1, *01*0*1*1, 3*1*0*1*1, 30**0*1*1, *011**1*1, 3*11**1*1, 30*1**1*1, 301***1*1, **1100**1, *0*100**1, 3**100**1, *01*00**1, 3*1*00**1, 30**00**1, *011*0**1, 3*11*0**1, 30*1*0**1, 301**0**1, *0110***1, 3*110***1, 30*10***1, 301*0***1, 3011****1, ***10011*, **1*0011*, *0**0011*, 3***0011*, **11*011*, *0*1*011*, 3**1*011*, *01**011*, 3*1**011*, 30***011*, **110*11*, *0*10*11*, 3**10*11*, *01*0*11*, 3*1*0*11*, 30**0*11*, *011**11*, 3*11**11*, 30*1**11*, 301***11*, **1100*1*, *0*100*1*, 3**100*1*, *01*00*1*, 3*1*00*1*, 30**00*1*, *011*0*1*, 3*11*0*1*, 30*1*0*1*, 301**0*1*, *0110**1*, 3*110**1*, 30*10**1*, 301*0**1*, 3011***1*, **11001**, *0*1001**, 3**1001**, *01*001**, 3*1*001**, 30**001**, *011*01**, 3*11*01**, 30*1*01**, 301**01**, *0110*1**, 3*110*1**, 30*10*1**, 301*0*1**, 3011**1**, *01100***, 3*1100***, 30*100***, 301*00***, 3011*0***, 30110****, ****01110, ***3*1110, **3**1110, *3***1110, 3****1110, ***30*110, **3*0*110, *3**0*110, 3***0*110, **33**110, *3*3**110, 3**3**110, *33***110, 3*3***110, 33****110, ***301*10, **3*01*10, *3**01*10, 3***01*10, **33*1*10, *3*3*1*10, 3**3*1*10, *33**1*10, 3*3**1*10, 33***1*10, **330**10, *3*30**10, 3**30**10, *33*0**10, 3*3*0**10, 33**0**10, *333***10, 3*33***10, 33*3***10, 333****10, ***3011*0, **3*011*0, *3**011*0, 3***011*0, **33*11*0, *3*3*11*0, 3**3*11*0, *33**11*0, 3*3**11*0, 33***11*0, **330*1*0, *3*30*1*0, 3**30*1*0, *33*0*1*0, 3*3*0*1*0, 33**0*1*0, *333**1*0, 3*33**1*0, 33*3**1*0, 333***1*0, **3301**0, *3*301**0, 3**301**0, *33*01**0, 3*3*01**0, 33**01**0, *333*1**0, 3*33*1**0, 33*3*1**0, 333**1**0, *3330***0, 3*330***0, 33*30***0, 333*0***0, 3333****0, ***30111*, **3*0111*, *3**0111*, 3***0111*, **33*111*, *3*3*111*, 3**3*111*, *33**111*, 3*3**111*, 33***111*, **330*11*, *3*30*11*, 3**30*11*, *33*0*11*, 3*3*0*11*, 33**0*11*, *333**11*, 3*33**11*, 33*3**11*, 333***11*, **3301*1*, *3*301*1*, 3**301*1*, *33*01*1*, 3*3*01*1*, 33**01*1*, *333*1*1*, 3*33*1*1*, 33*3*1*1*, 333**1*1*, *3330**1*, 3*330**1*, 33*30**1*, 333*0**1*, 3333***1*, **33011**, *3*3011**, 3**3011**, *33*011**, 3*3*011**, 33**011**, *333*11**, 3*33*11**, 33*3*11**, 333**11**, *3330*1**, 3*330*1**, 33*30*1**, 333*0*1**, 3333**1**, *33301***, 3*3301***, 33*301***, 333*01***, 3333*1***, 33330****, ****31300, ***1*1300, **0**1300, *1***1300, 3****1300, ***13*300, **0*3*300, *1**3*300, 3***3*300, **01**300, *1*1**300, 3**1**300, *10***300, 3*0***300, 31****300, ***131*00, **0*31*00, *1**31*00, 3***31*00, **01*1*00, *1*1*1*00, 3**1*1*00, *10**1*00, 3*0**1*00, 31***1*00, **013**00, *1*13**00, 3**13**00, *10*3**00, 3*0*3**00, 31**3**00, *101***00, 3*01***00, 31*1***00, 310****00, ***1313*0, **0*313*0, *1**313*0, 3***313*0, **01*13*0, *1*1*13*0, 3**1*13*0, *10**13*0, 3*0**13*0, 31***13*0, **013*3*0, *1*13*3*0, 3**13*3*0, *10*3*3*0, 3*0*3*3*0, 31**3*3*0, *101**3*0, 3*01**3*0, 31*1**3*0, 310***3*0, **0131**0, *1*131**0, 3**131**0, *10*31**0, 3*0*31**0, 31**31**0, *101*1**0, 3*01*1**0, 31*1*1**0, 310**1**0, *1013***0, 3*013***0, 31*13***0, 310*3***0, 3101****0, ***13130*, **0*3130*, *1**3130*, 3***3130*, **01*130*, *1*1*130*, 3**1*130*, *10**130*, 3*0**130*, 31***130*, **013*30*, *1*13*30*, 3**13*30*, *10*3*30*, 3*0*3*30*, 31**3*30*, *101**30*, 3*01**30*, 31*1**30*, 310***30*, **0131*0*, *1*131*0*, 3**131*0*, *10*31*0*, 3*0*31*0*, 31**31*0*, *101*1*0*, 3*01*1*0*, 31*1*1*0*, 310**1*0*, *1013**0*, 3*013**0*, 31*13**0*, 310*3**0*, 3101***0*, **01313**, *1*1313**, 3**1313**, *10*313**, 3*0*313**, 31**313**, *101*13**, 3*01*13**, 31*1*13**, 310**13**, *1013*3**, 3*013*3**, 31*13*3**, 310*3*3**, 3101**3**, *10131***, 3*0131***, 31*131***, 310*31***, 3101*1***, 31013****]