用于确定可以从序列中移除一组值的所有可能方式的算法

时间:2016-08-12 16:58:34

标签: javascript algorithm combinations combinatorics set-theory

我正在尝试确定从序列中删除一组值的多少种不同方法,使原始序列保持顺序(稳定),并确保从原始序列中仅删除1个实例值。例如,如果我有 [1,2,1,3,1,4,4]并且我想删除[1,4,4]我得到的组合将是:

[1,2,1,3,1,4,4] \ [1,4,4] = [ [2,1,3,1], [1,2,3,1], [1,2,1,3] ]

[1,2,1,3,1,4,4] \ [1,1] = [ [2,3,1,4,4], [1,2,3,4,4], [2,1,3,4,4] ]

我编写了javascript代码,用于生成所有数组值的组合而不删除,删除部分似乎应该很容易,但是当需要多次删除多个值时,我没有看到算法。

10 个答案:

答案 0 :(得分:31)

(因为在问题的原始版本中不清楚您是否要删除子序列或无序列表,我已经更新了我的答案以解决这两种情况。)

<强> 1。按顺序删除子序列

我们得到一系列值,例如[1,5,1,3,4,2,3,2,1,3,1,2],以及要从第一个序列中删除的值的子序列,例如[1,3,2,1]。如果我们找到值的每个实例所在的位置,我们得到如下图:

all connections

找到可以从序列中删除值的所有方法,按顺序,然后意味着找到所有方法,其中底行中的待删除值可以连接到序列中的实例,而不是任何线交叉,例如:

example solution

为了避免以不会导致有效解决方案的方式删除值(例如,从删除最右边的值1开始,之后没有可以删除的值3),我们将首先删除所有值图表中的连接导致此类无效解决方案。

这将通过迭代子序列两次来完成。首先,我们从左到右执行此操作,对于每个值,我们查看其最左侧的连接,并从其右侧的值中删除任何满足或跨越该连接的连接;例如当考虑值2的最左边连接时,从右边的值1(用红色表示)的两个连接被删除,因为它们通过这个连接:

removing crossed connections ltr

在下一步中,我们从右向左,并且对于每个值,我们查看其最右边的连接,并从其左侧的值中移除任何连接,以满足或跨越该连接;例如当从右边的值1考虑最右边的连接时,从左边的值2(用红色表示)的最右边的连接被删除,因为它穿过这个连接:

removing crossed connections rtl

然后我们最终得到如下所示的简化图表。然后,通过组合子序列中每个值的每个连接实例来进行组合。递归:迭代子序列中第一个值的选项,每次递归子序列的其余部分,以便第一个值的选项与其他值的所有选项组合。

simplified graph

简化图中可能存在交叉连接,但这些连接不再存在问题。在该示例中,您将看到即使为值3选择了正确的连接,也存在与值2不相交的值的连接:

example solution in simplified graph

将此转换为代码相对简单;在代码片段下方,您将找到代码的运行,其中包含示例中的数据。

&#13;
&#13;
function removeSubSeq(seq, sub) {
    var posi = []; // list position of values in seq, then connect to positions in sub
    sub.forEach(function(elem) {posi[elem] = []});
    seq.forEach(function(elem, index) {if (posi[elem]) posi[elem].push(index)});
    var conn = sub.map(function(elem) {return posi[elem].slice()});

    for (var i = 1; i < conn.length; i++) {
        var left = conn[i - 1][0];
        while (conn[i][0] <= left) {
            conn[i].shift();                // remove crossed connection left-to-right
        }
    }
    for (var i = conn.length - 2; i >= 0; i--) {
        var right = conn[i + 1][conn[i + 1].length - 1];
        while (conn[i][conn[i].length - 1] >= right) {
            conn[i].pop();                  // remove crossed connection right-to-left
        }
    }
    var combinations = [], result = [];
    combine(0, -1, []);                     // initiate recursion to find combinations
    for (var i = 0; i < combinations.length; i++) {
        result[i] = seq.slice();            // create copies of seq and remove values
        for (var j = combinations[i].length - 1; j >= 0; j--) {
            result[i].splice(combinations[i][j], 1);
        }
    }
    return result;

    function combine(step, prev, comb) {    // generate combinations using recursion
        for (var i = 0; i < conn[step].length; i++) {
            var curr = conn[step][i];
            if (prev >= curr) continue;     // skip crossed connection
            if (step + 1 == conn.length) combinations.push(comb.concat([curr]));
            else combine(step + 1, curr, comb.concat([curr]));
        }
    }
}
var result = removeSubSeq([1,5,1,3,4,2,3,2,1,3,1,2], [1,3,2,1]);
for (var i in result) document.write(result[i] + "<br>");
&#13;
&#13;
&#13;

