如何按从最少尝试到最多尝试的顺序获取一组 bool 组合?

How can I get an array of bool combinations in order from least trues to most trues?

我正在尝试创建一个布尔数组数组。我想要 bool 数组的所有组合,{false, false, false, false} 除外。我想要这个数组的顺序来保存它的子数组,这样它就按照从最不真实到最真实的顺序上升。 (倒序也可以,但还是得按顺序来。)

数组的每个子集都应随机排列。

我可以这样硬编码:

private bool[][] GetBoolArrays()
{
    var fourDoorList = new List<bool[]>();
    fourDoorList.Add(new bool[4] { true, true, true, true });
    fourDoorList = fourDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var threeDoorList = new List<bool[]>();
    threeDoorList.Add(new bool[4] { true, true, true, false });
    threeDoorList.Add(new bool[4] { true, true, false, true });
    threeDoorList.Add(new bool[4] { true, false, true, true });
    threeDoorList.Add(new bool[4] { false, true, true, true });
    threeDoorList = threeDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var twoDoorList = new List<bool[]>();
    twoDoorList.Add(new bool[4] { true, true, false, false });
    twoDoorList.Add(new bool[4] { true, false, true, false });
    twoDoorList.Add(new bool[4] { true, false, false, true });
    twoDoorList.Add(new bool[4] { false, true, true, false });
    twoDoorList.Add(new bool[4] { false, true, false, true });
    twoDoorList.Add(new bool[4] { false, false, true, true });
    twoDoorList = twoDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var oneDoorList = new List<bool[]>();
    oneDoorList.Add(new bool[4] { true, false, false, false });
    oneDoorList.Add(new bool[4] { false, true, false, false });
    oneDoorList.Add(new bool[4] { false, false, true, false });
    oneDoorList.Add(new bool[4] { false, false, false, true });
    oneDoorList = oneDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorList);
    boolArrayList.AddRange(threeDoorList);
    boolArrayList.AddRange(twoDoorList);
    boolArrayList.AddRange(oneDoorList);
    return boolArrayList.ToArray();
}

但是那太脏了!

我可以创建这样的列表,但这些列表按照我想要的方式无序排列:

private bool[][] GetBoolArrays()
{
    const int subArraySize = 4;
    bool[][] combinations = new bool[(int)Mathf.Pow(2, subArraySize) - 1][];
    for (int i = 1; i < Mathf.Pow(2, subArraySize); i++)
    {
        string binary = System.Convert.ToString(i, 2);
        while (binary.Length < subArraySize)
        {
            binary = 0 + binary;
        }
        bool[] singleCombination = binary.Select(c => c == '1').ToArray();
        combinations[i - 1] = singleCombination;
    }
    return combinations;
}

所以澄清一下,我正在尝试创建一个数组数组。每个子数组有 4 个布尔值。主数组具有子数组的所有组合,除了全部为 false。子数组应按 true 的数量排序,但每个具有一定数量 true 的部分应随机化。

如果这对我所追求的内容的解释不佳,我深表歉意……这有点难以解释。我可以澄清任何需要的东西。关于如何清理此硬编码版本的任何想法?

我假设您找到了一种方法来创建一个包含您需要的所有组合的数组。我们称它为 allCombinations.

您可以使用以下命令创建有序数组:

bool[][] orderedCombinations = allCombinations.OrderBy(combination => combination.Count(b => b)).ToArray();

这按包含的 true 个值的数量对组合进行排序。具有相同数量 true 的组合未排序(但也未明确 randomized)。

希望这对您有所帮助。


UPDATE 要随机化具有相同数量 true 的组合,您可以试试这个:

Random rand = new Random();
bool[][] orderedCombinations = allCombinations.
    OrderBy(combination => combination.Count(b => b)).
    ThenBy(combination => rand.Next()).
    ToArray();

让我们进行一系列小的重构。我们开始于:

private bool[][] GetBoolArrays()
{
    var fourDoorList = new List<bool[]>();
    fourDoorList.Add(new bool[4] { true, true, true, true });
    fourDoorList = fourDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var threeDoorList = new List<bool[]>();
    threeDoorList.Add(new bool[4] { true, true, true, false });
    threeDoorList.Add(new bool[4] { true, true, false, true });
    threeDoorList.Add(new bool[4] { true, false, true, true });
    threeDoorList.Add(new bool[4] { false, true, true, true });
    threeDoorList = threeDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var twoDoorList = new List<bool[]>();
    twoDoorList.Add(new bool[4] { true, true, false, false });
    twoDoorList.Add(new bool[4] { true, false, true, false });
    twoDoorList.Add(new bool[4] { true, false, false, true });
    twoDoorList.Add(new bool[4] { false, true, true, false });
    twoDoorList.Add(new bool[4] { false, true, false, true });
    twoDoorList.Add(new bool[4] { false, false, true, true });
    twoDoorList = twoDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var oneDoorList = new List<bool[]>();
    oneDoorList.Add(new bool[4] { true, false, false, false });
    oneDoorList.Add(new bool[4] { false, true, false, false });
    oneDoorList.Add(new bool[4] { false, false, true, false });
    oneDoorList.Add(new bool[4] { false, false, false, true });
    oneDoorList = oneDoorList.OrderBy(c => Random.Range(float.MinValue, float.MaxValue)).ToList();
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorList);
    boolArrayList.AddRange(threeDoorList);
    boolArrayList.AddRange(twoDoorList);
    boolArrayList.AddRange(oneDoorList);
    return boolArrayList.ToArray();
}

我们首先注意到随机播放代码是重复的。将其提取到辅助扩展。另外,为什么我们需要把它变成一个列表?我们只是稍后将其传递给 AddRange。保持顺序。

static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
{
  return items.OrderBy(c => Random.Range(float.MinValue, float.MaxValue));
}

此外,我们现在有一个打乱的序列和一个未打乱的列表。让它们保持独立的变量。

此外,我们注意到对只有一项内容的列表进行洗牌是没有意义的!

好的,现在我们得到了什么?

private bool[][] GetBoolArrays()
{
    var fourDoorList = new List<bool[]>();
    fourDoorList.Add(new bool[4] { true, true, true, true });
    var fourDoorListShuffle = fourDoorList; // No point shuffling!
    var threeDoorList = new List<bool[]>();
    threeDoorList.Add(new bool[4] { true, true, true, false });
    threeDoorList.Add(new bool[4] { true, true, false, true });
    threeDoorList.Add(new bool[4] { true, false, true, true });
    threeDoorList.Add(new bool[4] { false, true, true, true });
    var threeDoorListShuffle = threeDoorList.Shuffle();
    var twoDoorList = new List<bool[]>();
    twoDoorList.Add(new bool[4] { true, true, false, false });
    twoDoorList.Add(new bool[4] { true, false, true, false });
    twoDoorList.Add(new bool[4] { true, false, false, true });
    twoDoorList.Add(new bool[4] { false, true, true, false });
    twoDoorList.Add(new bool[4] { false, true, false, true });
    twoDoorList.Add(new bool[4] { false, false, true, true });
    var twoDoorListShuffle = twoDoorList.Shuffle();
    var oneDoorList = new List<bool[]>();
    oneDoorList.Add(new bool[4] { true, false, false, false });
    oneDoorList.Add(new bool[4] { false, true, false, false });
    oneDoorList.Add(new bool[4] { false, false, true, false });
    oneDoorList.Add(new bool[4] { false, false, false, true });
    var oneDoorListShuffle = oneDoorList.Shuffle();
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorListShuffle);
    boolArrayList.AddRange(threeDoorListShuffle);
    boolArrayList.AddRange(twoDoorListShuffle);
    boolArrayList.AddRange(oneDoorListShuffle);
    return boolArrayList.ToArray();
}

我们还注意到什么?我们说 "new bool[4]" 但编译器可以推断出类型和数字。

