在Java中随机化一组重复数组而不重复元素

时间:2016-01-14 21:11:10

标签: java arrays

在我的问题中,我有几个数字为1 - 3的数组,

[1,2,3],[1,2,3]

我将数组合并为一个完整的数组,

[1,2,3,1,2,3]

我需要在每次运行时随机化数组,这样就不会重复元素。

例如,这将起作用

[1,2,1,3,2,3]

但这不会。

[1,的 2,2 下,3,1,3]

我选择了1,2,3来简化它,但我的数组将由数字1 - 6组成。但这个想法仍然是相同的。是否有算法或简单的方法来实现这一目标?

5 个答案:

答案 0 :(得分:1)

这是随机改组的启发式解决方案,不允许连续重复。它适用于列表,但它很容易将其传输到数组,因为它只进行交换,不需要移位操作。对于由数百万个元素和各种密度因子组成的列表,它似乎适用于大多数情况,但始终要记住,启发式算法可能永远找不到解决方案。它使用来自遗传算法的逻辑,但该版本仅使用一个单独的选择性突变(虽然它很容易将其转换为真正的遗传算法),但它很简单并且工作如下:

如果找到重复项,请尝试使用随机元素进行交换;如果不可能,请尝试使用之前的元素进行交换(反之亦然)。这里的关键点是用于交换元素的随机位置,以便在随机输出上保持更好的均匀分布。

这个问题已经以其他形式提出,但我还没有找到可接受的解决方案。不幸的是,作为大多数提议的答案(除了贪婪"广泛的重新洗牌,直到我们得到匹配或计算每个组合),这个解决方案不提供完美的均匀分布,但似乎最小化一些模式,:(仍然无法删除每个模式,如下所示。尝试并发布任何评论以获得潜在的改进。

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

//Heuristic Non-Consecutive Duplicate (NCD) Shuffler
public class NCDShuffler {

    private static Random random = new Random();
    //private static int swaps = 0;

    public static <T> void shuffle (List<T> list) {
        if (list == null || list.size() <= 1) return;
        int MAX_RETRIES = 10; //it's heuristic
        boolean found;
        int retries = 1;
        do {
            Collections.shuffle(list);
            found = true;
            for (int i = 0; i < list.size() - 1; i++) {
                T cur = list.get(i);
                T next  = list.get(i + 1);
                if (cur.equals(next)) {

                    //choose between front and back with some probability based on the size of sublists
                    int r = random.nextInt(list.size());
                    if ( i < r) {
                        if (!swapFront(i + 1, next, list, true)) {
                            found = false;
                            break;
                        }
                    } else {
                        if (!swapBack(i + 1, next, list, true)) {
                            found = false;
                            break;
                        }
                    }
                }
            }
            retries++;
        } while (retries <= MAX_RETRIES && !found);

    }

    //try to swap it with an element in a random position after it
    private static <T> boolean swapFront(int index, T t, List<T> list, boolean first) {
        if (index == list.size() - 1) return first ? swapBack(index, t, list, false) : false;
        int n = list.size() - index - 1;
        int r = random.nextInt(n) + index + 1;
        int counter = 0;
        while (counter < n) {
            T t2 = list.get(r);
            if (!t.equals(t2)) {
                Collections.swap(list, index, r);
                //swaps++;
                return true;
            }
            r++;
            if (r == list.size()) r = index + 1;
            counter++;
        }

        //can't move it front, try back
        return first ? swapBack(index, t, list, false) : false;
    }

    //try to swap it with an element in a random "previous" position
    private static <T> boolean swapBack(int index, T t, List<T> list, boolean first) {
        if (index <= 1) return first ? swapFront(index, t, list, false) : false;
        int n = index - 1;
        int r = random.nextInt(n);
        int counter = 0;
        while (counter < n) {
            T t2 = list.get(r);
            if (!t.equals(t2) && !hasEqualNeighbours(r, t, list)) {
                Collections.swap(list, index, r);
                //swaps++;
                return true;
            }
            r++;
            if (r == index) r = 0;
            counter++;
        }
        return first ? swapFront(index, t, list, false) : false;
    }

    //check if an element t can fit in position i
    public static <T> boolean hasEqualNeighbours(int i, T t, List<T> list) {
        if (list.size() == 1) 
            return false;
        else if (i == 0) {
            if (t.equals(list.get(i + 1)))
                return true;
            return false;
        } else {
            if (t.equals(list.get(i - 1)) || (t.equals(list.get(i + 1))))
                return true;
            return false;
        }
    }

    //check if shuffled with no consecutive duplicates
    public static <T> boolean isShuffledOK(List<T> list) {
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i).equals(list.get(i - 1)))
                return false;
        }
        return true;
    }
    //count consecutive duplicates, the smaller the better; We need ZERO
    public static <T> int getFitness(List<T> list) {
        int sum = 0;
        for (int i = 1; i < list.size(); i++) {
            if (list.get(i).equals(list.get(i - 1)))
                sum++;
        }
        return sum;
    }

    //let's test it
    public static void main (String args[]) {
        HashMap<Integer, Integer> freq = new HashMap<Integer, Integer>();
        //initialise a list
        List<Integer> list = new ArrayList<Integer>();
        list.add(1);
        list.add(1);
        list.add(2);
        list.add(3);

        /*for (int i = 0; i<100000; i++) {
            list.add(random.nextInt(10));
        }*/

        //Try to put each output in the frequency Map
        //then check if it's a uniform distribution
        Integer hash;
        for (int i = 0; i < 10000; i++) {
            //shuffle it
            shuffle(list);

            hash = hash(list);
            if (freq.containsKey(hash)) {
                freq.put(hash, freq.get(hash) + 1);
            } else {
                freq.put(hash, 1);
            }
        }
        System.out.println("Unique Outputs: " + freq.size());
        System.out.println("EntrySet: " + freq.entrySet());
        //System.out.println("Swaps: " + swaps);
        //for the last shuffle
        System.out.println("Shuffled OK: " + isShuffledOK(list));
        System.out.println("Consecutive Duplicates: " + getFitness(list));
    }

    //test hash
    public static int hash (List<Integer> list) {
        int h = 0;
        for (int i = 0; (i < list.size() && i < 9); i++) {
            h += list.get(i) * (int)Math.pow(10, i); //it's reversed, but OK
        }
        return h;
    }
}

