以特定顺序迭代数组,以便公平地对其进行采样

时间:2016-06-22 16:01:38

标签: arrays algorithm language-agnostic sequence permutation

我想以某种方式迭代数组:
从数组的第一个和最后一个元素开始,我想访问的下一个元素是离所有先前访问过的元素最远的元素。

对于长度为n + 1的数组,序列为

  • 0,
  • n,
  • n / 2(距离0和n最远),
  • n / 4和n * 3/4(距离之前所有3个指数最远),
  • n / 8,n * 3/8,n * 5/8,n * 7/8,(离以前所有5个指数最远)
  • n * 1/16,n * 3/16,n * 5/16,n * 7/16,n * 9/16,n * 11/16,n * 13/16,n * 15/16
  • ...

如果n不是2的幂,则其中一些数字必须向上或向下舍入,但我不确定在舍入时如何避免重复。

最后我想要一个整数序列,它包含0和n之间的所有数字恰好一次。 (对于任何n,而不仅仅是2的幂)

这个排列是否有名称?

生成这些数字的函数如何工作?

我正在寻找能够即时生成这些数字的功能。

如果有十亿个元素,我不想管理所有先前访问过的元素的巨大列表,或者提前生成整个排列列表。

我的想法是,一旦找到符合某些标准的元素,我就可以中止迭代,所以在大多数情况下我不需要整个排列序列。

所以我正在寻找具有以下属性的函数f(int currentIndex, int maxIndex)

要对大小为8的数组进行交互,我会调用

f(0,8) returns 0, to get the index of the first element
f(1,8) returns 8
f(2,8) returns 4
f(3,8) returns 2
f(4,8) returns 6
f(5,8) returns 1
f(6,8) returns 3
f(7,8) returns 5
f(8,8) returns 7

(我不太确定如何将此示例扩展为不是2的幂的数字)

是否有具有这些属性的函数?

5 个答案:

答案 0 :(得分:1)

你所描述的跳跃是Van der Corput序列的一个特征,如a task I wrote on Rosetta Code中所述。

我有一个确切的函数来重新排序输入序列,但它需要与输入数组一样大的数组。

接下来是一个近似解决方案,它逐个产生索引,只取输入数组的长度,然后用常量内存计算索引。

测试给出了一些关于例程如何“好”的指示。

{{1}}

答案 1 :(得分:0)

你能不能使用数组[n] [i]

这样

Array [0][i] = "1,2,3,4,5,6,7" 'start
Array [1][i] = "1,2,3,4" '1st gen split 1
Array [2][i] = "4,5,6,7" '1st gen split 2
Array [3][i] = "1,2" '2nd gen split 1 split 1
Array [4][i] = "3,4" '2nd gen split 1 split 2
Array [5][i] = "4,5" '2nd gen split 2 split 1
Array [6][i] = "6,7" '2nd gen split 2 split 1

'使用动态迭代,以便知道进入数组的大小,即nextGen = Toint(Ubound(Array)/ 2)