private bool[][] GetBoolArrays()
{
    var fourDoorList = new List<bool[]>();
    fourDoorList.Add(new[] { true, true, true, true });
    var fourDoorListShuffle = fourDoorList; // No point shuffling!
    var threeDoorList = new List<bool[]>();
    threeDoorList.Add(new[] { true, true, true, false });
    threeDoorList.Add(new[] { true, true, false, true });
    threeDoorList.Add(new[] { true, false, true, true });
    threeDoorList.Add(new[] { false, true, true, true });
    var threeDoorListShuffle = threeDoorList.Shuffle();
    var twoDoorList = new List<bool[]>();
    twoDoorList.Add(new[] { true, true, false, false });
    twoDoorList.Add(new[] { true, false, true, false });
    twoDoorList.Add(new[] { true, false, false, true });
    twoDoorList.Add(new[] { false, true, true, false });
    twoDoorList.Add(new[] { false, true, false, true });
    twoDoorList.Add(new[] { false, false, true, true });
    var twoDoorListShuffle = twoDoorList.Shuffle();
    var oneDoorList = new List<bool[]>();
    oneDoorList.Add(new[] { true, false, false, false });
    oneDoorList.Add(new[] { false, true, false, false });
    oneDoorList.Add(new[] { false, false, true, false });
    oneDoorList.Add(new[] { false, false, false, true });
    var oneDoorListShuffle = oneDoorList.Shuffle();
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorListShuffle);
    boolArrayList.AddRange(threeDoorListShuffle);
    boolArrayList.AddRange(twoDoorListShuffle);
    boolArrayList.AddRange(oneDoorListShuffle);
    return boolArrayList.ToArray();
}

更好。如果我们使用集合初始值设定项而不是所有这些对 Add 的调用会怎么样?

private bool[][] GetBoolArrays()
{
    var fourDoorList = new List<bool[]>() {
      new[] { true, true, true, true }};
    var fourDoorListShuffle = fourDoorList; // No point shuffling!
    var threeDoorList = new List<bool[]>() {
      new[] { true, true, true, false },
      new[] { true, true, false, true },
      new[] { true, false, true, true },
      new[] { false, true, true, true }};
    var threeDoorListShuffle = threeDoorList.Shuffle();
    var twoDoorList = new List<bool[]>() {
      new[] { true, true, false, false },
      new[] { true, false, true, false },
      new[] { true, false, false, true },
      new[] { false, true, true, false },
      new[] { false, true, false, true },
      new[] { false, false, true, true }};
    var twoDoorListShuffle = twoDoorList.Shuffle();
    var oneDoorList = new List<bool[]>() {
      new[] { true, false, false, false },
      new[] { false, true, false, false },
      new[] { false, false, true, false },
      new[] { false, false, false, true }};
    var oneDoorListShuffle = oneDoorList.Shuffle();
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorListShuffle);
    boolArrayList.AddRange(threeDoorListShuffle);
    boolArrayList.AddRange(twoDoorListShuffle);
    boolArrayList.AddRange(oneDoorListShuffle);
    return boolArrayList.ToArray();
}

更好。我们需要解释变量做什么?

private bool[][] GetBoolArrays()
{
    var fourDoorList = new List<bool[]>() {
      new[] { true, true, true, true }};
    var threeDoorList = new List<bool[]>() {
      new[] { true, true, true, false },
      new[] { true, true, false, true },
      new[] { true, false, true, true },
      new[] { false, true, true, true }};
    var twoDoorList = new List<bool[]>() {
      new[] { true, true, false, false },
      new[] { true, false, true, false },
      new[] { true, false, false, true },
      new[] { false, true, true, false },
      new[] { false, true, false, true },
      new[] { false, false, true, true }};
    var oneDoorList = new List<bool[]>() {
      new[] { true, false, false, false },
      new[] { false, true, false, false },
      new[] { false, false, true, false },
      new[] { false, false, false, true }};
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorList);
    boolArrayList.AddRange(threeDoorList.Shuffle());
    boolArrayList.AddRange(twoDoorList.Shuffle());
    boolArrayList.AddRange(oneDoorList.Shuffle());
    return boolArrayList.ToArray();
}

嗯,为什么任何都必须是列表?

private bool[][] GetBoolArrays()
{
    var fourDoorList = new[] {
      new[] { true, true, true, true }};
    var threeDoorList = new[] {
      new[] { true, true, true, false },
      new[] { true, true, false, true },
      new[] { true, false, true, true },
      new[] { false, true, true, true }};
    var twoDoorList = new[] {
      new[] { true, true, false, false },
      new[] { true, false, true, false },
      new[] { true, false, false, true },
      new[] { false, true, true, false },
      new[] { false, true, false, true },
      new[] { false, false, true, true }};
    var oneDoorList = new[] {
      new[] { true, false, false, false },
      new[] { false, true, false, false },
      new[] { false, false, true, false },
      new[] { false, false, false, true }};
    var boolArrayList = new List<bool[]>();
    boolArrayList.AddRange(fourDoorList);
    boolArrayList.AddRange(threeDoorList.Shuffle());
    boolArrayList.AddRange(twoDoorList.Shuffle());
    boolArrayList.AddRange(oneDoorList.Shuffle());
    return boolArrayList.ToArray();
}