代码执行以下步骤:

  • 创建sub:
  • 中存在的每个值的实例位置的关联数组
posi[1] = [0,2,8,10], posi[2] = [5,7,11], posi[3] = [3,6,9]}  
  • 创建一个2D数组,将子序列中的位置与序列中的位置相关联:
conn = [[0,2,8,10],[3,6,9],[5,7,11],[0,2,8,10]]  
  • 从左到右遍历数组并删除交叉连接:
conn = [[0,2,8,10],[3,6,9],[5,7,11],[8,10]]
  • 从右到左遍历阵列并删除交叉连接:
conn = [[0,2],[3,6],[5,7],[8,10]]
  • 使用递归生成所有位置组合:
combinations = [[0,3,5,8],[0,3,5,10],[0,3,7,8],[0,3,7,10],
                [0,6,7,8],[0,6,7,10],[2,3,5,8],[2,3,5,10],
                [2,3,7,8],[2,3,7,10],[2,6,7,8],[2,6,7,10]]
  • 使用组合删除序列副本中的值(请参阅代码段输出)。

<强> 2。删除无序的值列表

如果要删除的值列表不一定是主序列的子序列,并且可以按任何顺序删除值,则可以使用与上面相同的方法,放宽交叉连接规则:

  

从位置列表中删除交叉连接,并在生成组合时避免交叉连接,只需对相同的值进行。

在此示例中,仅删除重复值1的交叉连接;先从左到右:

removing crossed connections ltr

然后从右到左:

removing crossed connections rtl

产生了这个简化的图形,删除了值1的有问题的交叉连接,剩下值2和3的交叉连接:

simplified graph

以下是从子序列的版本改编的代码示例;代码中只有几行被更改,如注释所示(我还使用了一种不同的方法来删除序列中的值)。要删除的值列表在开始时排序,以便将相同的值组合在一起,以便于跟踪相同的值。

&#13;
&#13;
function removeSubList(seq, sub) {
    sub.sort(function(a, b) {return a - b});       /* SORT SUB-LIST FIRST */
    var posi = []; // list position of values in seq, then connect to positions in sub
    sub.forEach(function(elem) {posi[elem] = []});
    seq.forEach(function(elem, index) {if (posi[elem]) posi[elem].push(index)});
    var conn = sub.map(function(elem) {return posi[elem].slice()});

    for (var i = 1; i < conn.length; i++) {
        if (sub[i - 1] != sub[i]) continue;        /* SKIP FOR NON-IDENTICAL VALUES */
        var left = conn[i - 1][0];
        while (conn[i][0] <= left) {
            conn[i].shift();                // remove crossed connection left-to-right
        }
    }
    for (var i = conn.length - 2; i >= 0; i--) {
        if (sub[i] != sub[i + 1]) continue;        /* SKIP FOR NON-IDENTICAL VALUES */
        var right = conn[i + 1][conn[i + 1].length - 1];
        while (conn[i][conn[i].length - 1] >= right) {
            conn[i].pop();                  // remove crossed connection right-to-left
        }
    }
    var combinations = [], result = [];
    combine(0, -1, []);                     // initiate recursion to find combinations
    for (var i = 0; i < combinations.length; i++) {
        var s = seq.slice();                // create copy of seq and delete values
        combinations[i].forEach(function(elem) {delete s[elem]});
        result[i] = s.filter(function(elem) {return elem != undefined});
    }
    return result;

    function combine(step, prev, comb) {    // generate combinations using recursion
        for (var i = 0; i < conn[step].length; i++) {
            var curr = conn[step][i];
            if (prev >= curr && seq[prev] == seq[curr]) continue;   /* SKIP FOR NIV */
            if (step + 1 == conn.length) combinations.push(comb.concat([curr]));
            else combine(step + 1, curr, comb.concat([curr]));
        }
    }
}
var result = removeSubList([1,5,1,3,4,2,3,2,1,3,1,2], [1,3,1,2,1]);
for (var i in result) document.write(result[i] + "<br>");
&#13;
&#13;
&#13;

