【问题标题】:sorting an array based on proximity to mean根据与均值的接近程度对数组进行排序
【发布时间】:2014-04-12 00:22:30
【问题描述】:

我有一个双打数组。我计算数组的平均值。

我现在需要根据每个值与平均值的接近程度对数组进行排序。

如果我可以将一个特殊的比较器函数传递给将平均值作为第三个参数的 qsort,那么它将解决我的问题:

int compareValues(double a, double b, double mean)
{
    double aValue = fabs(mean - a);
    double bValue = fabs(mean - b);
    if (aValue > bValue)
       return 1;
    else if (aValue < bValue)
       return -1;
    else return 0;
}

但是,根据我阅读的所有内容,您不能将这样的比较器传递给 qsort。

有没有我看不到的简单方法?

【问题讨论】:

  • 不使用某种全局变量(不要那样做),qsort 是不可能做到这一点的。
  • 在 BSD 和 Mac OS X 上,有:void qsort_r(void *base, size_t nel, size_t width, void *thunk, int (*compar)(void *, const void *, const void *));,其中 thunk 作为第一个指针传递给比较器函数。 qsort_r() 函数的行为与qsort() 相同,只是它接受了一个附加参数thunk,它作为第一个参数原样传递给指向compar 的函数。这允许比较函数在不使用全局变量的情况下访问其他数据,因此qsort_r() 适用于必须可重入的函数。

标签: c arrays sorting mean qsort


【解决方案1】:

构建您自己的专用快速排序功能:

int comp(double a, double b, double mean)
{
    a = fabs(mean - a);
    b = fabs(mean - b);
    if (a > b)
       return 1;
    else if (a < b)
       return -1;
    else return 0;
}

void swap(double *v, int a, int b)
{
    double temp;

    temp = v[a];
    v[a] = v[b];
    v[b] = temp;
}

void sort(double *v, int left, int right, double mean, int (*comp)(double, double, double))
{
    int i, last;

    if (left >= right) return;
    swap(v, left, (left + right) / 2);
    last = left;
    for (i = left + 1; i <= right; i++) {
        if (comp(v[i], v[left], mean) < 0)
            swap(v, ++last, i);
    }
    swap(v, left, last);
    sort(v, left, last - 1, mean, comp);
    sort(v, last + 1, right, mean, comp);
}

【讨论】:

  • 我会使用像int (*comp)(const void *, const void *, const void*) 这样的通用比较器函数类型,而我正在使用它。否则,这个排序函数只能用于具有状态的比较器的这个特定实例
  • +1 喜欢这种方法,因为我过去想做类似的事情。在一般的解决方案中,第三个变量是某种指向状态的指针。
  • 您的交换代码交换指针,而不是指向的数据。这与sort(void *v[], …) 声明一致,但要求您对double * 的数组进行排序,而不是double 的数组。当给定一个double * 的数组时,它可以使用蛮力强制转换,但这并不是所需要的。
  • @JonathanLeffler,是的,你是对的,当我尝试将我的双打数组作为第一个参数传递时,我发现了这一点。有没有办法改变代码,让它接受一个双精度数组,而不是双指针?
  • @user994179:是的,有办法做到这一点——BSD qsort_r() 方法是合理的并且提供了先例。
【解决方案2】:

我只是提出一个想法,我还没有考虑到这一点,但是您可以尝试为比较器构建一个类,并在初始化时将 mean 定义为全局成员。

另外,您可以创建一个并行数组,如下所示: newArray[i] = fabs(mean - oldArray[i]);

并根据新数组对旧数组进行排序。

【讨论】:

  • 那不会是 C(而是 C++),但类似的事情可以用结构来完成(虽然它有点恶心)。努力解决
【解决方案3】:

@merlin2011 现在删除的答案有一个好主意。

void qsort(void *base, size_t nmemb, size_t size, 
    int (*compar)(const void *a, const void *b));

传递给qsort 的比较函数采用指针(而不是双精度)。此外,qsort() 永远不会使用NULL 参数调用compar()。因此可以先用a == NULL 调用比较函数,然后b 可以指向状态信息。

int compar(const void *a, const void *b) {
  static void *state = NULL;
  if (a == NULL || b == NULL) {
    return Setup_State(&state, a, b);
  }
  return Do_normal_compare(state, a, b);
}

Setup_State() 的复杂性和可重入兼容性可能非常复杂,但下面是一个简单的不可重入示例。

int Setup_State(void **statep, void* a, void *b) {
  *statep = b;
  return 0;
}

int Do_normal_compare(void *state, void* a, void *b) {
  double da = *((double *) a);
  double db = *((double *) b);
  double dmean = *((double *) state);
  return OP_compareValues(da, db, dmean);
}