添加范围序列与连接序列相同:

private bool[][] GetBoolArrays()
{
    var fourDoorList = new[] {
      new[] { true, true, true, true }};
    var threeDoorList = new[] {
      new[] { true, true, true, false },
      new[] { true, true, false, true },
      new[] { true, false, true, true },
      new[] { false, true, true, true }};
    var twoDoorList = new[] {
      new[] { true, true, false, false },
      new[] { true, false, true, false },
      new[] { true, false, false, true },
      new[] { false, true, true, false },
      new[] { false, true, false, true },
      new[] { false, false, true, true }};
    var oneDoorList = new[] {
      new[] { true, false, false, false },
      new[] { false, true, false, false },
      new[] { false, false, true, false },
      new[] { false, false, false, true }};
    return fourDoorList.
      Concat(threeDoorList.Shuffle()).
      Concat(twoDoorList.Shuffle()).
      Concat(oneDoorList.Shuffle()).
      ToArray();
}

这比原始代码好看多了。请注意我们如何简单地进行一系列清晰、正确的重构,使每个修订版都变得更好。

现在,你能做一个方法来获取你想要的 bool 总数和你想要的 true 数量吗?

static IEnumerable<bool[]> Combinations(int totalCount, int trueCount) 
{ 
    You implement this
}

假设我们有这样一个方法,留作练习。 (我博客上的组合数学文章可能会有所帮助。)

现在我们可以写:

private bool[][] GetBoolArrays()
{
    var fourDoorList = Combinations(4, 4);
    var threeDoorList = Combinations(4, 3);
    var twoDoorList = Combinations(4, 2);
    var oneDoorList = Combinations(4, 1);
    return fourDoorList.
      Concat(threeDoorList.Shuffle()).
      Concat(twoDoorList.Shuffle()).
      Concat(oneDoorList.Shuffle()).
      ToArray();
}

现在,你能写一个具有这个签名的方法吗:

static IEnumerable<T> MultiConcat(IEnumerable<IEnumerable<T>> sequences) 
{
   ... you implement this ...
}

如果可以,那么你可以写:

private bool[][] GetBoolArrays()
{
    var combinations = new[] {
      Combinations(4, 4).Shuffle(),
      Combinations(4, 3).Shuffle(),
      Combinations(4, 2).Shuffle(),
      Combinations(4, 1).Shuffle()};
    return combinations.MultiConcat().ToArray();
}

我认为这确实比原始代码更容易阅读。事实上,我们可以将其归结为一条语句:

private bool[][] GetBoolArrays()
{
    return new[] 
    {
      Combinations(4, 4).Shuffle(),
      Combinations(4, 3).Shuffle(),
      Combinations(4, 2).Shuffle(),
      Combinations(4, 1).Shuffle()
    }.MultiConcat().ToArray();
}

但现在我们可能 简洁了。

但我们现在不要停下来。里面有很多重复的代码!

private bool[][] GetBoolArrays()
{
  var q = from num in new[] { 4, 3, 2, 1 }
          select Combinations(4, num).Shuffle();
  return q.MultiConcat().ToArray();
}

哦等等,我们已经在 LINQ 中内置了多重连接!嘿,很抱歉让你做那个练习,但我敢打赌它塑造了性格。

private bool[][] GetBoolArrays()
{
  var q = from num in new[] { 4, 3, 2, 1 }
          select Combinations(4, num).Shuffle() into all
          from combinations in all
          from combination in combinations
          select combination;
  return q.ToArray();
}

这就是我将要做的最简洁的内容。

注意这里的教训:

  • 迭代的小变化可以导致大的结果。
  • 将操作提取到专门用于这些操作的方法
  • 使用编译器推理来减少冗余并提高可读性
  • 使用表达式描述值的代码通常比使用语句描述操作的代码更紧凑。
  • 拥抱抽象。请注意,当我们放弃将所有内容都塞入列表的愿望时,一切变得容易多了。
  • 如果您要将事物实现为列表或数组,请尽可能晚地进行。