在等效数组之间分配元素以实现平衡和

Distribute elements between equivalent arrays to achieve balanced sums

提问人:Boris Mocialov 提问时间:2/27/2015 最后编辑:Boris Mocialov 更新时间:3/8/2015 访问量:407

问:

我得到了一组元素,比如说,从 10 到 21(总是连续的), 我生成相同大小的数组,其中大小由运行时决定。

3 个生成的数组示例(数组 # 是动态的,以及所有数组中元素的 #,其中某些元素可以是 0 - 未使用):

A1 = [10, 11, 12, 13]

答2 = [14, 15, 16, 17]

答3 = [18, 19, 20, 21]

这些生成的数组将被提供给不同的进程,以对元素进行一些计算。我的目标是平衡每个将获得数组的进程的负载。我的意思是:

在给定的例子中,有

答1 = 46

答2 = 62

答3 = 78

对为每个线程给定的元素进行潜在迭代。

我想重新排列初始数组,以便为每个进程提供相等的工作量,例如:

答1 = [21, 11, 12, 13] = 57

答2 = [14, 15, 16, 17] = 62

答3 = [18, 19, 20, 10] = 67

(不是平均分配,但比最初更公平)。分布可以不同,只要它们接近某个最佳分布,并且优于第一个和最后一个数组的最坏(初始)情况。在我看来,可以使用不同的索引来实现不同的分布 [其中数组的拆分{可以是不均匀的}]

这适用于给定的例子,但可能会有奇怪的情况。

所以,我认为这是一个反射问题(由于缺乏正确定义的知识),其中数组应该通过对角线来看待,例如:

10|111213

1415|1617

181920|21

然后可以进行明显的替换。

我试图实现这样的:

  if(rest == 0)
    payload_size = (upper-lower)/(processes-1);
  else
    payload_size = (upper-lower)/(processes-1) + 1;
  //printf("payload size: %d\n", payload_size);
  long payload[payload_size];
  int m = 0;
  int k = payload_size/2;
  int added = 0;  //track what been added so far (to skip over already added elements)
  int added2 = 0;  // same as 'added'
  int p = 0;
  for (i = lower; i <= upper; i=i+payload_size){
    for(j = i; j<(i+payload_size); j++){
       if(j <= upper){
         if((j-i) > k){
           if(added2 > j){
             added = j;
             payload[(j-i)] = j;
             printf("1 adding data: %d at location: %d\n", payload[(j-i)], (j-i));
           }else{
             printf("else..\n");
           }
         }else{
           if(added < upper - (m+1)){
             payload[(j-i)] = upper - (p*payload_size) - (m++);
             added2 = payload[(j-i)];
             printf("2 adding data: %d at location: %d\n", payload[(j-i)], (j-i));
           }else{
             payload[(j-i)] = j;
             printf("2.5 adding data: %d at location: %d\n", payload[(j-i)], (j-i));
           }
         }
       }else{ payload[(j-i)] = '\0'; }
    }
    p++;
    k=k/2;

    //printf("send to proc: %d\n", ((i)/payload_size)%(processes-1)+1);
  }

..但失败得很惨。

你肯定可以看到实现中的问题,因为它的可扩展性差、不完整、凌乱、写得不好等等,等等,等等,等等,等等......

因此,我需要帮助,要么是实施方面的帮助,要么是更好的方法来完成我想要实现的目标,给定的描述。

P.S. 我需要解决方案尽可能“内联”(避免循环嵌套)——这就是我使用一堆标志和全局索引的原因。

当然,这可以通过额外的循环和不必要的迭代来完成。我邀请那些能够欣赏 t̲h̲e̲ ̲a̲r̲t̲ ̲o̲f̲ ̲i̲n̲d̲e̲x̲i̲n̲g̲ 的人。

我确信那里有一个解决方案,但我就是无法进行适当的 Google 查询来找到它。

提示?我想到使用索引 % size_of_my_data 来完成这项任务。.

P.S. 应用:描述在这里

c 数组 循环 mapreduce

评论

1赞 WhozCraig 2/27/2015
“我确信那里有解决方案”——不要那么确定。最佳拟合(箱装)算法来自类似的问题(车队卡车上的奇数包装具有最佳效率,以实现最少的卡车数量交付就是这样一个例子),它们比大多数人想象的要复杂得多。
0赞 advocateofnone 2/27/2015
Are A_1,A_2,A_3...已经给出了?还是必须以某种特定方式生成?
0赞 Boris Mocialov 2/27/2015
@sashaI am given a set of elements from, say, 10 to 21, I generate arrays of the same size, where size is determined runtime.
0赞 Boris Mocialov 2/27/2015
@WhozCraig 很高兴知道我不是唯一一个为此苦苦挣扎的人。谢谢你的一些关键词。我知道启发式算法用于您描述的事情,这些是 NP-Hard 问题。
0赞 Shashank 2/27/2015
也许对它们进行排序,并从列表的中心向外扩展,反之亦然,在数组之间均匀分布数字。这是一个幼稚而简单的策略,但嘿,它让你非常接近。对于给定的示例,如果遵循此算法,则将得到 sum(A1) = 64、sum(A2) = 58、sum(A3) = 64。

答:

0赞 jschultz410 2/27/2015 #1

通过分配一个简单的序列,您可以依次迭代地将 min 和 max 元素添加到每个列表中。有一些终止细节需要修复,但这是一般的想法。应用于您的示例,输出将如下所示:

john-schultzs-macbook-pro:~ jschultz$ ./a.out
10 21 13 18  = 62
11 20 14 17  = 62
12 19 15 16  = 62

当num_procs平均分配num_elems时,像这样的简单反射分配将是最佳的。它将是次优的,但仍然不错,当它没有:

#include <stdio.h>

int compute_dist(int lower, int upper, int num_procs)
{
  if (lower > upper || num_procs <= 0)
    return -1;

  int num_elems                = upper - lower + 1;
  int num_elems_per_proc_floor = num_elems / num_procs;
  int num_elems_per_proc_ceil  = num_elems_per_proc_floor + (num_elems % num_procs != 0);
  int procs[num_procs][num_elems_per_proc_ceil];
  int i, j, sum;

  // assign pairs of (lower, upper) to each process until we can't anymore

  for (i = 0; i + 2 <= num_elems_per_proc_floor; i += 2)
    for (j = 0; j < num_procs; ++j)
    {
      procs[j][i]   = lower++;
      procs[j][i+1] = upper--;
    }          

  // handle left overs similarly to the above
  // NOTE: actually you could use just this loop alone if you set i = 0 here, but the above loop is more understandable

  for (; i < num_elems_per_proc_ceil; ++i)
    for (j = 0; j < num_procs; ++j)
      if (lower <= upper)
        procs[j][i] = ((0 == i % 2) ? lower++ : upper--);
      else
        procs[j][i] = 0;

  // print assignment results

  for (j = 0; j < num_procs; ++j)
  {
    for (i = 0, sum = 0; i < num_elems_per_proc_ceil; ++i)
    {
      printf("%d ", procs[j][i]);
      sum += procs[j][i];
    }
    printf(" = %d\n", sum);
  }

  return 0;
}

int main()
{
  compute_dist(10, 21, 3);

  return 0;
}

评论

0赞 jschultz410 2/27/2015
PS - 这也是一个贪婪的算法。
0赞 jschultz410 2/27/2015
修复了一些剩余/终止错误和行为以变得更好。
0赞 Boris Mocialov 3/7/2015
感谢您的输入。我没有使用你的解决方案,因为它使用的数据结构与我原来的方法不同,并且有额外的循环,我明确表示应该避免。因此,我不能接受你的解决方案
0赞 Shashank 2/27/2015 #2

这是我使用 deque 编写的 O(n) 解决方案(双端队列,不需要 deque,可以使用简单的数组,但由于 popRight 和 popLeft,deque 使代码干净)。代码是 Python,而不是伪代码,但它应该很容易理解(因为它是 Python)。

def balancingSumProblem(seqStart = None, seqStop = None, numberOfArrays = None):
    from random import randint
    from collections import deque

    seq = deque(xrange(seqStart or randint(1, 10), 
                        seqStop and seqStop + 1 or randint(11,30)))
    arrays = [[] for _ in xrange(numberOfArrays or randint(1,6))]

    print "# of elements: {}".format(len(seq))
    print "# of arrays: {}".format(len(arrays))
    averageNumElements = float(len(seq)) / len(arrays)
    print "average number of elements per array: {}".format(averageNumElements)

    oddIteration = True
    try:
        while seq:
            for array in arrays:
                if len(array) < averageNumElements and oddIteration:
                    array.append(seq.pop()) # pop() is like popright()
                elif len(array) < averageNumElements:
                    array.append(seq.popleft())
            oddIteration = not oddIteration
    except IndexError:
        pass

    print arrays
    print [sum(array) for array in arrays]

balancingSumProblem(10,21,3) # Given Example
print "\n---------\n"
balancingSumProblem() # Randomized Test

基本上,从迭代到迭代,它在抓取大元素并将它们均匀分布在数组中和抓取小元素并将它们均匀分布在数组中之间交替进行。它从外到内(尽管你可以从内到外),并尝试使用每个数组的平均元素数来进一步平衡它。

它不是所有测试的 100% 准确,但它在大多数随机测试中都做得很好。您可以尝试在此处运行代码: http://repl.it/cJg

评论

0赞 Boris Mocialov 3/7/2015
感谢您的输入。我不熟悉您在这里提到的一些 Python 函数,因此不能接受它作为答案
0赞 Boris Mocialov 3/7/2015 #3

我使用了这个实现,我在本报告中提到过(实现适用于我用于测试 (1-15K) (1-30K) 和 (1-100K) 数据集的情况。我并不是说它对所有情况都有效):

int aFunction(long lower, long upper, int payload_size, int processes)
{
    long result, i, j;
    MPI_Status status;


    long payload[payload_size];
    int m = 0;
    int k = (payload_size/2)+(payload_size%2)+1;
    int lastAdded1 = 0;
    int lastAdded2 = 0;
    int p = 0;
    int substituted = 0;
    int allowUpdate = 1;
    int s;
    int times = 1;
    int times2 = 0;
    for (i = lower; i <= upper; i=i+payload_size){
        for(j = i; j<(i+payload_size); j++){
            if(j <= upper){
                if(k != 0){
                    if((j-i) >= k){
                        payload[(j-i)] = j- (m);
                        lastAdded2 = payload[(j-i)];
                    }else{
                        payload[(j-i)] = upper - (p*payload_size) - (m++) + (p*payload_size);

                        if(allowUpdate){
                            lastAdded1 = payload[(j-i)];
                            allowUpdate = 0;
                        }
                    }
                }else{

                    int n;
                    int from = lastAdded1 > lastAdded2 ? lastAdded2 : lastAdded1;
                    from = from + 1;
                    int to = lastAdded1 > lastAdded2 ? lastAdded1 : lastAdded2;


                    int tempFrom = (to-from)/payload_size + ((to-from)%payload_size>0 ? 1 : 0);
                    for(s = 0; s < tempFrom; s++){

                        int restIndex = -1;


                        for(n = from; n < from+payload_size; n++){
                            restIndex = restIndex + 1;
                            payload[restIndex] = '\0';
                            if(n < to && n >= from){
                                payload[restIndex] = n;
                            }else{
                                payload[restIndex] = '\0';
                            }
                        }

                        from = from + payload_size;
                    }

                    return 0;

                }
            }else{ payload[(j-i)] = '\0'; }
        }
        p++;
        k=(k/2)+(k%2)+1;
        allowUpdate = 1;

    }

    return 0;
}

评论

0赞 Boris Mocialov 5/30/2015
现在我明白了,这实际上是一个非常丑陋的解决方案