Operasi CRUD sudah berfungsi, tetapi pengguna program sering perlu mencari produk berdasarkan nama yang tidak persis sama. Pengguna toko bisa mengetik “ber” untuk menemukan “Beras” dan “Beras Merah”. Pada bagian ini, kita tambahkan pencarian nama dengan substring case-insensitive dan dua algoritma pengurutan: by-name dan by-price.

Konsep #

Pencarian substring case-insensitive memerlukan dua langkah:

  1. Bandingkan setiap karakter pasangan string dengan tolower() agar abaikan besar/kecil huruf.
  2. Geser jendela pencarian satu karakter pada satu waktu di string utama (haystack).

Pengurutan kita implementasikan dengan bubble sort. Algoritma ini sederhana dan mudah dipahami, dengan kompleksitas O(n^2). Untuk 256 produk maksimum, performa bubble sort masih sangat memadai (sekitar 65 ribu operasi paling buruk).

Implementasi #

Fungsi helper untuk case-insensitive substring match:

c
/* Helper: case-insensitive substring match */
static int contains_ci(const char *haystack, const char *needle) {
    if (needle == NULL || needle[0] == '\0') {
        return 1; /* keyword kosong = match semua */
    }
    if (haystack == NULL) {
        return 0;
    }
    for (const char *h = haystack; *h != '\0'; h++) {
        const char *hi = h;
        const char *ni = needle;
        while (*hi != '\0' && *ni != '\0' &&
               tolower((unsigned char)*hi) == tolower((unsigned char)*ni)) {
            hi++;
            ni++;
        }
        if (*ni == '\0') {
            return 1;
        }
    }
    return 0;
}

Fungsi inventory_find_by_name:

c
int inventory_find_by_name(Inventory *inv, const char *keyword,
                           Product *out, int out_cap) {
    if (inv == NULL || out == NULL || out_cap <= 0) {
        return 0;
    }
    int found = 0;
    for (int i = 0; i < inv->count && found < out_cap; i++) {
        if (contains_ci(inv->items[i].name, keyword)) {
            out[found++] = inv->items[i];
        }
    }
    return found;
}

Fungsi sort dengan bubble sort dan swap helper:

c
static void swap_product(Product *a, Product *b) {
    Product tmp = *a;
    *a = *b;
    *b = tmp;
}

void inventory_sort_by_name(Inventory *inv) {
    if (inv == NULL || inv->count < 2) {
        return;
    }
    for (int i = 0; i < inv->count - 1; i++) {
        for (int j = 0; j < inv->count - i - 1; j++) {
            if (strcmp(inv->items[j].name,
                       inv->items[j + 1].name) > 0) {
                swap_product(&inv->items[j], &inv->items[j + 1]);
            }
        }
    }
}

void inventory_sort_by_price(Inventory *inv) {
    if (inv == NULL || inv->count < 2) {
        return;
    }
    for (int i = 0; i < inv->count - 1; i++) {
        for (int j = 0; j < inv->count - i - 1; j++) {
            if (inv->items[j].price > inv->items[j + 1].price) {
                swap_product(&inv->items[j], &inv->items[j + 1]);
            }
        }
    }
}

Verifikasi pencarian dan pengurutan:

c
/* demo_search.c - Uji coba pencarian dan sort */
#include <stdio.h>
#include "inventory.h"

int main(void) {
    Inventory inv;
    inventory_init(&inv);
    inventory_add(&inv, "Chiki", 5000.0, 10);
    inventory_add(&inv, "Apel", 8000.0, 5);
    inventory_add(&inv, "Bola", 3000.0, 7);
    inventory_add(&inv, "Beras Merah", 75000.0, 3);

    printf("Cari 'ber' (case-insensitive):\n");
    Product out[16];
    int n = inventory_find_by_name(&inv, "ber", out, 16);
    printf("Ditemukan %d produk:\n", n);
    for (int i = 0; i < n; i++) {
        product_print(&out[i]);
    }

    printf("\nSetelah sort by name:\n");
    inventory_sort_by_name(&inv);
    for (int i = 0; i < inv.count; i++) {
        product_print(&inv.items[i]);
    }

    printf("\nSetelah sort by price:\n");
    inventory_sort_by_price(&inv);
    for (int i = 0; i < inv.count; i++) {
        product_print(&inv.items[i]);
    }
    return 0;
}

Output yang Dihasilkan:

text
Cari 'ber' (case-insensitive):
Ditemukan 1 produk:
[000004] Beras Merah                      Rp  75000.00  stok=   3

Setelah sort by name:
[000002] Apel                              Rp   8000.00  stok=   5
[000004] Beras Merah                       Rp  75000.00  stok=   3
[000001] Chiki                             Rp   5000.00  stok=  10
[000003] Bola                              Rp   3000.00  stok=   7

Setelah sort by price:
[000003] Bola                              Rp   3000.00  stok=   7
[000001] Chiki                             Rp   5000.00  stok=  10
[000002] Apel                              Rp   8000.00  stok=   5
[000004] Beras Merah                       Rp  75000.00  stok=   3

Mari kita telaah baris-baris kode di atas:

  1. Baris 4-7 (contains_ci early return): Bila needle kosong, kembalikan 1 (match semua). Ini memudahkan UI: pengguna yang menekan Enter tanpa mengetik apa pun akan mendapatkan seluruh daftar.
  2. Baris 11-19 (inner loop): Untuk setiap posisi awal di haystack, kita coba match needle karakter per karakter. Loop berhenti saat salah satu string habis atau ketemu mismatch.
  3. Baris 21-23 (*ni == '\0'): Bila needle habis (semua karakter cocok), maka substring ditemukan.
  4. Baris 39-44 (inventory_find_by_name): Salin produk yang match ke buffer out hingga out_cap. Batas ini melindungi pemanggil dari buffer overflow bila hasil pencarian sangat banyak.
  5. Baris 56-65 (inventory_sort_by_name): Bubble sort membandingkan pasangan elemen berdekatan dan menukar bila urutan salah. Loop luar memastikan setiap iterasi menempatkan satu elemen terbesar di akhir, sehingga loop dalam bisa berhenti lebih awal.
  6. Baris 25-28 (swap_product): Tukar dua struct utuh dengan assignment. C11 mengizinkan assignment struct secara langsung, lebih cepat daripada memcpy manual.

Kode Lengkap #

Kode lengkap untuk fungsi pencarian dan sort ada di inventory.c. Fungsi swap_product dan contains_ci dibuat static karena hanya dipakai internal.

Pengujian #

Selain demo di atas, kita perlu memastikan edge case:

  • Pencarian dengan keyword kosong mengembalikan seluruh produk.
  • Pencarian tanpa match mengembalikan 0.
  • Sort pada array 0 atau 1 elemen tidak menyebabkan crash (cek guard count < 2).
  • Sort by name memakai strcmp (case-sensitive) — “Apel” lebih dulu dari “apel” karena ASCII ‘A’ < ‘a’. Bila ingin case-insensitive sort, ganti strcmp dengan strcasecmp (POSIX) atau implementasi sendiri.
bash
gcc -std=c11 -Wall -Wextra -o /tmp/demo_search demo_search.c \
    inventory.c product.c -lm
/tmp/demo_search