첫 줄에 주소 수 n이 주어지고, 이어서 웹 주소의 경로 부분이 한 줄에 하나씩 n개 주어진다.
주소마다 경로를 정리해 한 줄에 하나씩 출력하라.
주소에 # 가 있으면 처음 나오는 # 부터 끝까지가 조각이다.
조각을 뺀 앞부분에 ? 가 있으면 처음 나오는 ? 부터 그 앞부분의 끝까지가 질의다.
나머지 맨 앞부분이 경로다.
그래서 조각 안의 ? 는 질의가 아니고, 질의와 조각 안의 / 와 . 은 경로가 아니다.
경로를 / 가 있는 자리마다 끊어 나눈 부분 하나하나를 이름이라 하고, 앞에서부터 차례로 다음처럼 처리한다.
빈 이름("//"나 맨 앞·맨 끝의 / 때문에 생긴 것)과 "."은 버린다.
".."은 지금까지 남은 이름 중 마지막 하나를 지운다. 남은 이름이 없으면 아무 일도 하지 않는다.
그 밖의 이름("...", "..a", ".git" 포함)은 그대로 남긴다.
남은 이름들을 / 로 이어 맨 앞에 / 를 붙인 것이 정리한 경로다(남은 이름이 없으면 "/").
경로의 마지막 이름(맨 끝 / 뒤의 부분)이 빈 이름, ".", ".." 중 하나이면 그 주소는 폴더를 가리킨다. 이때 남은 이름이 하나 이상이면 정리한 경로 끝에 / 를 하나 붙인다.
정리한 경로 뒤에 질의와 조각을 원래 그대로 붙여 출력한다.
입력
1 ≤ n ≤ 100. 주소는 / 로 시작하고 길이 1 ~ 200이며, 영어 소문자, 숫자, 그리고 . - _ / ? # = & 로만 이루어진다
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이
함정이 다섯이에요. 첫째, ?를 문자열 전체에서 먼저 찾으면 "/a/b#x/../y?z"에서 조각 안의 ?를 질의의 시작으로 보고, 조각의 x/../y까지 경로로 정리해 버려요. #를 먼저 찾고, ?는 그 앞부분에서만 찾아야 해요. 처음 나오는 자리 찾기는 Python find(없으면 -1, index는 예외), JS·TS·Java indexOf, C# IndexOf, C strchr(없으면 NULL), C++ find(없으면 npos), 크레이콘 자리찾기(없으면 -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("/"), 크레이콘 나누기("/")는 빈 이름을 남기고, 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), 크레이콘 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 + fragn = 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;}