{ }codelper
字符串下标连接字符串P0144

整理网址路径

第一行给出地址个数 n,接着每行给出一个网址的路径部分,共 n 个。
整理每个地址的路径,每行输出一个结果。
如果地址中有 #,从第一个 # 到末尾是片段。
如果去掉片段后的前面部分中有 ?,从第一个 ?
到这部分的末尾是查询。
剩下的最前面部分是路径。
所以片段里的 ?
不是查询,查询和片段里的 / 和 .
也不属于路径。
在路径中每个 / 的位置切开,切出的每一部分叫作名字,从前往后依次这样处理。
丢掉空名字(由 "//" 或开头、末尾的 / 产生的)和 "."。
".." 删除到目前为止保留下来的名字中的最后一个。
如果没有保留的名字,就什么也不做。
其他名字(包括 "..."、"..a"、".git")原样保留。
把保留的名字用 / 连接,前面加上 /,就是整理后的路径(没有保留的名字时是 "/")。
如果路径的最后一个名字(最后一个 / 后面的部分)是空名字、"." 或 "..",这个地址就指向文件夹。
这时如果至少保留了一个名字,就在整理后的路径末尾加一个 /。
在整理后的路径后面原样接上查询和片段,然后输出。

输入
1 ≤ n ≤ 100。
地址以 / 开头,长度 1 到 200,只由英文小写字母、数字以及 .
- _ / ?
# = & 组成
输出
整理后的 n 个地址,每行一个
样例 1 · stdin
4
/a/./b/../c/
/../x//y
/docs/v1/../v2/index.html?lang=ko#top
/a/b?next=/../c#x?y
stdout
/a/c/
/x/y
/docs/v2/index.html?lang=ko#top
/a/b?next=/../c#x?y
样例 2 · stdin
5
/a/b#x/../y?z
/./.../..a/.
/a/b/..
/a/..
/
stdout
/a/b#x/../y?z
/.../..a/
/a/
/
/
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

陷阱有五个。
第一,如果先在整个字符串里找 ?,"/a/b#x/../y?z" 中片段里的 ?
会被当成查询的开头,连片段里的 x/../y 也被当作路径整理掉。
必须先找 #,再只在它前面的部分里找 ?。
查找第一次出现的位置:Python find(没有则 -1,index 会抛异常),JS·TS·Java indexOf,C# IndexOf,C strchr(没有则 NULL),C++ find(没有则 npos),Craykon 查找位置(没有则 -1)。
第二,不去掉查询和片段就整体分割,"?next=/../c" 里的 .. 也会被当作路径处理。
第三,在最顶层遇到 .. 的情况。
Python list.pop() 对空列表抛出 IndexError,Java remove(size() - 1) 抛出 IndexOutOfBoundsException,C++ 空 vector 的 pop_back() 是未定义行为,所以要先检查是否为空。
JS·TS 的 pop() 对空数组只是返回 undefined,不检查也可以。
第四,只看是否以点开头,会把 "..." 或 ".git" 也删掉;要看整个名字是否等于 . 或 ..。
第五,总是去掉末尾的 / 会把 "/a/c/" 变成 "/a/c"。
只看路径是否以 / 结尾也不够:"/a/b/.." 以 .. 结尾,同样指向文件夹,所以是 "/a/"。
分割时各语言对空名字的处理不同:Python split("/")、JS·TS split("/")、Craykon 分割("/") 保留空名字,Java split("/") 只丢掉末尾的空名字,C# Split('/', StringSplitOptions.RemoveEmptyEntries) 和 C strtok 全部丢掉。
所以在 Java、C#、C 里看分割结果的最后一项会漏掉 "/a/" 的空名字;要截取最后一个 / 后面的部分来看(lastIndexOf('/')、C strrchr、C++ rfind)。
连接用 Python "/".join(stack),JS·TS stack.join("/"),Java String.join("/", stack),C# string.Join("/", stack),Craykon stack.连接("/");C·C++ 没有,就除了第一个名字外,每个名字前加 / 依次写出。

