검색 화면에서 고른 조건들을 기본 링크 뒤에 붙여 링크 한 줄을 완성하려고 한다.
첫 줄에 기본 링크 base와 조건 줄의 수 n이 주어진다.
조건 한 줄은 '이름'만 있거나, '이름 값'처럼 이름과 값이 빈칸 하나로 띄워져 있다.
줄에서 처음 나오는 빈칸의 앞이 이름이고, 그 뒤 전부가 값이다.
값 안에는 빈칸이 더 있을 수 있고, 빈칸이 잇달아 나올 수도 있다. 값 안의 빈칸은 하나도 버리지 않는다.
이름만 있는 줄은 값이 없는 조건이므로 버린다.
남은 조건마다 이름, '=', 바꾼 값을 차례로 이어 붙여 조각을 만든다.
값은 한 글자씩 다음 규칙으로 바꾼다: 영어 대문자·소문자, 숫자, 그리고 - _ . ~ 네 글자는 그대로 두고, 그 밖의 글자는 '%' 뒤에 그 글자의 코드를 16진수 두 자리로 적은 것으로 바꾼다.
16진수의 A~F는 대문자로 쓴다. 예를 들어 빈칸(코드 32)은 %20, '/'(코드 47)는 %2F가 된다.
남은 조건이 하나라도 있으면 base 뒤에 '?'를 붙이고, 조각들을 주어진 차례대로 '&'로 이어 붙인다.
남은 조건이 없으면 base만 그대로 출력한다.
입력
첫 줄에 base와 n이 빈칸 하나로 띄워 주어진다(1 ≤ n ≤ 100). base는 빈칸이 없는 1~50글자 문자열이고, 바꾸지 않고 그대로 쓴다. 다음 n줄에 조건이 한 줄씩 주어진다. 이름은 영어 소문자 1~10글자다. 값은 1~100글자이고, 코드 32~126인 글자(빈칸, 영문자, 숫자, 기호)로만 되어 있으며, 빈칸으로 시작하거나 끝나지 않는다.
출력
완성한 링크 한 줄
예시 1 · stdin
https://shop.example/search 4
q red shoes & socks
page
sort price_asc
tag 50%/off!
실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).
$ 준비됐어요
코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.
다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이
문자열 조각을 모아 사이에만 구분 글자를 넣는 법과, 글자를 코드로 바꿔 16진수로 적는 법을 함께 쓰는 문제예요. 잇기: Python "&".join(조각들), JS·TS 조각들.join("&"), C# string.Join("&", 조각들), 크레이콘 조각들.잇기("&")는 사이에만 넣어 줘요. Java StringBuilder와 C++ std::string +=로 하나씩 붙일 때는 첫 조각 앞에 ?, 그다음부터 &를 직접 골라요. C는 이어 붙이지 않고 조각을 바로 출력해요. 16진수 두 자리: C printf("%%%02X", c), Java String.format("%%%02X", (int) c), C# ((int)c).ToString("X2"), Python f"%{ord(c):02X}", JS c.charCodeAt(0).toString(16).toUpperCase(), C++과 크레이콘은 "0123456789ABCDEF"에서 코드 / 16번째와 코드 % 16번째 글자를 골라요(크레이콘은 글자.코드(c)와 수학.몫(코드, 16)). 이 변환을 해 주는 표준 함수도 있지만 규칙이 조금씩 달라요. C# Uri.EscapeDataString(값)과 Python urllib.parse.quote(값, safe="")는 이 문제의 규칙과 같아요. 함정 여덟: ① JS encodeURIComponent는 ! ' ( ) * 다섯 글자를 그대로 둬요. ② Python quote는 기본이 safe="/"라서 /를 그대로 둬요. ③ Java URLEncoder.encode는 HTML 폼 규칙이라 빈칸을 +로 바꾸고, *는 그대로 두고, ~는 %7E로 바꿔요. ④ C의 %02x는 소문자예요. 대문자는 %02X예요. ⑤ 줄을 이름과 값으로 나눌 때 Java split(" ", 2), C# Split(' ', 2), Python split(" ", 1)은 나머지를 마지막 조각에 남기지만, JS split(" ", 2)는 앞의 두 조각만 남기고 나머지를 버려요. JS에서는 indexOf(" ")와 slice로 잘라요. ⑥ Python split()으로 낱말을 나눴다가 빈칸 하나로 다시 이으면 잇달아 나온 빈칸이 하나로 줄어요. ⑦ 쌍마다 앞에 &를 붙이면 ?&q=...처럼 첫 쌍 앞에도 &가 생기고, 남은 조건이 없는데 ?를 붙여도 틀려요. ⑧ 값이 없는 줄을 이름=으로 붙이면 안 돼요.
Pythonmain.py · 13 줄
from urllib.parse import quotebase, n = input().split()pairs = []for _ in range(int(n)): # 처음 나오는 빈칸에서만 자른다. 빈칸이 없으면 value는 빈 문자열이다 name, _, value = input().partition(" ") if value == "": continue # quote는 기본으로 "/"를 그대로 둔다. safe=""로 그것도 바꾸게 한다 pairs.append(name + "=" + quote(value, safe=""))# 조각을 목록에 모았다가 join으로 한 번에 잇는다: 사이에만 "&"가 들어간다print(base + ("?" + "&".join(pairs) if pairs else ""))
JavaScriptmain.js · 19 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [base, n] = lines[0].split(" ");// encodeURIComponent는 ! ' ( ) * 다섯 글자를 그대로 둔다. 그 다섯 글자는 따로 바꾼다function encode(value) { return encodeURIComponent(value).replace( /[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase() );}const pairs = [];for (let i = 1; i <= Number(n); i++) { const line = lines[i]; const at = line.indexOf(" "); // split(" ", 2)는 셋째 조각부터 버리므로 쓰지 않는다 if (at === -1) continue; pairs.push(line.slice(0, at) + "=" + encode(line.slice(at + 1)));}console.log(base + (pairs.length > 0 ? "?" + pairs.join("&") : ""));
TypeScriptmain.ts · 20 줄
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const [base, n] = lines[0].split(" ");// encodeURIComponent는 ! ' ( ) * 다섯 글자를 그대로 둔다. 그 다섯 글자는 따로 바꾼다function encode(value: string): string { return encodeURIComponent(value).replace( /[!'()*]/g, (c: string) => "%" + c.charCodeAt(0).toString(16).toUpperCase() );}const pairs: string[] = [];for (let i = 1; i <= Number(n); i++) { const line = lines[i]; const at = line.indexOf(" "); // split(" ", 2)는 셋째 조각부터 버리므로 쓰지 않는다 if (at === -1) continue; pairs.push(line.slice(0, at) + "=" + encode(line.slice(at + 1)));}console.log(base + (pairs.length > 0 ? "?" + pairs.join("&") : ""));
JavaMain.java · 36 줄
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;public class Main { // URLEncoder.encode는 빈칸을 +로 바꾸고 ~를 바꿔 버려서 규칙과 다르다. 그래서 직접 만든다 static void appendEncoded(StringBuilder out, String value) { for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); boolean keep = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~'; if (keep) { out.append(c); } else { out.append(String.format("%%%02X", (int) c)); // %% 는 % 한 글자, %02X는 대문자 16진수 두 자리 } } } public static void main(String[] args) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String[] first = in.readLine().split(" "); int n = Integer.parseInt(first[1]); StringBuilder out = new StringBuilder(first[0]); boolean any = false; for (int i = 0; i < n; i++) { String[] parts = in.readLine().split(" ", 2); // 많아야 두 조각: 둘째 조각에 나머지가 모두 남는다 if (parts.length < 2) continue; out.append(any ? '&' : '?'); // 첫 쌍 앞에는 ?, 그다음부터는 & any = true; out.append(parts[0]).append('='); appendEncoded(out, parts[1]); } System.out.println(out); }}
C#Main.cs · 20 줄
using System;using System.Collections.Generic;class Program{ static void Main() { string[] first = Console.ReadLine().Split(' '); int n = int.Parse(first[1]); var pairs = new List<string>(); for (int i = 0; i < n; i++) { string[] parts = Console.ReadLine().Split(' ', 2); // 많아야 두 조각: 둘째 조각에 나머지가 모두 남는다 if (parts.Length < 2) continue; // Uri.EscapeDataString은 영문자·숫자와 - _ . ~ 만 남기고 %XX(대문자)로 바꾼다 pairs.Add(parts[0] + "=" + Uri.EscapeDataString(parts[1])); } Console.WriteLine(first[0] + (pairs.Count > 0 ? "?" + string.Join("&", pairs) : "")); }}
C++main.cpp · 36 줄
#include <iostream>#include <string>int main() { std::string base; int n; std::cin >> base >> n; std::string line; std::getline(std::cin, line); // 첫 줄의 나머지(줄바꿈)를 넘긴다 const std::string hex = "0123456789ABCDEF"; std::string out = base; // std::string은 += 로 뒤에 이어 붙여도 빠르다 bool any = false; for (int i = 0; i < n; i++) { std::getline(std::cin, line); auto at = line.find(' '); // 처음 나오는 빈칸 if (at == std::string::npos) continue; out += any ? '&' : '?'; any = true; out += line.substr(0, at); out += '='; for (std::size_t k = at + 1; k < line.size(); k++) { char c = line[k]; bool keep = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~'; if (keep) { out += c; } else { out += '%'; out += hex[c / 16]; // 16진수 윗자리 out += hex[c % 16]; // 16진수 아랫자리 } } } std::cout << out << '\n';}
Cmain.c · 30 줄
#include <ctype.h>#include <stdio.h>#include <string.h>int main(void) { char base[64]; int n; scanf("%63s %d", base, &n); getchar(); /* 첫 줄 끝의 줄바꿈을 넘긴다 */ fputs(base, stdout); int any = 0; char line[160]; for (int i = 0; i < n; i++) { if (!fgets(line, sizeof line, stdin)) break; line[strcspn(line, "\n")] = '\0'; char *space = strchr(line, ' '); /* 처음 나오는 빈칸 */ if (space == NULL) continue; *space = '\0'; /* 그 자리에서 이름과 값을 끊는다 */ /* C에서는 문자열을 이어 붙이는 대신 조각을 바로바로 출력한다 */ printf("%c%s=", any ? '&' : '?', line); any = 1; for (const char *p = space + 1; *p != '\0'; p++) { unsigned char c = (unsigned char)*p; if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') putchar(c); else printf("%%%02X", (unsigned)c); /* %X는 대문자, %x는 소문자 16진수 */ } } putchar('\n'); return 0;}
Craykonmain.cray · 45 줄
십육진수 = "0123456789ABCDEF";// 값의 글자를 하나씩 보고, 그대로 둘 글자가 아니면 %와 16진수 두 자리로 바꾼다바꾸기값(값){ 결과 = ""; 각각 (c 에서 값) { 그대로 = c >= "A" && c <= "Z" || c >= "a" && c <= "z" || c >= "0" && c <= "9" || c == "-" || c == "_" || c == "." || c == "~"; 만약 (그대로) { 결과 = 결과 + c; } 아니면 { 코드 = 글자.코드(c); 결과 = 결과 + "%" + 십육진수[수학.몫(코드, 16)] + 십육진수[코드 % 16]; } } 돌려주기 결과;}첫줄 = 콘솔.줄읽기().나누기(" ");n = 첫줄[1].수로변환();쌍들 = [];반복 (i = 0; i < n; i++){ 줄 = 콘솔.줄읽기(); 자리 = 줄.자리찾기(" "); // 처음 나오는 빈칸, 없으면 -1 만약 (자리 == -1) { 건너뛰기; } 이름 = 줄.부분문자열(0, 자리); 값 = 줄.부분문자열(자리 + 1, 줄.길이 - 자리 - 1); 쌍들.넣기(이름 + "=" + 바꾸기값(값));}만약 (쌍들.개수 > 0){ 콘솔.쓰기(첫줄[0] + "?" + 쌍들.잇기("&"));}아니면{ 콘솔.쓰기(첫줄[0]);}