【问题标题】:Generating confusing list of random numbers that user must sort ASC/DESC生成用户必须对 ASC/DESC 进行排序的随机数的混乱列表
【发布时间】:2013-10-05 18:01:32
【问题描述】:

背景

我和其他一些人正在我们大学的一个项目中开发一个 android 警报应用程序。 我们有一个名为“挑战”的概念,其中用户必须完成一项才能关闭警报。这些挑战/用户故事之一是正确地对数字列表 ASC/DESC 进行排序。

问题

目标/问题是为用户提供一个提供最大混乱度的列表,以便人类尽可能难以对列表进行排序。

我的基本想法是,如果你得到一个打乱数字的列表,例如:[131, 129, 315, 328, 931, 953],那将很难排序(如果你有更好的混淆概念,请分享)。

计算性能不是我们主要关心的问题,而是列表的质量。

尝试解决

首先我立即搜索 Fisher-Yates,改组,然后继续寻找有关方差和标准差的信息。

我的一个朋友建议,如果我们说(第 1 步)从 100-999 中生成 3 个数字,然后(第 2 步)生成 3 个较小的数字(比如 1- 为每个大数字加到大数字中)我们得到了一个漂亮而令人困惑的数字列表。可能会进行一些检查以确保较大的数字变化足够大,而较小的数字变化不大。最后,我们对计算的数字进行洗牌。

我想到的最好的(用 Java 编写,但任何语言都可以)是:

    // Config variables.
    int min = 101;
    int max = 999;
    int innerMin = 1;
    int innerMax = 99;

    int innerSize = 3;
    int outerSize = 3;

    // The numbers here were just picked "at random".
    double minVariance = 100.0;
    double maxInnerVariance = 33.0;

    // java.util.Random is maybe not optimal, but for now...
    Random rng = new Random();

    int[] numbers = new int[outerSize * innerSize];

    // Fill big array first.
    int[] big = new int[outerSize];
    while ( computeVariance( big ) < minVariance ) {
        for ( int i = 0; i < outerSize; ++i ) {
            int random;
            do {
                // Maybe use nextGaussian here instead?
                random = (int) (min + (rng.nextDouble() * (max - min)));
            } while ( random % 10 == 0 ); // Exclude all numbers that are modulo 10, too easy.

            big[i] = random;
        }
    }

    for ( int i = 0; i < outerSize; ++i ) {
        // Fill a small array for each big array.
        int[] small = new int[innerSize];
        while ( computeVariance( small ) > maxInnerVariance ) {
            for ( int j = 0; i < innerSize; ++i ) {
                int random;
                do {
                    // Maybe use nextGaussian here instead?
                    random = (int) (innerMin + (rng.nextDouble() * (innerMax - innerMin)));
                } while ( random % 10 == 0 ); // Exclude all numbers that are modulo 10, too easy.

                small[i] = big[i] + random;
                numbers[innerSize * i + j] = small[i];
            }
        }
    }

    // Finally shuffle.
    fisherYatesShuffle( numbers, rng );

如您所见,代码看起来相当复杂,有 4 个嵌套循环 - 哎呀?有没有更好的方法是在概念上或算法上等等?

编辑

编辑 1,在@ElKamina :s 评论之后做了一些更清晰的假设...

我做了以下视觉假设: - 数字列表在视觉上被打乱了。 - 它们再次被打乱,为数字提供背景颜色,以产生额外的混乱。 - 为了解决您提出的认知问题,数字以网格表示,因此不适用。

现在一个模型假设: - 所有数字都有 3 位数字(它们的长度没有区别)。

工作解决方案

重做整个实现并使用 nextGaussian 等使其工作。这个解决方案保证了每个集群中的唯一性 (AFAIK),可能速度较慢,但​​它很健壮,质量 > 速度在这里(非常欢迎对代码进行优化) .

