LoginSignup
1
2

【C言語】あえて連想配列をフルスクラッチし、ホールスプール法(BM法)で文字列照合を実装した

Last updated at Posted at 2024-02-03

はじめに

学校の課題でホールスプール法を用いた文字列照合の実装が出ました。
ASCIIコードでチャチャっと実装しちゃうこともできるのですが、せっかくC言語なのでポインタ使ってゴリゴリに書きたいと思いました。そこで、連想配列をフルスクラッチで実装することで、あえてASCIIコードを使わないホールスプール法の実装に成功しました!嬉しかったので記録しておきます。

出力結果

"she sells sea shells on the sea shore"というテキストから"shell"という文字列を照合しました。また、照合の過程や連想配列の中身も確認できるように出力しました。

スクリーンショット 2024-02-03 17.02.01.png

コード全文

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define P_BUCKET_SIZE 10 // ハッシュ表の大きさ
#define S_BUCKET_SIZE 10 // ハッシュ表の大きさ

typedef char KEY; // キーの型

/* データ部(キー以外)のフィールド */
typedef struct data
{
    int value;
} DATA;

/* データ一つを表すレコード */
typedef struct record
{
    KEY key;
    DATA data;
    struct record *next;
} RECORD;

/* グローバル変数 */
RECORD *p_table[P_BUCKET_SIZE];
RECORD *s_table[S_BUCKET_SIZE];

/*ハッシュ表の初期化*/
void init()
{
    int i;
    for (i = 0; i < P_BUCKET_SIZE; i++)
        p_table[i] = NULL;
    for (i = 0; i < S_BUCKET_SIZE; i++)
        s_table[i] = NULL;
}

/*ハッシュ関数*/
int hash(KEY key)
{
    return key % 10;
}

/*key1とkey2が等しいかどうか。等しいなら1, 等しくないなら0を返す*/
int keyequal(KEY key1, KEY key2)
{
    return key1 == key2;
}

DATA *search(RECORD **table, KEY key)
{
    RECORD *p;
    int index;

    index = hash(key); // keyのハッシュ値を求める

    /*連結リストをたどる*/
    for (p = table[index]; p != NULL; p = p->next)
    {
        if (keyequal(key, p->key))
            return &p->data; // 発見=>DATAへのポインタを返す
    }
    return NULL; // 見つからない時にはNULLを返す
}

int add(RECORD **table, RECORD *record)
{
    RECORD *p;
    int index;

    if (search(table, record->key) != NULL)
    {
        return -1;
    }

    p = (RECORD *)malloc(sizeof(RECORD));
    if (p == NULL)
    {
        perror("out of memory.\n");
        exit(1); // プログラムを強制終了
    }

    /*ハッシュ関数でrecord.keyのハッシュ値を求める*/
    index = hash(record->key);

    p->key = record->key;
    p->data = record->data;

    /* table[index]に新しく作成したレコードを挿入する*/
    p->next = table[index];
    table[index] = p;

    return 0;
}

void p_createMap(RECORD **table, char P[], int length)
{
    printf("✅ p_createMap\n");
    int i = 0;

    for (i = 0; i < length; i++)
    {
        if (search(table, P[i]) == NULL)
        {
            DATA data;
            data.value = length - i - 1;

            RECORD record;
            record.key = P[i];
            record.data = data;
            add(table, &record);
        }
    }
}

void s_createMap(RECORD **table, char S[], int s_length, int p_length)
{
    int i = 0;
    printf("✅ s_createMap\n");
    for (i = 0; i < s_length; i++)
    {
        if (search(table, S[i]) == NULL)
        {
            DATA data;
            DATA *pData = search(p_table, S[i]);
            if (pData != NULL)
            {
                data.value = pData->value;
            }
            else
            {
                data.value = p_length;
            }
            RECORD record;
            record.key = S[i];
            record.data = data;
            add(table, &record);
        }
    }
}

int main()
{
    // テキスト
    char *T = "she sells sea shells on the sea shore";
    // パターン
    char *P = "shell";
    int n = strlen(T); // テキストの文字数
    int m = strlen(P); // パターンの文字数

    int i = 0; // テキストに対して照合を行う位置の先頭
    int j = 0; // パターンに対して照合を行う位置
    int times = 0;

    init();

    p_createMap(p_table, P, m);

    for (int i = 0; i < P_BUCKET_SIZE; i++)
    {
        RECORD *p = p_table[i];
        while (p != NULL)
        {
            printf("Key: '%c', Value: %d\n", p->key, p->data.value);
            p = p->next;
        }
    }

    s_createMap(s_table, T, n, m);

    for (int i = 0; i < S_BUCKET_SIZE; i++)
    {
        RECORD *p = s_table[i];
        while (p != NULL)
        {
            printf("Key: '%c', Value: %d\n", p->key, p->data.value);
            p = p->next;
        }
    }

    i = 0;
    while (i < n - m + 1)
    {
        printf("%d", i);
        if (i < 10)
            printf("  ");
        else
            printf(" ");
        j = m - 1;
        while (j >= 0 && T[i + j] == P[j])
        {
            if (j - (m - 1) != 0)
                printf("   ");
            printf("%s\n", T);
            for (int k = 0; k < i + j + 3; k++)
                printf(" ");
            printf("+\n");
            for (int k = 0; k < i + 3; k++)
                printf(" ");
            printf("%s\n\n", P);
            j--;
            times++;
        }
        if (j == -1)
        {
            printf("テキスト index=%dの位置にパターン%sがありました\n", i, P);
            printf("比較回数は%dでした\n", times);
            return 0;
        }
        else
        {
            if (j - (m - 1) != 0)
                printf("   ");
            printf("%s\n", T);
            for (int k = 0; k < i + j + 3; k++)
                printf(" ");
            printf("|\n");
            for (int k = 0; k < i + 3; k++)
                printf(" ");
            printf("%s\n\n", P);
            i += search(s_table, T[i + (m - 1)])->value;
            times++;
        }
    }
    printf("❎ パターンが見つかりませんでした\n");
    printf("比較回数は%dでした\n", times);

    return 0;
}

最後に

フルスクラッチは難しいからこそ、欲しい結果が得られた時の達成感が気持ちいですよね。これからもアルゴリズムの勉強頑張ります!

1
2
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2