If(
   last(Array[n][i]) = first(Array[n+1][i] 
   then Pop(Array[n+1][i])
)

答案 2 :(得分:0)

我知道如何做到这一点,但描述一下是很棘手的..忍受我。

关键思想是将数组逻辑分为两组:一组包含多个元素,其中两个元素的最大幂仍然小于数组的大小,另一个包含其他所有元素。 (所以,如果你的数组包含29个元素,那么你有一个16个,另一个有13个。)你希望这些元素尽可能公平地混合,你想要:

  1. 找到" Real"的功能第一个i-th元素的索引 逻辑集(等效于:第二组中有多少元素位于第一组的i-th元素之前)
  2. 告诉您某个索引i是否属于的函数 到第一个或第二个逻辑集。
  3. 然后你运行"理想"您在第一组中描述的函数(使用上面的函数1进行映射),然后对其余元素进行单次传递。只要您在逻辑集之间公平分配,就可以按照您的描述进行。

    (逻辑上)描述哪些索引属于哪个分区:调用第一个逻辑分区k的大小和第二个分区j的大小。假设第一组的每个元素都有j/k单位的" credit"与之相关联。开始使用逻辑数组的元素填充真实数组,随时添加信用,但每次获得多个信用单位时,请从第二个数组中放置一个元素,并将存储的信用减少一个。这将在第一个数组的j元素之间公平地分配来自第二个数组的k元素。注意:您实际上并未执行此计算,它只是一个逻辑定义。

    通过一点算术,您可以使用它来实现我上面描述的功能。在第一组的i-th元素与第二组的floor(i * j/k)元素完全相同之前。您只能在最后一次传递期间运行第二个函数,因此您可以从定义中完全运行该函数。

    这有意义吗?我确信这会奏效,但很难描述。

答案 3 :(得分:0)

是的,它被称为分区 在有序数组中搜索是一种非常常见的方法 此外,它由QuickSort算法使用。

它主要是作为递归函数实现的,它对" center"进行采样。元素,然后递归"左"收集,然后"权利"集合。
如果数组长度为1,则对其进行采样并且不要递归。

在以下示例中,我只按您描述的顺序搜索数组,
如果数组是有序的,在检查第一个数据透视后,我会跳过检查RightPart或LeftPart,具体取决于数据透视值。

int partition(int* arr, int min, int max, int subject) 
{ // [min, max] inclusive!
    int pivot = (max - min + 1) >> 1; // (max - min)/2
    if(arr[pivot] == subject)
        return pivot;

    if(pivot > 0) 
    {
        int leftPart = partition(arr, min, pivot - 1, subject);
        if(leftPart >= 0)
            return leftPart;
    }

    if(max - pivot > 0) 
    {
        int rightPart = partition(arr, pivot + 1, max, subject);
        if(rightPart >= 0)
            return rightPart;
    }

    return -1; // not found
}

int myArr[10] = {4,8,11,7,2,88,42,6,5,11 };
int idxOf5 = partition(myArr, 0, 9, 5);

答案 4 :(得分:0)

我自己能够用Paddy3118和Edward Peters给出的提示解决这个问题。

我现在有一种方法可以为给定范围生成Van der Corput排列,没有重复项且没有遗漏值,并且具有恒定且可忽略的内存要求和良好的性能。

该方法使用c#iterable来动态生成序列。

方法VanDerCorputPermutation()采用两个参数,范围的上限,以及应该用于生成序列的基数。默认情况下,使用基数2.

如果范围不是给定基数的幂,则在内部使用下一个更大的幂,并且将丢弃在该范围之外生成的所有索引。

用法:

Console.WriteLine(string.Join("; ",VanDerCorputPermutation(8,2)));
// 0; 4; 2; 6; 1; 3; 5; 7

Console.WriteLine(string.Join("; ",VanDerCorputPermutation(9,2)));
// 0; 8; 4; 2; 6; 1; 3; 5; 7

Console.WriteLine(string.Join("; ",VanDerCorputPermutation(10,3)));
// 0; 9; 3; 6; 1; 2; 4; 5; 7; 8 

Console.WriteLine(VanDerCorputPermutation(Int32.MaxValue,2).Count());
// 2147483647 (with constant memory usage)

foreach(int i in VanDerCorputPermutation(bigArray.Length))
{
     // do stuff with bigArray[i]
}

for (int max = 0; max < 100000; max++)
{
    for (int numBase = 2; numBase < 1000; numBase++)
    {
        var perm = VanDerCorputPermutation(max, numBase).ToList();
        Debug.Assert(perm.Count==max);
        Debug.Assert(perm.Distinct().Count()==max);
    }
}

代码本身只使用整数arithemtic和很少的部门:

IEnumerable<int> VanDerCorputPermutation(int lessThan, int numBase = 2)
{
    if (numBase < 2) throw new ArgumentException("numBase must be greater than 1");

    // no index is less than zero
    if (lessThan <= 0) yield break;

    // always return the first element
    yield return 0;

    // find the smallest power-of-n that is big enough to generate all values
    int power = 1;
    while (power < lessThan / numBase + 1) power *= numBase;

    // starting with the largest power-of-n, this loop generates all values between 0 and lessThan 
    // that are multiples of this power, and have not been generated before.
    // Then the process is repeated for the next smaller power-of-n
    while (power >= 1)
    {
        int modulo = 0;
        for (int result = power; result < lessThan; result+=power)
        {
            if (result < power) break; // overflow, bigger than MaxInt

            if (++modulo == numBase)
            {
                //we have used this result before, with a larger power 
                modulo = 0;
                continue;
            }

            yield return result;
        }
        power /= numBase; // get the next smaller power-of-n
    }
}