4
\$\begingroup\$

Performs interpolation binary search over an array sorted in ascending order. If value found returns found and index else returns not found, the insertion index is returned. Assumptions :

  1. data is sorted in ascending order and all the values in data are unique
  2. left >=0 and right <= data.size - 1 and left < right during input

File array_search.h

#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef int (*cmp_fn_t)(const void *restrict, const void *restrict);

static inline int compare_int(const void *restrict a, const void *restrict b) { //compare_int is provided as an example
    const int arg1 = *(const int*)a;
    const int arg2 = *(const int*)b;
    return (arg1 > arg2) - (arg1 < arg2);
}

typedef long long (*subtract_fn_t)(const void *restrict, const void *restrict);

static inline long long subtract_int(const void *restrict a, const void *restrict b) { //subtract_int is provided as an example // a > b if positive answer is expected -> a - b
    return (long long)((*(const int *restrict)a - *(const int *restrict)b));
}

static inline size_t interpolate(const void *restrict val, const void *restrict data, size_t sz, size_t left, size_t right, subtract_fn_t subtract) {
    long long fraction = subtract(val, (char *)data + (left * sz)) / subtract((const char *restrict)data + (right * sz), (char *)data + (left * sz));
    return (fraction >= 0) ? left + ((right - left) * (size_t)fraction) : (left - ((right -left) * (size_t)(-fraction)));
}

static inline void update_ib_search_bounds(const void *restrict val, const void *restrict data, size_t sz, size_t *interpolation, size_t *left, size_t *right, cmp_fn_t cmp) {
    const char *restrict dataBytes = (const char *restrict)data;
    size_t mid;
    if(cmp(val, dataBytes + (*interpolation) * sz) > 0) {
        (*interpolation)++;
        if(cmp(val, dataBytes + (*interpolation) * sz) <= 0) {
            *left = *right = *interpolation;
            return;
        }
        mid = (*interpolation + *right) / 2;
        *left = (cmp(val, dataBytes + (mid * sz)) <= 0) ? ((*right = mid), (*interpolation + 1)) : (mid + 1);        
    } else {
        size_t lookIdx = *interpolation - 1;
        if(*interpolation == *left || cmp(val, dataBytes + (lookIdx) * sz) > 0) {
            *left = *right = *interpolation;
            return;
        }
        mid = (*interpolation + *left) / 2;
        *right = (cmp(val, dataBytes + (mid * sz)) >= 0) ? ((*left = mid), lookIdx) : (mid - 1);
    }
}

static inline bool ibs_valIsInArray(const void *restrict val, const void *restrict data, size_t sz, size_t left, size_t data_len, size_t *idx, subtract_fn_t subtract, cmp_fn_t cmp) {
    if(data_len == 0 || cmp(val, (char *)data + (left * sz)) < 0) {
        *idx = left;
        return false;
    }
    size_t right = data_len - 1;
    if(cmp(val, (char *)data + (right * sz)) > 0) {
        *idx = data_len;
        return false;
    }
    while(left < right) {
        *idx = interpolate(val, data, sz, left, right, subtract);
        update_ib_search_bounds(val, data, sz, idx, &left, &right, cmp);
    }
    return cmp(val, (char *)data + (left * sz)) == 0;
}

static inline bool sq_valIsInArray(const void *restrict val, const void *restrict data, size_t sz, size_t left, size_t data_len, size_t *idx, cmp_fn_t cmp) {
    *idx = left;
    while (*idx < data_len && cmp(val, (char *)data + ((*idx) * sz)) > 0) {
        (*idx)++;
    }
    return (*idx < data_len) && (cmp(val, (char *)data + ((*idx) * sz)) == 0);
}

File test.c

#define TEST_1