我觉得使用 2.0 标准差可以提供很好的传播。 更多代码@http://pastebin.com/iu3U6VG0

    @Override
    public int[] generateList( Random rng, int size ) {
        // outer = index 0, inner = index 1.
        int[] sizes = computeSizes( size );
        int[] numbers = new int[sizes[0] * sizes[1]];
        int outerMultiplier = com.google.common.math.IntMath.pow( 10, this.numDigits - 1 );
        int innerMax = outerMultiplier - 1;

        // Fill outer array first.
        int[] outer = new int[sizes[0]];
        for ( int i = 0; i < sizes[0]; ++i ) {
            outer[i] = RandomMath.nextRandomRanged( rng, 1, 9 ) * outerMultiplier;
        }

        // Fill inner array for each outer array.
        for ( int i = 0; i < sizes[0]; ++i ) {
            // Calculate bounds [min, max].
            int[] innerBounds = new int[] { RandomMath.nextRandomNon10( rng, 1, innerMax ), RandomMath.nextRandomNon10( rng, 1, innerMax ) };
            int diff = innerBounds[1] - innerBounds[0];
            if ( diff < 0 ) {
                // Wrong order, swap!
                PrimitiveArrays.swap( innerBounds, 0, 1 );
                diff = -diff;
            }
            if ( diff < sizes[1] ) {
                // Difference is too small, make sure we got room!
                innerBounds[0] = Math.max( 1, innerBounds[0] - sizes[1] );
                innerBounds[1] = innerBounds[0] + sizes[1];

                diff = innerBounds[1] - innerBounds[0];
            }

            BitSet bits = new BitSet( diff );
            boolean filledModulo10 = false;

            // Now do the filling.
            int[] inner = new int[sizes[1]];
            for ( int j = 0; j < sizes[1]; ++j ) {
                inner[j] = RandomMath.nextGaussianNon10( rng, innerBounds[0], innerBounds[1], MAX_GAUSS_ITERATIONS, INNER_STANDARD_DEVIATIONS );

                // Protect against same numbers all the time, can we do away with this loop? not O(n) but still...
                boolean hasDuplicate = false;
                for ( int k = 0; k < j; ++k ) {
                    if ( inner[k] == inner[j] ) {
                        hasDuplicate = true;
                    }
                }

                if ( hasDuplicate ) {
                    if ( !filledModulo10 ) {
                        // Set all numbers that end with 0 in BitSet, we don't want them!
                        // This assumes that neither innerBounds[0, 1] are modulo 10.
                        for ( int l = ((innerBounds[0] / 10) + 1) * 10; l <= innerBounds[1]; l += 10 ) {
                            bits.set( l - innerBounds[0] );
                        }

                        filledModulo10 = true;
                    }

                    // Find first false bit.
                    // This beats the idea of randomness, but avoiding duplicates is more important!
                    inner[j] = bits.nextClearBit( 0 ) + innerBounds[0];
                }

                bits.set( inner[j] - innerBounds[0] );

                numbers[sizes[1] * i + j] = outer[i] + inner[j];
            }
        }

        return numbers;
    }

【问题讨论】:

  • TL,DR:创建一个随机数列表,其中包含较小的附近数字簇?
  • 是的,好的 TL:DR;我忘记提到的一个约束是附近数字的较小簇(假设所有数字都有 3 位,但不要假设在代码中)必须具有相同的最高有效数字,例如:[107,111,118 ], [342, 346, 351], [981, 984, 986] 不错。
  • 不幸的是,我提供的代码不尊重这一点。也许我的做法完全错误 - 如果我们首先像这样生成最外面的数字怎么办: rand([1-9]) * 100 然后将较低的数字添加到使用 nextGaussian 生成的随机平均值和&一个从低间隔随机化的stdDev? nextGaussian 性能有多快(不是真正的问题,但仍然......)?

标签: java algorithm random numbers statistics


【解决方案1】:

我认为这个问题的定义非常不明确。当你要求人类进行排序时,我认为最困难的部分是理解一个数字的“大”或“小”(视觉认知更重要)。

示例:视觉上如果您在同一行中提供数字,则很难对它们进行排序。你必须计算他们有多少位数等等。

更容易排序:

111111
99999

相比

111111, 99999

因此,人类的分类难度是非常主观的,除非你设置一些限制,否则这是一个定义不明确的问题。

回复 OP 的编辑

你还没有提到人类可以使用什么“硬件”。他们是否有一个基于网格的计算机界面,他们可以用鼠标在数字周围移动?或者,数字打印在纸上,您需要将这些数字以书面形式“输出”到另一张纸上?

您可以从一个实际存在的问题开始。假设你有一堆棒球卡,上面有一些统计数据(比如总跑数)。并且您想根据该统计数据对卡片进行排序。我们可以从这里开始吗?

【讨论】:

  • 对不起,我的错!编辑了我的帖子,概述了一些假设/限制。考虑到这些限制,我(非常主观地)认为,对总体上变异系数高但在较小集群中低的数字进行排序是困难的,至少对我来说是这样。
  • 实际上,我确实提到了GUI,它是Android 手机或平板电脑,假设具有触摸屏功能。 =)
  • @Centril 抱歉,我错过了。界面究竟是如何工作的?你可以拖放数字吗?每个操作的相对困难是什么?
  • 假设正方形中有一个等号网格。你点击按钮。
【解决方案2】:

您可以找到有效的解决方案@http://pastebin.com/iu3U6VG0 - 或阅读问题 =)

【讨论】:

    猜你喜欢
    • 2014-10-01
    • 1970-01-01
    • 2010-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    相关资源
    最近更新 更多