答案 1 :(得分:6)

这可以通过简单的组合学来完成。

为简单起见,我们假设原始列表中的值为1,2,3,...,n
a[i]为原始列表中i的出现次数。
b[i]为值删除列表中i的出现次数

减少i的选项数量为Choose(a[i],b[i]) = a[i]!/((a[i]-b[i])!b[i]!)

由于您在“AND”闭包中组合了所有这些,因此可能性总数为:

Choose(a[1],b[1]) * Choose(a[2],b[2]) * ... * Choose(a[n], b[n])

关于不在缩减集中的值,您无需担心它们。因为b列表中的值为0,所有Choose(x,0) = 1的值为x

这为您提供线性时间解决方案(假设您可以在执行一些预处理以缓存因子值后,在恒定时间内计算选择(。,。)。

在您的示例中,您有:

a = [3, 1, 1, 2]
b = [1, 0, 0, 2]
Choose(3,1) = 3
Choose(1,0) = 1
Choose(1,0) = 1
Choose(2,2) = 1
#number_possibilities = 3 * 1 * 1 * 1 = 3

答案 2 :(得分:5)

(请参阅下面的其他两个答案,包括有序和无序的多组合组合。)

避免&#34;死胡同&#34;在递归中,从散列索引创建组合。例如,

[1,2,1,3,1,4,4] / [1,3] 

Hash = {1: [0,2,4], 3: [3]} // for repeated elements in the to-be-removed array,
                            // you could reserve the first element of the index array


Use the multi-set combination algorithm of your choice to make combinations from the 
hashed indexes, like [0,3], [2,3], etc.; and accumulate results without the corresponding elements.

答案 3 :(得分:5)

要确定一组值(让我们称这个组needles)可以从一个序列(称为haystack)中移除的所有方法,请执行以下操作:

  1. 计算必须删除每个needle的次数(让我们称之为此计数k)。这可以通过needles上的单次传递来确定。
  2. needle中找到要删除的每个haystack的所有位置。这可以通过haystack上的单次传递来确定。
  3. 生成所有可能的方法,您可以从找到的位置删除每个needle k次。这是标准的enumeration of k-combinations,其时间复杂度是非多项式的。
  4. 生成所有可能的方法,您可以将每个needle的删除可能性组合在一起。这是标准的n-fold Cartesian product,其时间复杂度也是非多项式的。
  5. 对于找到的每种方式,过滤掉haystack
  6. 中的相关元素

    以下是此方法的ECMAScript 2016实施:

    &#13;
    &#13;
    function* removalCombinations(haystack, needles) {
      // Comments walk through sample input of haystack = [1,2,1,3,1,4,4] and needles = [1,4,4]
    
      // How many of each needle there are, e.g.,
      // needleCounts = { 1 => 1, 4 => 2 }
      let needleCounts = elementCounts(needles);
    
      // Where each needle is located, e.g.,
      // needleIndexes = { 1 => [ 0, 2, 4 ], 4 => [ 5, 6 ] }
      let needleIndexes = findIndices(needleCounts.keys(), haystack.entries());
    
      // The possible indices to be removed for a particular needle, e.g.,
      // indexCombinations = [ [ [ 0 ], [ 2 ], [ 4 ] ], [ [ 5, 6 ] ] ]
      var indexCombinations = [];
      for (let [needle, indexes] of needleIndexes) {
        indexCombinations.push(Array.from(generateCombinations(indexes, needleCounts.get(needle))));
      }
    
      // All the ways that the possible index removals can be fully combined together, e.g.,
      // fullRemovalCombinations = [ [ 0, 5, 6 ], [ 2, 5, 6 ], [ 4, 5, 6 ] ]
      let fullRemovalCombinations = cartesianProductOf(indexCombinations);
    
      // For every possible index removal combination,
      // filter those indices from the original haystack, e.g.,
      // yielded values = [ [ 2, 1, 3, 1 ], [ 1, 2, 3, 1 ], [ 1, 2, 1, 3 ] ]
      for (let indicesToFilter of fullRemovalCombinations) {
        indicesToFilter = new Set(indicesToFilter);
        yield haystack.filter((_, index) => !indicesToFilter.has(index))
      }
    
      // Calculates how many there are of each element.
      function elementCounts(iterable) {
        let counts = new Map();
        for (let el of iterable) {
          counts.set(el, counts.get(el) + 1 || 1);
        }
        return counts;
      }
    
      // Finds the indices of where each target occurs within iterable.
      function findIndices(targets, entries) {
        let indices = new Map();
        for (let el of targets) {
          indices.set(el, []);
        }
        for (let [index, value] of entries) {
          if (indices.has(value)) {
            indices.get(value).push(index);
          }
        }
        return indices;
      }
    
      // Generates all possible combinations of choosing k elements from arr.
      function* generateCombinations(arr, k) {
        function* doGenerateCombinations(offset, combo) {
          if (combo.length == k) {
            yield combo;
          } else {
            let len = arr.length;
            for (let i = offset; i < len; i++) {
              yield * doGenerateCombinations(i + 1, combo.concat(arr[i]));
            }
          }
        }
    
        yield* doGenerateCombinations(0, []);
      }
    
      // Given an array of arrays, generates all ways the elements can be combined together,
      // when taking a single element from each array.
      function* cartesianProductOf(arrays) {
        function* doCartesianProductOf(i, prod) {
          if (i == arrays.length) {
            yield prod;
          } else {
            for (let j = 0; j < arrays[i].length; j++) {
              yield* doCartesianProductOf(i + 1, prod.concat(arrays[i][j]));
            }
          }
        }
    
        yield* doCartesianProductOf(0, []);
      }
    }
    
    console.log(JSON.stringify(Array.from(removalCombinations([1, 2, 1, 3, 1, 4, 4], [1, 4, 4]))));
    console.log(JSON.stringify(Array.from(removalCombinations([8, 6, 4, 4], [6, 4, 8]))));
    &#13;
    &#13;
    &#13;

答案 4 :(得分:3)

我认为分枝和修剪是解决这个问题的正统方法,并且具有很多优化可能性 但是,如果您只想要一个简单直观的解决方案。 在这里。

首先,找到删除列表中的数字 [1,2,1,3,1,4,4] [1,4,4]
由此得到[1,1,1,4,4]
其次,从第一步中选择删除列表元素,即组合5C3 由此我们得到[1,1,1] [1,1,4] [1,4,4] ....
第三,比较顺序。然后,你得到了结果。
这是代码..对不起,它是在C ++中,我使用了一个简单的组合库。

#include<vector>
#include<algorithm>
#include<iostream>
#include"combi.h"
using namespace std;

int main()
{
    vector<int> list {1,2,1,3,1,4,4};
    vector<int> to_remove {1,4,4};
    vector<int> index;
    for(int i=0; i<list.size(); i++) {
        if(find(to_remove.begin(), to_remove.end(), list[i]) != to_remove.end())
            index.push_back(i);//insert index
    }
    bool sequence;
    nCr ncr(index.size(), to_remove.size());
    while(ncr.next()) {
        sequence = true;
        for(int i=0; i<ncr.size(); i++) 
            if(list[index[ncr[i]-1]] != to_remove[i]) sequence = false;
        if(sequence) {
            for(int i=0, j=0; i<list.size(); i++) {
                if(i == index[ncr[j]-1]) j++;
                else cout << list[i] << ' ';
            }
            cout << endl;
        }
    }
}

这是组合库..

class Combination
{
public:
    Combination(int n, int r);
    virtual ~Combination() { delete [] ar;}
    int& operator[](unsigned i) {return ar[i];}
    bool next();
    int size() {return r;}

protected:
    int* ar;
    int n, r;
};

class nCr : public Combination
{
public: 
    nCr(int n, int r);
    bool next();
};

Combination::Combination(int n, int r)
{
    ar = new int[r];
    this->n = n;
    this->r = r;
}

nCr::nCr(int n, int r) : Combination(n, r)
{
    if(r == 0) return;
    for(int i=0; i<r-1; i++) ar[i] = i + 1;
    ar[r-1] = r-1;
}

bool nCr::next()
{
    if(r == 0) return false;
    ar[r-1]++;
    int i = r-1;
    while(ar[i] == n-r+2+i) {
        if(--i == -1) return false;
        ar[i]++;
    }
    while(i < r-1) ar[i+1] = ar[i++] + 1;
    return true;
}

答案 5 :(得分:2)

很好的练习,像往常一样,代码需要1个时间单位,10个键入:-)。我无法满足语言约束,因为我使用了yet to be named language,因此我可能会退出竞争对手。但我会挑战所有提供正确性检查的解决方案的人。很抱歉省略逗号。请检查这些论点:

[1 2 1 3 1 4 4] \ [1 4 4 1]

应该产生以下解决方案:

(2 3 1)(2 1 3)(1 2 3) 

[1 2 1 3 1 4 4] \ [1 4 4 1 1]

应该产生以下解决方案:

(2 3)

[1 1 1 1 1] \ [1 1 1]

应该(imho)产生以下解决方案:

(1 1)

并且

[1] \ [2]

应该(imho)产生以下解决方案:

[zero-length array]

并且

[1 2 1 1 4 4 3 8 6 4 1 1 4 3 2 1] \ [1 1 4 1 1 1 3 4 8 6 2 2 4]

应该产生以下解决方案:

(4 3 1)(3 4 1)(1 4 3)(3 1 4)(4 1 3)(1 3 4) 

<强> SOLUTION:

这不是最简单的实现方法,尽管它在逻辑上非常清晰。我正在使用术语“子阵列”,如下所示:

(1 2 3)(4 5 6)(7 8 9 10) <- Array with 3 "sub-arrays", 3 "elements"

第1步:分配参数(遵循原始示例)

arg = 1,2,1,3,1,4,4   
vec = 1,4,4   

第2步:检查vec中的唯一身份证,以及其中有多少。

A = 1,4 // The uniques in vec
B = 1,2 // Occurances of them

