【问题标题】:Shuffling an ArrayList until conditions are fulfilled (Java)改组 ArrayList 直到满足条件(Java)
【发布时间】:2014-01-23 19:30:15
【问题描述】:

首先,我的母语不是英语,所以请原谅我的一些错误和错误。

我想改组一个 ArrayList(那里没问题),但是改组后列表必须满足某些条件。 我的第一种方法是创建 if 语句并在每次它们为真时随机播放。但是条件太多了,不知道怎么正确链接。

示例: 我有一个包含这些整数的 ArrayList:

0, 1, 2, 3, 4, 5, 6

洗牌后的条件:

  • 偶数不能跟在偶数后面,除非是 6。
  • 6 可以放在任何地方。
  • 还有例如0 后面不能跟 1,所以下一个可能的数字是 为 3、5 或 6。(
  • 同样适用于例如1. 1 后面只能跟 0、4 或 6。

ArrayList 中的每个元素只应列出一次,这就是为什么我认为 shuffle 是创建新 List 的最简单方法。

我想错了吗?有没有更简单的方法?我对编程很陌生... 提前感谢您的任何答案或建议。

编辑:这是所有条件:

  • 列表必须以偶数开头(最好不要以 6 开头,但这并不重要)
  • 一个偶数后面不能跟另一个偶数
  • 一个数字后面不能跟下一个最接近的数字(1 后面不能跟 2,只能是 0、4 或 6)
  • 如前所述:6 可以放在任何地方
  • 改组后的列表可能是这样的:0、3、6、5、2、1、4

嗯,我想就是这样。

如果我想创建多个 if 语句,我的主要问题是找出有效链接它们的正确方法(以便考虑每个 if 语句)。

【问题讨论】:

  • 您的方法是错误的 - 通过随机打乱列表可能需要大量重试才能达到所需状态。您应该提出一种算法来强制进行所需的随机播放,例如,通过分别随机播放奇数和偶数,然后合并它们。
  • @Alex :我试着只陈述几个 if 语句,例如: if (rng.get(i).equals(0) && rng.get(i+1).equals(1)){ Collections.shuffle(rng);}
  • 我猜你可以暴力破解所有组合,并在违反约束时修剪搜索树,这行得通吗?否则这个问题更适合说functional-programming。您能否明确列出您的所有条件?顺便说一句,你的英语真的很好。

标签: java arraylist shuffle multiple-conditions


【解决方案1】:

洗牌方法不好,有两个原因:

  1. 可能没有解决方案,因此您的程序将随机播放直到所有时间结束。
  2. 随着元素数量的变化,这将变得相当耗时。

我建议另一种解决方案:

  • 我们得到一组数字(可以用列表表示)
  • 我们需要一个布尔函数canFollow,如果给定的数字可以扩展我们目前的结果列表,则返回true。 (您给出的规则将允许使用两个数字的更简单的函数,但对于更复杂的条件,例如 5 can be followed by 8 only when not preceded by 6,更通用的函数会起作用。)
  • 一旦你有了这个,你就可以构建一个解决方案:从一个空列表开始。当给定的集合不为空时,从中取出一个元素,并检查它是否可以跟随结果。如果是这样,重复直到给定的集合为空,在这种情况下你有一个解决方案。否则,请尝试下一个数字:

在伪代码中:

List<Int> brute(List<Int> result, List <Int> given) {
    if (given is empty) return result;
    foreach i in given
        if i can follow result
            r = brute(result + i, given - i)
            if r != null return r
    return null
}
solution = brute([], [1,2,3,4,5,6])

(请注意,result + i 是 result with i appended and given - i 是在没有 i 的情况下给出的,但请确保在不破坏原始结果和给定的情况下构造它。)

如果您需要所有解决方案,可以通过将有效结果添加到以空开头的列表中来轻松更改。

【讨论】:

    【解决方案2】:

    假设您要求所有可能的结果都具有相同的可能性,那么唯一简单的方法就是暴力创建所有组合,然后从中随机选择。所有组合都是7! == 7*6*5*4*3*2*1 == 5040 组合,实际上并不多。不过,对于更大的数字,我不推荐这种方法。

    List<int[]> valid = new ArrayList<>(5040);
    
    recursiveBuild(valid, new int[] {}, new int[] { 0,1,2,3,4,5,6));
    

    recursiveBuild 在哪里:

    void recursiveBuild(List<int[]> valid, int[] soFar, int[] remaining) {
    
        if (remaining.length == 0) {
           // Check the whole thing is valid - can maybe skip this check
           // if the character-by-character check covers everything
           if (isValid(soFar)) {
              valid.add(soFar);
           }
        } else {
           for (int i=0;i<remaining.length;i++) {
               int[] newSoFar = new int[soFar.length+1];
               for (int j=0;j<soFar.length;j++) {
                   newSoFar[j]=soFar[j];
               }
               newSoFar[newSoFar.length-1]=remaining[i];
    
               int[] newRemaining = new int[remaining.length-1];
               for (int j=0;j<newRemaining.length;j++) {
                   if (j>=i) {
                       newRemaining = remaining[j+1];
                   } else {
                       newRemaining = remaining[j];
                   }
               }
    
               // Only continue if the new character added is valid
               if (isValid(newSoFar, newSoFar.length-1)
                   recursiveBuild(valid, newSoFar, newReamining);
           }
        }
    }
    

    为了解决您列出的实际问题,我将使用策略模式的变体,将每个规则定义为自己的对象(在 Java 8 中,闭包会减少冗长):

    interface CheckCondition {
        boolean passesCondition(int index, int[] arr);
    }
    
    
    CheckCondition[] conditions = new CheckCondition[] {
        new CheckCondition() {
             @override
             public boolean passesCondition(int index, int[] arr) {
                 // The list has to start with an even number
                 return index!=0 || arr[index]%2==0;
             }
        },
        new CheckCondition() {
             @override
             public boolean passesCondition(int index, int[] arr) {
                 // an even number can't follow an even number, unless it's 6.
                 return index==0 || arr[index]==6 || arr[index]%2==1 || arr[index-1]%2==1;
             }
        },
        new CheckCondition() {
             @override
             public boolean passesCondition(int index, int[] arr) {
                 // a number can't be followed by the next closest one unless its 6
                 return index==0 || arr[index]!=arr[index-1]-1 || arr[index]==6;
             }
        },
    };
    

    现在使用这些规则来检查有效性:

    boolean isValid(int[] arr, int index) {
       for (CheckCondition c: conditions)
          if (!c.passesCondition(arr.length-1, arr)
              return false;
       return true;
    }
    
    boolean isValid(int[] arr) {
       for (int i=0;i<arr.length;i++) {
           if (!isValid(arr, i);
               return false;
       }
       return true;
    }
    

    【讨论】:

    • 注意我什至没有通过语法检查器运行这段代码,所以它几乎肯定需要一些戳/测试/等:)
    • 注意!这是一个好主意!谢谢 :) 我会努力实现它。
    • 我刚刚用你更新的标准列表更新了它
    • @TimB 因为元素越多,排列的数量就会非常高,最好在添加数字之前或之后立即检查它是否有效。顺便说一句,您的 recursiveBuild 方法不是递归的,想知道它是否有效。
    • 你是对的,我错过了实际的递归调用。现在添加它。另一方面,我同意在每次通过时检查有效性会更有效,我也会添加这一点。
    猜你喜欢
    • 1970-01-01
    • 2019-11-10
    • 2014-10-18
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    相关资源
    最近更新 更多