// usage 

double mean;
compar(NULL, &mean);
qsort(d_array, N, sizeof(double), compar);

也许与b == NULL 的通话可能表示某种状态后清理。

compar(NULL, NULL);

【讨论】:

    【解决方案4】:

    虽然您确实不能将此类比较器传递给qsort,但您也可以将平均值存储在比较器和调用qsort 的函数之外的静态变量中,并使用“知道”从静态变量中获取平均值的常规双参数比较器:

    static double mean;
    
    int compareValues(const void *pa, const void *pb)
    {
        double a = *((double*)pb);
        double b = *((double*)pa);
        double aValue = fabs(mean - a);
        double bValue = fabs(mean - b);
        if (aValue > bValue)
           return 1;
        else if (aValue < bValue)
           return -1;
        else return 0;
    }
    
    void call_sort(double *data, size_t count) {
        mean = find_mean(data, count);
        qsort(data, count, sizeof(double), compareValues)
    }
    

    这种方法的不幸后果是您的排序函数不再是可重入的。

    【讨论】:

    • 是的,我想过,但我在 Lent 期间放弃了全局变量。
    • @user994179 静态变量不是全局变量——它们在翻译单元之外是不可见的。
    • 对不起,我的错。不确定“翻译单元”是什么,但我会找出答案的。
    【解决方案5】:

    我更喜欢“构建自己的qsort”答案。但是你可以做这样的事情。

    我不是对双精度列表和外部均值进行排序,而是对结构列表进行排序,每个结构存储指向双精度和(相同)均值的指针。

    我对 that 数组进行排序,然后列出结果的值。

    似乎有效,但最终比@AlterMann 所做的要糟糕得多。

    gcc -o cmp cmp.c -std=c99 -Lmath

    #include <stdlib.h>
    #include <stdio.h>
    #include <math.h>
    
    int compareValues(double a, double b, double mean)
    {
        double aValue = fabs(mean - a);
        double bValue = fabs(mean - b);
        if (aValue > bValue)
           return 1;
        else if (aValue < bValue)
           return -1;
        else return 0;
    }
    
    typedef struct dbl_and_mean_t{
        const double* value;
        const double* mean;
    } dbl_and_mean;
    
    int cmpToMean(const void* a, const void* b){
        dbl_and_mean* dma = (dbl_and_mean*)a;
        dbl_and_mean* dmb = (dbl_and_mean*)b;
    
        double aValue = *(dma->value);
        double bValue = *(dmb->value);
        // we assume mean is same in both cases.
        double mean = *(dma->mean);
        return compareValues( aValue, bValue, mean );
    }
    
    /*
           void qsort(void *base, size_t nmemb, size_t size,
                      int(*compar)(const void *, const void *));
    */
    
    int main(int argc, char* argv[]) {
        unsigned int numlen = 100;
        double numbers[100] = {
     20.11267454572858, 27.00916748845121, 41.69273886807976, 59.574594859929206, 5.148795948275042, 25.0952600949092, 7.490782458661016, 46.82558348995649, 99.1505635434539, 80.82884752229698, 34.562195425918965, 12.83419004171462, 65.21675903144343, 60.939055397544415, 59.716932510808405, 68.58214712201324, 16.96903326566488, 40.890117173893096, 43.24295370982686, 49.74083920053604, 51.879578222761545, 85.55201465227584, 0.9988146850440804, 72.9927624741183, 24.544352584593764, 38.07294449540915, 89.21601198806061, 46.242113823416055, 34.77013261276065, 75.50489987606491, 68.73075063359678, 34.11250399830412, 33.03497314824547, 50.356507540969574, 43.44185408674688, 1.3480391491077937, 17.87324689034111, 27.521463721587523, 65.36478555088043, 89.08983557487836, 91.20949281863321, 36.15883451406319, 64.71929705431249, 96.51660222081459, 84.3925334284804, 9.273474377948954, 20.7970994809055, 81.63173897647613, 34.54178336906219, 69.17908602857048, 34.49554788014096, 27.658713128591337, 37.84762296714004, 32.47882877578083, 80.33388365434217, 12.535403896961606, 1.6177858463917616, 58.492589297744544, 4.996882418234216, 0.6516899504362961, 94.14913555948795, 45.01455399721226, 91.13032884578304, 0.9747543756017163, 87.73797888418335, 17.05103955970504, 34.9990215191348, 32.132359722564175, 51.39141618413181, 90.41510433921886, 70.85275376557709, 60.81740079574899, 56.276844928014334, 96.84741168778665, 38.587969750110915, 26.93423429759396, 56.28727064877738, 40.69208717486249, 30.893466414304214, 54.69704130793473, 8.422991004598423, 42.51756379315109, 6.109299688810255, 97.5321480398511, 76.34912536352495, 83.6200551607522, 19.447640061947336, 29.659746702311896, 72.24996303415246, 7.992406225268933, 57.09202659164654, 60.782606246000036, 60.398430869817474, 41.77937462471086, 47.28376403551421, 54.31179044336384, 39.837395485680894, 39.301123086537395, 71.8438289228498, 49.209926974123285
        };
        double mean, sum = 0;
        dbl_and_mean* doubles_and_mean = calloc( numlen, sizeof(dbl_and_mean) );
        for( unsigned i = 0; i < numlen; i++){
            sum = sum + numbers[i];
            doubles_and_mean[i].value = &(numbers[i]);
            doubles_and_mean[i].mean = &mean;
        }
        mean = sum / numlen;
        printf("Mean is %f\n", mean);
        qsort( doubles_and_mean , 100, sizeof(dbl_and_mean), &cmpToMean);
        for( unsigned int i=0; i< numlen; i++){
            printf("%03d: %f\n", i, *(doubles_and_mean[i].value) );
        }
    }
    

    【讨论】:

      【解决方案6】:

      David Ranieri 在他的answer 中给出的代码是一个混合的、半泛型的排序函数。这是 BSD qsort_r() 函数的重新实现,它是 qsort() 的完全通用变体,为比较器函数提供“上下文”(或“thunk”)。

      代码包含测试数据,并输出:

      Mean:   3.4940
      Before:
      [0] =   3.1416 (delta =   0.3524)
      [1] =   2.7813 (delta =   0.7127)
      [2] =   1.6130 (delta =   1.8810)
      [3] =   9.8126 (delta =   6.3186)
      [4] =   0.1213 (delta =   3.3727)
      After:
      [0] =   3.1416 (delta =   0.3524)
      [1] =   2.7813 (delta =   0.7127)
      [2] =   1.6130 (delta =   1.8810)
      [3] =   0.1213 (delta =   3.3727)
      [4] =   9.8126 (delta =   6.3186)
      Mean:   5.1282
      Before:
      [0] =   1.2300 (delta =   3.8982)
      [1] =   3.2900 (delta =   1.8382)
      [2] =   8.1800 (delta =   3.0518)
      [3] =   2.5100 (delta =   2.6182)
      [4] =   4.1800 (delta =   0.9482)
      [5] =   9.9900 (delta =   4.8618)
      [6] =   6.7500 (delta =   1.6218)
      [7] =   1.0100 (delta =   4.1182)
      [8] =   4.6800 (delta =   0.4482)
      [9] =   8.2100 (delta =   3.0818)
      [10] =   6.3800 (delta =   1.2518)
      After:
      [0] =   4.6800 (delta =   0.4482)
      [1] =   4.1800 (delta =   0.9482)
      [2] =   6.3800 (delta =   1.2518)
      [3] =   6.7500 (delta =   1.6218)
      [4] =   3.2900 (delta =   1.8382)
      [5] =   2.5100 (delta =   2.6182)
      [6] =   8.1800 (delta =   3.0518)
      [7] =   8.2100 (delta =   3.0818)
      [8] =   1.2300 (delta =   3.8982)
      [9] =   1.0100 (delta =   4.1182)
      [10] =   9.9900 (delta =   4.8618)
      

      这部分基于我一直在使用的一些代码,它参数化了来自快速排序的其余部分的各种实现的分区函数,因此它被拆分得比其他情况要多一些。

      #include <assert.h>
      #include <math.h>
      #include <stdlib.h>
      #include <string.h>
      
      /*
      ** Implement BSD qsort_r() under the name Qsort_r()
      **
      ** void qsort_r(void *base, size_t nel, size_t width, void *thunk,
      **              int (*compar)(void *, const void *, const void *));
      **
      ** The qsort_r() function behaves identically to qsort(), except that it
      ** takes an additional argument, thunk, which is passed unchanged as the
      ** first argument to function pointed to compar.  This allows the
      ** comparison function to access additional data without using global
      ** variables, and thus qsort_r() is suitable for use in functions which
      ** must be reentrant.
      */
      
      typedef int (*Comparator)(void *, const void *, const void *);
      
      extern void Qsort_r(void *base, size_t num, size_t size, void *thunk, Comparator cmp);
      
      static
      void swap(void *val1, void *val2, size_t size)
      {
          char temp[size];
      
          memmove(temp, val1, size);
          memmove(val1, val2, size);
          memmove(val2, temp, size);
      }
      
      /* Generate random number in range p..r inclusive */
      static size_t random_int(size_t p, size_t r)
      {
          assert(p <= r);
          size_t q = rand() % (r - p + 1) + p;
          return q;
      }
      
      static inline size_t thin_partition_random(void *base, size_t size,
          size_t lo, size_t hi, void *thunk, Comparator cmp)
      {
          /* Use a random element as the pivot */
          assert(hi > 0);
          size_t m = random_int(lo, hi - 1);
          swap((char *)base + size * m, (char *)base + size * lo, size);
      
          /*
          ** Set l and r and move the array elements such
          ** that for all i in [lo..r), a[i] <= pivot,
          ** and for all i in [r+1..hi), a[i] >= pivot.
          */
      
          size_t l = lo + 1;
          size_t r = hi;
          while (l < r)
          {
              if (cmp(thunk, (char *)base + size * l, (char *)base + size * lo) <= 0)
                  l++;
              else
                  swap((char *)base + size * l, (char *)base + size * --r, size);
          }
      
          /* Put pivot element into place */
          swap((char *)base + size * lo, (char *)base + size * --l, size);
          return r;
      }
      
      /* Sorts the elements of a between low and high inclusive */
      /* Sort short sub-partition recursively and iterate to sort longer */
      void Qsort_r(void *base, size_t num, size_t size, void *thunk, Comparator cmp)
      {
          size_t low = 0;
          size_t high = num;
      
          while (low < high)
          {
              size_t r = thin_partition_random(base, size, low, high, thunk, cmp);
              size_t l = r - 1;
              assert(l >= low && high >= r);
              if (l - low > high - r)
              {
                  if (high - r > 1)
                      Qsort_r((char *)base + size * r, high - r + 1, size, thunk, cmp);
                  high = l;
              }
              else
              {
                  if (l - low > 1)
                      Qsort_r((char *)base + size * low, l - low + 1, size, thunk, cmp);
                  low = r;
              }
          }
      }
      
      /* -- Test harness -- */
      
      #include <stdio.h>
      
      static void dump_array(int n, double *d, double mean)
      {
          for (int i = 0; i < n; i++)
              printf("[%d] = %8.4f (delta = %8.4f)\n", i, d[i], fabs(d[i] - mean));
      }
      
      static
      int comp(void *thunk, const void *pa, const void *pb)
      {
          double aValue = fabs(*(double *)thunk - *(double *)pa);
          double bValue = fabs(*(double *)thunk - *(double *)pb);
          if (aValue > bValue)
             return 1;
          else if (aValue < bValue)
             return -1;
          else return 0;
      }
      
      static void test(int n, double *d)
      {
          double sum = 0;
          for (int i = 0; i < n; i++)
              sum += d[i];
          double mean= sum / n;
      
          printf("Mean: %8.4f\n", mean);
          printf("Before:\n");
          dump_array(n, d, mean);
          Qsort_r(d, n, sizeof(double), &mean, comp);
          printf("After:\n");
          dump_array(n, d, mean);
      }
      
      int main(void)
      {
          double d[] = { 3.1416, 2.7813, 1.613, 9.8126, 0.1213 };
          enum { NUM_D = sizeof(d) / sizeof(d[0]) };
          test(NUM_D, d);
      
          double data[] =
          {
              1.23, 3.29, 8.18, 2.51, 4.18, 9.99,
              6.75, 1.01, 4.68, 8.21, 6.38,
          };
          enum { NUM_DATA = sizeof(data) / sizeof(data[0]) };
          test(NUM_DATA, data);
      
          return 0;
      }
      

      请注意,由于它使用标准库函数rand(),因此它不是生产就绪的,一个好的库函数不应该这样做——调用这个函数可能会破坏程序的可重复性。一个不错的替代方案使用drand48() 系列函数,并专门使用自己的种子和状态运行,使用:

      • long nrand48(unsigned short xsubi[3]);

      返回范围 [0, 231) 中的值。获得合适的种子是另一个可以解决的问题,但往往每个平台都可以解决。


      注意Linux(或GNU C库)也实现了qsort_r(),但接口与BSD版本不同:

      void qsort_r(void *base, size_t nmemb, size_t size,
                   int (*compar)(const void *, const void *, void *),
                   void *arg);
      

      这两个接口等价但不同。 BSD 版本将辅助数据作为第一个参数传递给比较器,而 Linux 将辅助数据作为最后一个参数传递给比较器。另外,在对qsort_r() 的调用中,BSD 版本在比较器之前传递辅助数据指针,Linux 版本在比较器之后传递它。关于差异的唯一好消息是,为两个接口之一创建的代码不会为另一个接口完全编译。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-14
        • 2015-04-08
        • 2020-02-13
        • 2019-08-10
        相关资源
        最近更新 更多