第3步:为每个A(1个来源)的索引构建索引:

C = (1 3 5)(6 7) // 1 appears at indexes 1,3,5 in arg, 4 appears at indexes 6,7

第4步:将C的每个元素取为B次的每个元素:

D = (1 3 5)(6 7)(6 7) // B is (1,2) so we take (1 3 5) once and (6 7) twice.

第5步 :(棘手的步骤)使用外部联接在D中创建元素的所有组合:

首先创建两个最右边元素的所有组合,即。 (6 7)和(6 7):

(6 6)(6 7)(7 6)(7 7) // (6 7) combined with (6 7) all possible ways

然后将其与下一个 D(朝)结合使用:

E = (1 6 6)(1 6 7)(1 7 6)(1 7 7)(3 6 6)(3 6 7)(3 7 6)(3 7 7)(5 6 6)(5 6 7)(5 7 6)(5 7 7) // (1 3 5) combined with (6 6)(6 7)(7 6)(7 7) all possible ways

如果D中有更多元素,我们将逐一(左侧)将它们带到目前为止与已实现的组合相结合。直到完成D的所有元素(其中“element”是“子数组”)。

第6步:从E中删除包含重复数字“内部”的此类元素(例如,元素(1 6 6)将被删除):

F = (1 6 7)(1 7 6)(3 6 7)(3 7 6)(5 6 7)(5 7 6) // What is left from E

第7步:从F中删除子数组在内部排序时,这些元素是重复的:

(1 6 7)(1 6 7)(3 6 7)(3 6 7)(5 6 7)(5 6 7) // F with sub-arrays sorted internally
G = (1 6 7)(3 6 7)(5 6 7)                  // Duplicate sub-arrays removed

第8步:几乎准备好了!我们现在所拥有的是arg中的“非索引” - 那些应被排除的索引。

arg有7个元素,因此所有索引都是(1,2,3,4,5,6,7)。

挑选上面G的第一个元素(1 6 7),意味着索引(1 2 3 4 5 6 7)没有(1 6 7)是第一个答案。所有答案/索引:

(1 2 3 4 5 6 7) without (1 6 7) -> (2 3 4 5). arg[2 3 4 5] is (2 1 3 1)
(1 2 3 4 5 6 7) without (3 6 7) -> (1 2 4 5). arg[1 2 4 5] is (1 2 3 1)
(1 2 3 4 5 6 7) without (5 6 7) -> (1 2 3 4). arg[1 2 3 4] is (1 2 1 3)