#ifdef TEST_1
#include "array_search.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    
    int array1[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55};
    int array2[] = {1,10,15,30,400,401,402,600,620,640,650,700,701,702,705,2000,2005,3000,3200,3400,3500,3600,6000,6200,6500,6700,6800,6801,6803,8000,9001,9010,9100,9300,9500,9601,9602,9802,9900};
    int array3[] = {343, 1540, 1562, 2135, 2275, 2559, 3780, 4440, 4946, 6110, 6200, 7040, 7290, 7541, 7881, 9269};
    printf(
        "Arrays available:\n"
        "1)array1[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55}\n"
        "2)array2[] = {1,10,15,30,400,401,402,600,620,640,650,700,701,702,705,2000,2005,3000,3200,3400,3500,3600,6000,6200,6500,6700,6800,6801,6803,8000,9001,9010,9100,9300,9500,9601,9602,9802,9900}\n"
        "3)array3[] = {343, 1540, 1562, 2135, 2275, 2559, 3780, 4440, 4946, 6110, 6200, 7040, 7290, 7541, 7881, 9269};\n"
    );
    
    int number;
    clock_t start, end;
    double cpu_time_used;
    size_t array1_len = sizeof(array1) / sizeof(array1[0]);
    size_t array2_len = sizeof(array2) / sizeof(array2[0]);
    size_t array3_len = sizeof(array3) / sizeof(array3[0]);
    size_t idx;
    while(1) {
        printf("Enter array number:");
        scanf("%d", &number);
        int *p;
        size_t len;
        switch (number)
        {
        case 1:
            p = array1;
            len = array1_len;
            break;
        case 2:
            p = array2;
            len = array2_len;
            break;
        case 3:
            p = array3;
            len = array3_len;
            break;
        default:
            p = NULL;
            len = 0;
            exit(0);
            break;
        }
        printf("Enter a key to find in array %d: ", number);
        scanf("%d", &number);
        printf("You entered: %d\n", number);
        start = clock();
        bool b = ibs_valIsInArray(&number, p, sizeof(int), 0, len, &idx, subtract_int, compare_int);
        end = clock();
        if(b) {
            printf("Found at idx: ");
        } else {
            printf("Not found. idx is at :");
        }
        printf("%zu\n", idx);        
        cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
        printf("Time taken for IBS in array: %f seconds\n", cpu_time_used);
        start = clock();
        b = sq_valIsInArray(&number, p, sizeof(int), 0, len, &idx, compare_int);
        end = clock();
        if(b) {
            printf("Found at idx: ");
        } else {
            printf("Not found. idx is at :");
        }
        printf("%zu\n", idx);        
        cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
        printf("Time taken for SQ in array: %f seconds\n", cpu_time_used);
    }
    return 0;
}

#endif
\$\endgroup\$
7
  • \$\begingroup\$ #pragma once is not defined in standard C. \$\endgroup\$
    – chux
    Commented Feb 29 at 8:52
  • \$\begingroup\$ Why use long double all over? (long double)(right - left) size_t subtraction, though likely within the whole number portion of long double, is not specified to be so. Consider 64-bit size_t and 64-bit long double. OTOH, if you are looking for speed, consider using the narrowest FP that serves the purpose. \$\endgroup\$
    – chux
    Commented Feb 29 at 9:00
  • \$\begingroup\$ @chux-ReinstateMonica you are correct! I can change everything to int instead of long double. The problem was subtract(val, (char *)data + (left * sz) may return a negative value if that is ignored the whole search stops working. FP is not really required because at the end of the day it will be converted to size_t type for index. When I wrote it I thought rounding was necessary but after a few test I saw rounding is not needed. I will change it to int. Thank you for your comment. \$\endgroup\$
    – Pragmatic
    Commented Feb 29 at 9:16
  • \$\begingroup\$ int may be narrow than size_t - maybe something at least as wide as size_t? I suspect you may need an integer type 2x the width of size_t. Will review later. \$\endgroup\$
    – chux
    Commented Feb 29 at 9:28
  • \$\begingroup\$ Pragmatic, "size_t a - size_t b would return something ... which can be positive or negative ..." --> No, not when SIZE_MAX > INT_MAX (which is very common) as the difference is then an unsigned result. Note size_t is always an unsigned type. \$\endgroup\$
    – chux
    Commented Mar 1 at 5:12

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.