Pythonmain.py · 26 行
def normalize(url):    # 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다    h = url.find("#")    rest, frag = (url, "") if h == -1 else (url[:h], url[h:])    q = rest.find("?")    path, query = (rest, "") if q == -1 else (rest[:q], rest[q:])    segs = path.split("/")    stack = []    for seg in segs:        if seg == "" or seg == ".":            continue        if seg == "..":            if stack:  # 맨 위에서는 더 올라가지 않는다                stack.pop()        else:            stack.append(seg)    clean = "/" + "/".join(stack)    # 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다    if stack and segs[-1] in ("", ".", ".."):        clean += "/"    return clean + query + frag  n = int(input())for _ in range(n):    print(normalize(input()))
JavaScriptmain.js · 25 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]); function normalize(url) {  // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다  const h = url.indexOf("#");  const rest = h === -1 ? url : url.slice(0, h);  const frag = h === -1 ? "" : url.slice(h);  const q = rest.indexOf("?");  const path = q === -1 ? rest : rest.slice(0, q);  const query = q === -1 ? "" : rest.slice(q);  const stack = [];  for (const seg of path.split("/")) {    if (seg === "" || seg === ".") continue;    if (seg === "..") stack.pop(); // 빈 배열의 pop()은 오류 없이 undefined    else stack.push(seg);  }  let clean = "/" + stack.join("/");  // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다  const last = path.slice(path.lastIndexOf("/") + 1);  if (stack.length > 0 && (last === "" || last === "." || last === "..")) clean += "/";  return clean + query + frag;} console.log(lines.slice(1, 1 + n).map(normalize).join("\n"));
TypeScriptmain.ts · 26 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const n: number = Number(lines[0]); function normalize(url: string): string {  // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다  const h: number = url.indexOf("#");  const rest: string = h === -1 ? url : url.slice(0, h);  const frag: string = h === -1 ? "" : url.slice(h);  const q: number = rest.indexOf("?");  const path: string = q === -1 ? rest : rest.slice(0, q);  const query: string = q === -1 ? "" : rest.slice(q);  const stack: string[] = [];  for (const seg of path.split("/")) {    if (seg === "" || seg === ".") continue;    if (seg === "..") stack.pop(); // 빈 배열의 pop()은 오류 없이 undefined    else stack.push(seg);  }  let clean: string = "/" + stack.join("/");  // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다  const last: string = path.slice(path.lastIndexOf("/") + 1);  if (stack.length > 0 && (last === "" || last === "." || last === "..")) clean += "/";  return clean + query + frag;} console.log(lines.slice(1, 1 + n).map(normalize).join("\n"));
JavaMain.java · 39 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayList;import java.util.List; public class Main {    static String normalize(String url) {        // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다        int h = url.indexOf('#');        String rest = h == -1 ? url : url.substring(0, h);        String frag = h == -1 ? "" : url.substring(h);        int q = rest.indexOf('?');        String path = q == -1 ? rest : rest.substring(0, q);        String query = q == -1 ? "" : rest.substring(q);        List<String> stack = new ArrayList<>();        for (String seg : path.split("/")) {            if (seg.isEmpty() || seg.equals(".")) continue;            if (seg.equals("..")) {                if (!stack.isEmpty()) stack.remove(stack.size() - 1);            } else {                stack.add(seg);            }        }        String clean = "/" + String.join("/", stack);        // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다        String last = path.substring(path.lastIndexOf('/') + 1);        if (!stack.isEmpty() && (last.isEmpty() || last.equals(".") || last.equals(".."))) clean += "/";        return clean + query + frag;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) out.append(normalize(in.readLine())).append('\n');        System.out.print(out);    }}
C#Main.cs · 37 行
using System;using System.Collections.Generic; class Program{    static string Normalize(string url)    {        // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다        int h = url.IndexOf('#');        string rest = h == -1 ? url : url.Substring(0, h);        string frag = h == -1 ? "" : url.Substring(h);        int q = rest.IndexOf('?');        string path = q == -1 ? rest : rest.Substring(0, q);        string query = q == -1 ? "" : rest.Substring(q);        var stack = new List<string>();        foreach (string seg in path.Split('/', StringSplitOptions.RemoveEmptyEntries))        {            if (seg == ".") continue;            if (seg == "..")            {                if (stack.Count > 0) stack.RemoveAt(stack.Count - 1);            }            else stack.Add(seg);        }        string clean = "/" + string.Join("/", stack);        // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다        string last = path.Substring(path.LastIndexOf('/') + 1);        if (stack.Count > 0 && (last == "" || last == "." || last == "..")) clean += "/";        return clean + query + frag;    }     static void Main()    {        int n = int.Parse(Console.ReadLine());        for (int i = 0; i < n; i++) Console.WriteLine(Normalize(Console.ReadLine()));    }}
C++main.cpp · 41 行
#include <iostream>#include <sstream>#include <string>#include <vector> std::string normalize(const std::string& url) {    // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다    size_t h = url.find('#');    std::string rest = url.substr(0, h);  // h가 npos면 끝까지    std::string frag = h == std::string::npos ? "" : url.substr(h);    size_t q = rest.find('?');    std::string path = rest.substr(0, q);    std::string query = q == std::string::npos ? "" : rest.substr(q);    std::vector<std::string> stack;    std::stringstream ss(path);    std::string seg;    while (std::getline(ss, seg, '/')) {        if (seg.empty() || seg == ".") continue;        if (seg == "..") {            if (!stack.empty()) stack.pop_back();  // 빈 vector의 pop_back은 정의되지 않은 동작        } else {            stack.push_back(seg);        }    }    std::string out = "/";    for (size_t k = 0; k < stack.size(); k++) {        if (k > 0) out += '/';        out += stack[k];    }    // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다    std::string last = path.substr(path.rfind('/') + 1);    if (!stack.empty() && (last.empty() || last == "." || last == "..")) out += '/';    return out + query + frag;} int main() {    std::string line;    std::getline(std::cin, line);    int n = std::stoi(line);    for (int i = 0; i < n && std::getline(std::cin, line); i++) std::cout << normalize(line) << '\n';}
Cmain.c · 42 行
#include <stdio.h>#include <string.h> int main(void) {    int n;    char url[256];    if (!fgets(url, sizeof url, stdin)) return 0;    sscanf(url, "%d", &n);    for (int i = 0; i < n; i++) {        if (!fgets(url, sizeof url, stdin)) break;        url[strcspn(url, "\n")] = '\0';        /* 조각(#)을 먼저 찾아야 한다: 조각 안의 ?는 질의가 아니다 */        char *h = strchr(url, '#');        char frag[256] = "";        if (h) { strcpy(frag, h); *h = '\0'; }        char *q = strchr(url, '?');        char query[256] = "";        if (q) { strcpy(query, q); *q = '\0'; }        /* 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리킨다(strtok이 url을 바꾸기 전에 본다) */        const char *last = strrchr(url, '/') + 1;        int dirLike = strcmp(last, "") == 0 || strcmp(last, ".") == 0 || strcmp(last, "..") == 0;        /* 이제 url에는 경로만 남았다. strtok은 빈 조각("//")을 저절로 건너뛴다 */        char *stack[128];        int top = 0;        for (char *seg = strtok(url, "/"); seg; seg = strtok(NULL, "/")) {            if (strcmp(seg, ".") == 0) continue;            if (strcmp(seg, "..") == 0) {                if (top > 0) top--;            } else {                stack[top++] = seg;            }        }        char out[512] = "/";        for (int k = 0; k < top; k++) {            if (k > 0) strcat(out, "/");            strcat(out, stack[k]);        }        if (top > 0 && dirLike) strcat(out, "/");        printf("%s%s%s\n", out, query, frag);    }    return 0;}
Craykonmain.cray · 54 行
n = 控制台.读行().转数字();循环 (i = 0; i < n; i++){    控制台.输出(normalize(控制台.读行()));} normalize(url){    // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다    rest = url;    frag = "";    h = url.查找位置("#");    如果 (h != -1)    {        rest = url.截取(0, h);        frag = url.截取(h);    }    path = rest;    query = "";    q = rest.查找位置("?");    如果 (q != -1)    {        path = rest.截取(0, q);        query = rest.截取(q);    }    segs = path.分割("/");    stack = [];    遍历 (seg 在 segs)    {        如果 (seg == "" || seg == ".")        {            继续;        }        如果 (seg == "..")        {            如果 (stack.数量 > 0)            {                stack.按位置移除(stack.数量 - 1);            }        }        否则        {            stack.添加(seg);        }    }    clean = "/" + stack.连接("/");    // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다    last = segs[segs.数量 - 1];    如果 (stack.数量 > 0 && (last == "" || last == "." || last == ".."))    {        clean += "/";    }    返回 clean + query + frag;}