{ }codelper
string indexingjoining stringsP0144

Normalize a web address path

The first line gives the number of addresses n, followed by n path parts of web addresses, one per line. Clean up the path of each address and print the results one per line. If the address contains #, everything from the first # to the end is the fragment. If the part before the fragment contains ?, everything from the first ?
to the end of that part is the query. The remaining front part is the path. So a ?
inside the fragment is not a query, and / and .
inside the query or fragment are not part of the path. Cut the path at every /; call each resulting part a name, and process the names from the front as follows. Drop empty names (from "//" or from a / at the very start or end) and ".". ".." removes the last of the names kept so far.
If no name is kept, it does nothing. Every other name (including "...", "..a" and ".git") is kept as it is. The cleaned path is the kept names joined with / with a / in front ("/" if no name is kept). If the last name of the path (the part after its final /) is empty, "." or "..", the address points to a folder.
In that case, if at least one name is kept, add one / to the end of the cleaned path. Print the cleaned path followed by the query and the fragment exactly as they were.

Input
1 ≤ n ≤ 100.
Each address starts with /, has length 1 to 200 and consists only of lowercase English letters, digits and .
- _ / ?
# = &
Output
the n cleaned addresses, one per line
Example 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
Example 2 · stdin
5
/a/b#x/../y?z
/./.../..a/.
/a/b/..
/a/..
/
stdout
/a/b#x/../y?z
/.../..a/
/a/
/
/
main.py
Input (stdin)

When you run, these lines are fed to input()/stdin in order (the program doesn't stop to ask).

$ Ready

Open this on a computer to write and run code. On mobile you can read the problem and the solutions.

In other languagesShow solutions by language · 8 languages

There are five traps.
First, finding ?
in the whole string first takes the ?
inside the fragment of "/a/b#x/../y?z" as the start of a query, and then cleans x/../y of the fragment as if it were the path.
Find # first, and look for ?
only in the part before it.
Finding the first position is Python find (-1 if missing; index raises), JS/TS/Java indexOf, C# IndexOf, C strchr (NULL if missing), C++ find (npos if missing), Craykon IndexOf (-1 if missing).
Second, splitting the whole string without removing the query and fragment treats the .. in "?next=/../c" as part of the path.
Third, meeting .. at the top.
Python list.pop() raises IndexError on an empty list, Java remove(size() - 1) throws IndexOutOfBoundsException, and pop_back() on an empty C++ vector is undefined behavior, so check for empty first.
JS/TS pop() just returns undefined on an empty array, so no check is needed.
Fourth, only checking whether a name starts with a dot also removes "..." or ".git"; compare the whole name with . and ...
Fifth, always dropping the trailing / turns "/a/c/" into "/a/c".
Checking only whether the path ends with / is not enough either: "/a/b/.." ends in .. and still points to a folder, so it becomes "/a/".
Splitting treats empty names differently: Python split("/"), JS/TS split("/") and Craykon Split("/") keep them, Java split("/") drops only the trailing ones, and C# Split('/', StringSplitOptions.RemoveEmptyEntries) and C strtok drop them all.
So in Java, C# and C the end of the split result misses the empty name of "/a/"; take the part after the last / instead (lastIndexOf('/'), C strrchr, C++ rfind).
Joining is Python "/".join(stack), JS/TS stack.join("/"), Java String.join("/", stack), C# string.Join("/", stack), Craykon stack.Join("/"); C and C++ have none, so write the names with a / before each one except the first.

Pythonmain.py · 26 lines
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 lines
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 lines
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 lines
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 lines
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 lines
#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 lines
#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 lines
n = Console.ReadLine().ToNumber();for (i = 0; i < n; i++){    Console.WriteLine(normalize(Console.ReadLine()));} normalize(url){    // 조각(#)을 먼저 떼야 한다: 조각 안의 ?는 질의가 아니다    rest = url;    frag = "";    h = url.IndexOf("#");    if (h != -1)    {        rest = url.Substring(0, h);        frag = url.Substring(h);    }    path = rest;    query = "";    q = rest.IndexOf("?");    if (q != -1)    {        path = rest.Substring(0, q);        query = rest.Substring(q);    }    segs = path.Split("/");    stack = [];    foreach (seg in segs)    {        if (seg == "" || seg == ".")        {            continue;        }        if (seg == "..")        {            if (stack.Count > 0)            {                stack.RemoveAt(stack.Count - 1);            }        }        else        {            stack.Add(seg);        }    }    clean = "/" + stack.Join("/");    // 마지막 이름이 빈 이름·.·.. 이면 폴더를 가리키니 끝의 / 를 남긴다    last = segs[segs.Count - 1];    if (stack.Count > 0 && (last == "" || last == "." || last == ".."))    {        clean += "/";    }    return clean + query + frag;}