{ }codelper
문자열 만들어 가기글자 코드P0290

검색 조건을 붙여 링크 완성하기

검색 화면에서 고른 조건들을 기본 링크 뒤에 붙여 링크 한 줄을 완성하려고 한다. 첫 줄에 기본 링크 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!
stdout
https://shop.example/search?q=red%20%20shoes%20%26%20socks&sort=price_asc&tag=50%25%2Foff%21
예시 2 · stdin
/list 2
page
lang
stdout
/list
main.py
입력(stdin)

실행하면 이 줄들이 차례로 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 quote base, 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]);}