Scala中的组合:如何迭代/枚举合并多个序列/列表的所有可能性(riffle shuffle permutations)

时间:2014-06-18 14:01:05

标签: scala recursion combinations permutation combinatorics

更新了问题:

在我原来的问题中,我不知道如何参考以下问题。为了澄清我的问题,我添加了following illustration from Wikipedia

Riffle Shuffle

事实证明,这个问题也是以这个类比命名的:Riffle shuffle permutations。根据这个术语,我的问题就变成:如何在多层甲板的一般情况下迭代/枚举所有的riffle shuffle排列?

原始问题:

假设我们给出了多个序列,我们希望将这些序列合并为一个单一的序列。生成的序列应保留原始序列的顺序。考虑通过从任意(随机)堆栈中随机抽取卡片将多个堆栈卡(例如Seq[Seq[T]])合并到一个堆栈(Seq[T])中。所有输入堆栈应完全合并到生成的堆栈中。如何迭代或枚举这样一个结果序列的所有可能成分?

澄清一下:如果我有三个堆栈A,B,C(每个都有5个元素),那么我不仅希望这些堆栈的六种可能的排列方式如“所有A,所有B,全部C”和“所有的A,所有的C,所有的B”等。我宁愿想要所有可能的组合,如“1. A,1。B,2。A,1。C,3。A,2 B,......“。

由于我今天有点天气,我的第一种方法非常难看,也会产生重复:

def enumerateCompositions[T](stacks: Seq[Seq[T]], prefix: Seq[T]): Seq[Seq[T]] = {
  if (stacks.length == 0) return {
    Seq(prefix)
  }
  stacks.zipWithIndex.flatMap{ case (stack, stackInd) =>
    if (stack.length > 0) {
      val stacksWithHeadRemoved = stacks.indices.map{ i =>
        if (i != stackInd) stacks(i) else stacks(i).drop(1)
      }
      enumerateCompositions(stacksWithHeadRemoved, prefix :+ stack.head)
    } else {
      val remainingStacks = stacks.indices.filterNot(_ == stackInd).map(i => stacks(i))
      enumerateCompositions(remainingStacks, prefix)
    }
  }
}

任何想法如何让这更优雅,摆脱重复?

2 个答案:

答案 0 :(得分:2)

让我们把这个操作称为“反叛”。这是一个干净的自我解决方案:

def allRiffles[T](stack1: List[T], stack2: List[T]): List[List[T]] =
  (stack1, stack2) match {
    case (x :: xs, y :: ys) => {
      allRiffles(xs, stack2).map(x :: _) ++
      allRiffles(stack1, ys).map(y :: _)
    }
    case _ => List(stack1 ++ stack2) // at least one is empty
  }

def allRifflesSeq[T](stacks: Seq[List[T]]): List[List[T]] =
  stacks.foldLeft(List(List[T]())) { (z, x) => 
    z.flatMap(y => allRiffles(y, x))
  }

allRiffles将生成两个堆栈的所有可能的riffling。 allRifflesSeq将采用一系列堆栈并使用折叠产生所有可能的riffling。例如,如果{(1}}给出了堆栈A,B和C,它首先产生A和B的所有可能的riffling,然后将C反复进入每个riffling。

请注意,allRifflesSeq消耗与最短堆栈长度成比例的堆栈空间,allRiffles消耗由最长堆栈长度限制的堆栈空间。此外,返回的列表可能是巨大的(组合爆炸)并消耗大量的堆空间。基于allRifflesSeq的解决方案更安全,但不那么漂亮:

Iterator

答案 1 :(得分:1)

我用Java写的。代码如下。

import java.util.*;
import java.io.*;

public class RiffleShufflePermutation {

    protected static ArrayList<String> a1 = null;
    protected static ArrayList<String> a2 = null;
    protected static ArrayList<ArrayList<String>> a = new <ArrayList<ArrayList<String>>();

    private static int getStartingPosition(ArrayList<String> inA, String inS) {
        for(String s : inA)
            if (s.equals(inS))
                return inA.indexOf(s)+1;
        return 0;
    }

    private static void shootRiffle(int previous, int current) {
        ArrayList<ArrayList<String>> newAA = new ArrayList<ArrayList<String>>();
        ArrayList<String> newA;
        int start;
        for(ArrayList<String> al : a) {
            start = (previous < 0)?0:getStartingPosition(al,a2.get(previous));
            for(int i=start; i<=al.size(); i++) {
                newA = new ArrayList<String>();
                newA.addAll(al);
                newA.add(i,a2.get(current));
                newAA.add(newA);
            }
        }
        a.clear();
        a.addAll(newAA);
    }

    public static void main(String[] args) {
        a1 =  new ArrayList(Arrays.asList("a1","a2","a3"));
        a2 =  new ArrayList(Arrays.asList("b1","b2"));
        a.add(a1);
        for (int i=0; i<a2.size(); i++)
            shootRiffle(i-1,i);
        int i = 0;
        for (ArrayList<String> s : a)
            System.out.println(String.format("%2d",++i)+":"+s);
    }

}

这是输出:

 1:[b1, b2, a1, a2, a3]
 2:[b1, a1, b2, a2, a3]
 3:[b1, a1, a2, b2, a3]
 4:[b1, a1, a2, a3, b2]
 5:[a1, b1, b2, a2, a3]
 6:[a1, b1, a2, b2, a3]
 7:[a1, b1, a2, a3, b2]
 8:[a1, a2, b1, b2, a3]
 9:[a1, a2, b1, a3, b2]
10:[a1, a2, a3, b1, b2]

希望这很有用。

enter image description here