C语言学习记录20240609

功能:把网站地图的网址列表转换成树结构。接受标准输入的网址,输出结果为 json 格式。
命名:根据功能将程序命名为 UrlsToTree,太长不方便输入,太短容易重名,最后命名为 u2tr。
用法:

cat sitemap.txt | u2tr > tree.json
u2tr < sitemap.txt > tree.json

树结构,用结构体构造节点,有子节点,声明结构体时有调用自身,形成递归结构,结构体采用别名命名时必须有结构体名。

节点数不确定,也就不能在声明时确定变量占用的内存大小,需要在堆中动态分配内存 malloc,动态分配内存必须有相应的手动回收操作 free。

网址从第三个斜线分割成根网址和路径两部分,去掉问号和井号后的参数和 fragment 。

需要避免重复创建节点。

需要转义网址中影响 json 输出结果的特殊符号,如双引号、反斜线。

最后还是参考《大话数据结构》160页树的孩子表示法,结合 AI 生成修改得到了下面的代码。

主要缺陷:没有考虑网址列表中不同根网址情况,如不同协议、不同域名、不同端口。
在这里插入图片描述

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

#define BUFFER_SIZE 1024

// 将网址分为根网址和路径两部分
typedef struct {
    char *part1;
    char *part2;
} URL;

void remove_query_fragment(char *url) {
    char *p = strpbrk(url, "?#");
    if (p != NULL) {
        *p = '\0';
    }
}

bool split_url(const char *url, URL *result) {
    char *url_copy = strdup(url);
    remove_query_fragment(url_copy);
    
    const char *current = url_copy;
    int parts_found = 0;
    for (int i = 0; i < 3; i++) {
        current = strstr(current, "/");
        if (current == NULL) {
            free(url_copy);
            return false;
        }
        current += 1; // Move to the end of the matched substring
        parts_found++;
    }
    if (parts_found == 3) {
        size_t part1_len = current - url_copy;
        result->part1 = (char *)malloc(part1_len);
        strncpy(result->part1, url_copy, part1_len - 1);
        result->part1[part1_len - 1] = '\0';
        result->part2 = strdup(current);
        free(url_copy);
        return true;
    }
    free(url_copy);
    return false;
}

char* escape_json_string(const char* str) {
    size_t len = strlen(str);
    char* escaped_str = malloc(2 * len + 1); // Worst case: every character needs escaping
    char* p = escaped_str;

    for (size_t i = 0; i < len; i++) {
        if (str[i] == '"' || str[i] == '\\') {
            *p++ = '\\';
        }
        *p++ = str[i];
    }
    *p = '\0';

    return escaped_str;
}

// 定义树节点的数据结构
typedef struct TreeNode {
    char *data;
    struct TreeNode **children;
    int childCount;
} TreeNode;

// 创建新节点
TreeNode *createNode(const char *data) {
    TreeNode *newNode = (TreeNode *)malloc(sizeof(TreeNode));
    newNode->data = escape_json_string(data); // Escape JSON special characters
    newNode->childCount = 0;
    newNode->children = NULL;
    return newNode;
}

// 销毁节点及其子树
void destroyTree(TreeNode *root) {
    if (root == NULL) {
        return;
    }
    for (int i = 0; i < root->childCount; i++) {
        destroyTree(root->children[i]);
    }
    free(root->data); // Free the data string
    free(root->children);
    free(root);
}

// 添加子节点到父节点
TreeNode *addChild(TreeNode *parent, TreeNode *child) {
    // 判断父节点中是否存在相同数据的子节点
    for (int i = 0; i < parent->childCount; i++) {
        if (strcmp(parent->children[i]->data, child->data) == 0) {
            free(child->data); // Avoid memory leak
            free(child);
            return parent->children[i];
        }
    }

    // 添加子节点到父节点
    parent->children = (TreeNode **)realloc(parent->children, (parent->childCount + 1) * sizeof(TreeNode *));
    parent->children[parent->childCount++] = child;
    return child;
}

// 将路径字符串转换为树结构
void convertPathToTree(TreeNode *root, char *path) {
    TreeNode *currentNode = root;

    char *token = strtok(path, "/");
    while (token != NULL) {
        TreeNode *newNode = createNode(token);
        currentNode = addChild(currentNode, newNode);
        token = strtok(NULL, "/");
    }
}

// 深度优先遍历树并输出为 JSON 格式
void depthFirstTraversal(TreeNode *node) {
    if (node == NULL) return;

    // 输出当前节点信息
    if (node->data != NULL) {
        printf("{\"data\": \"%s\", \"children\": [", node->data);
    }

    // 递归遍历子节点
    for (int i = 0; i < node->childCount; i++) {
        depthFirstTraversal(node->children[i]);
        if (i < node->childCount - 1) {
            printf(",");
        }
    }

    if (node->data != NULL) {
        printf("]}");
    }
}

int main() {
    char buffer[BUFFER_SIZE];
    TreeNode *root = NULL;

    // 动态读取网址列表
    while (fgets(buffer, BUFFER_SIZE, stdin)) {
        buffer[strcspn(buffer, "\n")] = '\0'; // 去掉换行符

        URL result;
        if (split_url(buffer, &result)) {
            if (root == NULL) {
                root = createNode(result.part1);
            }

            convertPathToTree(root, result.part2);

            free(result.part1);
            free(result.part2);
        } else {
            fprintf(stderr, "Failed to split URL: %s\n", buffer);
        }
    }

    if (root != NULL) {
        // 深度优先遍历并输出节点信息
        depthFirstTraversal(root);
        printf("\n");

        // 销毁树
        destroyTree(root);
    }

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值