笨辦法學C 練習42:棧和佇列

飛龍發表於2019-05-14

練習42:棧和佇列

原文:Exercise 42: Stacks and Queues

譯者:飛龍

到現在為止,你已經知道了大多數用於構建其它資料結構的資料結構。如果你擁有一些ListDArrayHashmapTree,你就能用他們構造出大多數其它的任何結構。你碰到的其它任何結構要麼可以用它們實現,要麼是它們的變體。如果不是的話,它可能是外來的資料結構,你可能不需要它。

StackQueue是非常簡單的資料結構,它們是List的變體。它們是List的弱化或者轉換形式,因為你只需要在List的一端放置元素。對於Stack,你只能能夠在一段壓入和彈出元素。而對於Queue,你只能夠在開頭壓入元素,並在末尾彈出(或者反過來)。

我能夠只通過C前處理器和兩個標頭檔案來實現這兩個資料結構。我的標頭檔案只有21行的長度,並且實現了所有StackQueue的操作,不帶有任何神奇的定義。

我將會向你展示單元測試,你需要實現標頭檔案來讓它們正常工作。你不能建立stack.cqueue.c實現檔案來通過測試,只能使用stack.hqueue.h來使測試執行。

#include "minunit.h"
#include <lcthw/stack.h>
#include <assert.h>

static Stack *stack = NULL;
char *tests[] = {"test1 data", "test2 data", "test3 data"};
#define NUM_TESTS 3


char *test_create()
{
    stack = Stack_create();
    mu_assert(stack != NULL, "Failed to create stack.");

    return NULL;
}

char *test_destroy()
{
    mu_assert(stack != NULL, "Failed to make stack #2");
    Stack_destroy(stack);

    return NULL;
}

char *test_push_pop()
{
    int i = 0;
    for(i = 0; i < NUM_TESTS; i++) {
        Stack_push(stack, tests[i]);
        mu_assert(Stack_peek(stack) == tests[i], "Wrong next value.");
    }

    mu_assert(Stack_count(stack) == NUM_TESTS, "Wrong count on push.");

    STACK_FOREACH(stack, cur) {
        debug("VAL: %s", (char *)cur->value);
    }

    for(i = NUM_TESTS - 1; i >= 0; i--) {
        char *val = Stack_pop(stack);
        mu_assert(val == tests[i], "Wrong value on pop.");
    }

    mu_assert(Stack_count(stack) == 0, "Wrong count after pop.");

    return NULL;
}

char *all_tests() {
    mu_suite_start();

    mu_run_test(test_create);
    mu_run_test(test_push_pop);
    mu_run_test(test_destroy);

    return NULL;
}

RUN_TESTS(all_tests);

之後是queue_tests.c,幾乎以相同的方式來使用Queue

#include "minunit.h"
#include <lcthw/queue.h>
#include <assert.h>

static Queue *queue = NULL;
char *tests[] = {"test1 data", "test2 data", "test3 data"};
#define NUM_TESTS 3


char *test_create()
{
    queue = Queue_create();
    mu_assert(queue != NULL, "Failed to create queue.");

    return NULL;
}

char *test_destroy()
{
    mu_assert(queue != NULL, "Failed to make queue #2");
    Queue_destroy(queue);

    return NULL;
}

char *test_send_recv()
{
    int i = 0;
    for(i = 0; i < NUM_TESTS; i++) {
        Queue_send(queue, tests[i]);
        mu_assert(Queue_peek(queue) == tests[0], "Wrong next value.");
    }

    mu_assert(Queue_count(queue) == NUM_TESTS, "Wrong count on send.");

    QUEUE_FOREACH(queue, cur) {
        debug("VAL: %s", (char *)cur->value);
    }

    for(i = 0; i < NUM_TESTS; i++) {
        char *val = Queue_recv(queue);
        mu_assert(val == tests[i], "Wrong value on recv.");
    }

    mu_assert(Queue_count(queue) == 0, "Wrong count after recv.");

    return NULL;
}

char *all_tests() {
    mu_suite_start();

    mu_run_test(test_create);
    mu_run_test(test_send_recv);
    mu_run_test(test_destroy);

    return NULL;
}

RUN_TESTS(all_tests);

你應該在不修改測試檔案的條件下,使單元測試能夠執行,並且它應該能夠通過valgrind而沒有任何記憶體錯誤。下面是當我直接執行stack_tests時它的樣子:

$ ./tests/stack_tests
DEBUG tests/stack_tests.c:60: ----- RUNNING: ./tests/stack_tests
----
RUNNING: ./tests/stack_tests
DEBUG tests/stack_tests.c:53: 
----- test_create
DEBUG tests/stack_tests.c:54: 
----- test_push_pop
DEBUG tests/stack_tests.c:37: VAL: test3 data
DEBUG tests/stack_tests.c:37: VAL: test2 data
DEBUG tests/stack_tests.c:37: VAL: test1 data
DEBUG tests/stack_tests.c:55: 
----- test_destroy
ALL TESTS PASSED
Tests run: 3
$

queue_test的輸出基本一樣,所以我在這裡就不展示了。

如何改進

你可以做到的唯一真正的改進,就是把所用的List換成DArrayQueue資料結構難以用DArray實現,因為它要同時處理兩端的節點。

完全在標頭檔案中來實現它們的缺點,是你並不能夠輕易地對它做效能調優。你需要使用這種技巧,建立一種以特定的方式使用List的“協議”。做效能調優時,如果你優化了List,這兩種資料結構都會有所改進。

附加題

  • 使用DArray代替List實現Stack,並保持單元測試不變。這意味著你需要建立你自己的STACK_FOREACH

相關文章