因此答案是

(2 1 3 1)(1 2 3 1)(1 2 1 3) 

第9步 :(可选)答案可能包含元素级别的重复项。只保留唯一身份。

您可以在tryapl.org

尝试使用此Dyalog APL单线程
1 2 1 3 1 4 4 {↑∪{c[b~⍵]}¨{(∊⍵≡¨∪¨⍵)/⍵},⊃∘.,/(+/¨a=¨⊂⍵)/((a←∪⍵)=¨⊂⍺)/¨⊂b←⍳⍴c←⍺} 1 4 4

粘贴并按[回车],即可获得:

2 1 3 1
1 2 3 1
1 2 1 3

您将无法测试上面最长的挑战样本,因为它超过了tryapl服务器的可用处理时间分配,但可以随意使用任何较短的参数进行测试。

答案 6 :(得分:2)

这是一个使用重复功能逐步减少值的解决方案。如果并非所有需要删除的值的值都存在于起始数组中,则此函数不会返回解决方案。

// Algorithm to strip values from an array
// Note, if not all elements of the stripValues array are found this function will return no solutions

function stripValues(startingValues, stripValues) {
    let solutions = []

    searchForSolutions(startingValues, stripValues, solutions, [])

    return solutions
}

function searchForSolutions(startingValues, stripValues, solvedSolutions, possibleSolution) {
    // If there are values to remove
    if(stripValues.length > 0) {
        // Copy the values of any possible solution to avoid tampering
        let newPossibleSolution = []
        possibleSolution.forEach((value) => {
            newPossibleSolution.push(value)
        })

        // Loop through the starting values looking for an instance of the first remaining value to strip
        for(i = 0; i < startingValues.length; i++) {
            if(startingValues[i] == stripValues[0]) {
                // The value was found, so reduce the arrays and look for the next element to remove
                let remainingStripValues = []
                stripValues.forEach((value, index) => {
                    if(index > 0) {
                        remainingStripValues.push(value)
                    }
                })

                let remainingValues = []
                for(j = i + 1; j< startingValues.length; j++) {
                    remainingValues.push(startingValues[j])
                }

                // Reiterate the search
                searchForSolutions(remainingValues, remainingStripValues, solvedSolutions, newPossibleSolution)
            }

            // Whether or not the value was found we want to continue finding permutations 
            newPossibleSolution.push(startingValues[i])
        }
    } else {
        //There are no remaining values to search for, so we have found a solution
        for(i = 0; i < startingValues.length; i++) {
            newPossibleSolution.push(startingValues[i]);
        }

        solvedSolutions.push(newPossibleSolution)
    }
}

答案 7 :(得分:1)

