You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Rust中实现类似Node.js path.relative的相对路径功能

实现Rust版Node.js path.relative功能(非直接移植)

需求说明

要在Rust中实现类似Node.js path.relative(...)的功能,不直接移植Node.js原代码。已通过regex crate定义路径分隔符正则:

static PATH_SEPARATOR: Lazy<Regex> = lazy_regex!(r"[/\\]");

要求计算已解析所有..、.及//的绝对路径a到同类型绝对路径b的相对路径,例如调用relative_path("/a/b", "/a/c")应返回"../c"。

现有代码及问题

已实现路径解析函数resolve_single_path():

fn resolve_single_path(path: &str) -> String {
    let mut r = Vec::<String>::new();
    for p in PATH_SEPARATOR.split(path) {
        if p == "." {
            continue;
        } else if p == ".." {
            if !r.is_empty() {
                r.remove(r.len() - 1);
            }
        } else if !p.is_empty() {
            r.push(p.to_owned());
        }
    }
    r.join("/")
}

但自行编写的relative_path()函数存在逻辑错误,当from_parts与to_parts长度不同时输出不符合预期:

pub fn relative_path(from_path: &str, to_path: &str) -> String {
    let mut r = Vec::<String>::new();

    let from_parts: Vec<String> = PATH_SEPARATOR.split(resolve_single_path(from_path).as_ref()).map(|s| s.to_owned()).collect();
    let to_parts: Vec<String> = PATH_SEPARATOR.split(resolve_single_path(to_path).as_ref()).map(|s| s.to_owned()).collect();

    let l1 = from_parts.len();
    let l2 = to_parts.len();

    for i in 0..l1 {
        if from_parts[i] != to_parts[i] {
            r.push("..".to_owned());
        }
        r.push(to_parts[i].clone());
    }

    if l1 > l2 {
        for _i in l2..l1 {
            r.push("..".to_owned());
        }
    } else {
        for i in l1..l2 {
            r.push(to_parts[i].clone());
        }
    }

    let r = r.join("/");
    let r = r.trim();
    if r.is_empty() {".".to_owned() } else { r.to_owned() }
}

错误分析

原函数的核心逻辑错误:

  • 循环遍历from_parts时,无论路径段是否相等,都直接将to_parts[i]加入结果,导致公共路径段被错误重复添加。
  • 当from_parts比to_parts长时,直接追加..的逻辑未结合公共前缀判断,会生成多余的上级路径。
  • 未先找到两个路径的最长公共前缀,这是计算相对路径的核心前提。

正确实现逻辑

计算相对路径的正确步骤:

  • 解析两个输入路径为处理后的路径段数组(已过滤./../空段)。
  • 找到两个路径段数组的最长公共前缀长度:遍历数组直到出现第一个不相等的路径段,记录此时的索引。
  • 对from_parts中公共前缀之后的每个路径段,添加一个..到结果(表示向上一级)。
  • 将to_parts中公共前缀之后的所有路径段添加到结果(表示向下进入目标路径)。
  • 处理边界情况:若结果为空(两个路径完全相同),返回".";否则用/连接结果为字符串。

修正后的代码

pub fn relative_path(from_path: &str, to_path: &str) -> String {
    // 解析路径为处理后的路径段数组,过滤空段
    let from_parts: Vec<String> = PATH_SEPARATOR
        .split(resolve_single_path(from_path).as_ref())
        .filter(|s| !s.is_empty())
        .map(|s| s.to_owned())
        .collect();
    let to_parts: Vec<String> = PATH_SEPARATOR
        .split(resolve_single_path(to_path).as_ref())
        .filter(|s| !s.is_empty())
        .map(|s| s.to_owned())
        .collect();

    // 计算最长公共前缀的长度
    let common_prefix_len = from_parts
        .iter()
        .zip(to_parts.iter())
        .take_while(|(a, b)| a == b)
        .count();

    let mut result = Vec::new();

    // 从from路径向上走到公共前缀的父级
    for _ in common_prefix_len..from_parts.len() {
        result.push("..".to_owned());
    }

    // 从公共前缀向下走到to路径
    result.extend(to_parts[common_prefix_len..].to_vec());

    // 处理边界情况
    if result.is_empty() {
        ".".to_owned()
    } else {
        result.join("/")
    }
}

测试验证

  • 测试用例1:relative_path("/a/b", "/a/c") → 返回"../c",符合预期。
  • 测试用例2:relative_path("/a/b/c", "/a") → 返回"../.."。
  • 测试用例3:relative_path("/x/y", "/x/y") → 返回"."。
  • 测试用例4:relative_path("/a/b/c", "/a/d/e/f") → 返回"../../d/e/f"。

内容的提问来源于stack exchange,提问作者Hydroper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 10:00:27