这是一个示例输出;很容易理解非均匀分布的问题。

独特输出:6

EntrySet:[1312 = 1867,3121 = 1753,2131 = 1877,1321 = 1365,1213 = 1793,1231 = 1345]

洗牌好了:是的

连续重复:0

答案 1 :(得分:0)

您可以使用Collections.shuffle随机化列表。在while循环中执行,直到列表通过约束。

答案 2 :(得分:0)

如果数组相对较小,那么仅仅将两个数组合并,随机化然后检查数字就不会太难,如果数字太多,只需将一个数移一遍或者再次随机化。< / p>

答案 3 :(得分:0)

我没有预先编写的算法(这并不代表一个不存在的算法),但问题很容易理解,而且实现很简单。

我将提供两个建议,具体取决于您是否要构建有效数组,或者是否要构建数组,然后检查其有效性。

1 - 创建一些集合(Array,ArrayList等),其中包含将包含在最终数组中的所有可能值。获取其中一个值并将其添加到数组中。将该值的副本存储在变量中。从可能的值中获取另一个值,检查它是否与之前的值不相等,如果它有效,则将其添加到数组中。

2 - 创建一个包含所需值数的数组。对于除最后一项之外的所有项目,检查项目n!=项目n + 1。如果您未通过其中一项检查,请为该位置生成新的随机值,或者从该位置的值中添加或减去某个常量。检查完此阵列中的所有值后,您就知道自己拥有一个有效的数组。假设第一个和最后一个值可以相同。

答案 4 :(得分:0)

我能想到的最佳解决方案是计算每个值的出现次数,从逻辑上为每个不同的值创建一个“池”。

然后,您可以从任何不是上一个选择值的池中随机选择一个值。随机选择按池大小加权。

如果池的大小超过所有剩余值的一半,则必须从该池中进行选择,以防止在最后重复。

通过这种方式,您可以快速生成结果而无需任何形式的重试或回溯。

示例(使用字母作为值来澄清与计数的差异):

Input: A, B, C, A, B, C

Action                              Selected     Pools(Count)
                                               A(2)  B(2)  C(2)
Random from all 3 pools                A       A(1)  B(2)  C(2)
Random from B+C pools                  C       A(1)  B(2)  C(1)
Random from A+B pools (1:2 ratio)      A       A(0)  B(2)  C(1)
Must choose B (>half)                  B       A(0)  B(1)  C(1)
Random from A+C, so C                  C       A(0)  B(1)  C(0)
Must choose B (>half)                  B       A(0)  B(0)  C(0)

Result: A, C, A, B, C, B