(这个答案也可以在这里找到:How can I determine all possible ways a subsequence can be removed from a sequence?

这是有序多组合的答案,这似乎类似于枚举较大数组中的匹配子序列。

首先,按照与主数组相同的外观顺序(#{1}}时间使用哈希)来排序您的集合,然后继续下面的算法。

此问题可以在O(n)时间内解决,其中O(n*m + r)是结果的总长度,使用经典的longest common subsequence算法。

制作完表后,就像在Wikipedia的example中一样,将其替换为带有对角线箭头的单元格列表,该箭头也具有与其行对应的值。现在从最后一行中的对角线向后遍历每个单元格,在字符串中累积相关索引并复制和分割累积,使得每个具有斜箭头的单元格将具有前一行中具有对角线的所有单元格的延续,在它的左边(存储也计算,当你构建矩阵)和一个较少的值。当累积达到零单元时,拼接索引中的累积索引并将其作为结果添加。

(箭头对应的LCS目前是否来自r,请参阅函数definition。)

例如:

LCS(X[i-1],Y[j]) and/or LCS(X[i],Y[j-1]), or LCS(X[i-1],Y[j-1])

JavaScript代码:

  0  a  g  b  a  b  c  c
0 0  0  0  0  0  0  0  0
a 0 ↖1  1  1 ↖1  1  1  1
b 0  1  1 ↖2  2 ↖2  2  2
c 0  1  1  2  2  2 ↖3 ↖3

答案 8 :(得分:1)

如果您想枚举多组元素的无序组合,您可以这样做:

记录多组中元素数组中的位置;枚举choose(indexes,multiplicity)的所有组合。

JavaScript代码:

// straighforward choose(n,r) combinations
function choose(ns,r){
  if (r > ns.length) return [];
    
  var res = [];

  function _choose(i,_res){
    if (_res.length == r){
      res.push(_res);
      return;

    } else if (_res.length + ns.length - i == r){
      _res = _res.concat(ns.slice(i));
      res.push(_res);
      return
    }

    var temp = _res.slice();
    temp.push(ns[i]);

    _choose(i + 1,temp);
    _choose(i + 1,_res);
  }

  _choose(0,[]);
  return res;
}

// function to collect an array without specified indexes
function remove(arr,indexSet){
  var _arr = [];
  arr.forEach(function(v,i){ if (!indexSet.has(i)) _arr.push(arr[i]); });
  return _arr;
}

// main function
// the multiset is formatted as {element: [multiplicity,indexes]}
function removeAllCombs(arr,multiset){
  var res = [];
  
  // record the positions of multiset elements in the array
  arr.forEach(function(v,i){
    if (multiset[v]) multiset[v][1].push(i);
  });
  
  var keys = Object.keys(multiset);
  
  function enumerate(i,accum){
    if (i == keys.length){
      res.push(remove(arr,new Set(accum)));
      return;
    }
    
    var combs = choose(multiset[keys[i]][1],multiset[keys[i]][0]);

    for (let j in combs){
      var _accum = accum.slice();
      _accum = _accum.concat(combs[j]);
      
      enumerate(i + 1,_accum);
    }
  }
  
  enumerate(0,[]);
  return res;
}

console.log(JSON.stringify(
  removeAllCombs([1,2,1,3,1,4,4],{1: [1,[]], 4: [2,[]]})
));

答案 9 :(得分:0)

这个提议基本上构建了一个包含要删除的所需项目的映射,计算它们,检查相同项目的长度是否与给定项目相同,然后将其放入common数组中。所有其他用于构建组合,然后用于构建交叉产品。

最后,过滤掉所有在交叉积或共同中找到的值。

function remove(sequence, sub) {
    var count = new Map,
        distribute = [],
        common = [];

    sub.forEach((a, i) => {
        var o = count.get(a)
        if (!o) {
            o = { sub: 0, pos: [] };
            count.set(a, o);
        }
        o.sub++;
    });

    sequence.forEach((a, i) => {
        var o = count.get(a);
        o && o.pos.push(i);
    });

    count.forEach((v, k) => {
        if (v.pos.length > v.sub) {
            distribute.push({ value: k, pos: v.pos, count: v.sub });
        } else {
            common.push(k);
        }
    });

    return crossProduct(distribute.map(a => combination(a.pos, a.count))).
        map(a =>
            sequence.filter((b, i) => a.indexOf(i) === -1 && common.indexOf(b) === -1));
}

console.log(remove([1, 2, 1, 3, 1, 4, 4], [1, 4, 4])); // [ [2,1,3,1], [1,2,3,1], [1,2,1,3] ]
console.log(remove([1, 2, 1, 3, 1, 4, 4], [1, 1]));    // [ [2,3,1,4,4], [1,2,3,4,4], [2,1,3,4,4] ]
console.log(remove([1, 2, , 5, 1, 3, 5, 1, 4, 4, 5], [1, 4, 4, 5]));

function crossProduct(array) {
    function c(part, index) {
        array[index].forEach(a => {
            var p = part.concat(a);
            if (p.length === array.length) {
                result.push(p);
                return;
            }
            c(p, index + 1);
        });
    }

    var result = [];
    if (array.length === 1) { return array[0]; }
    c([], 0);
    return result;
}

function combination(array, size) {

    function c(part, start) {
        var i, l, p;
        for (i = start, l = array.length + part.length + 1 - size; i < l; i++) {
            p = part.slice();
            p.push(array[i]);
            p.length < size ? c(p, i + 1) : result.push(p);
        }
    }

    var result = [];
    c([], 0);
    return result;
}
.as-console-wrapper { max-height: 100% !important; top: 0; }