{ }codelper
拼接构建字符串字符编码P0290

附上搜索条件,拼出完整链接

要把搜索页面上选好的条件附在基础链接后面,拼出一行完整的链接。
第一行给出基础链接 base 和条件的行数 n。
每行条件要么只有“名称”,要么像“名称 值”这样,名称和值之间用一个空格隔开。
一行中第一个空格之前是名称,之后的全部内容是值。
值里面还可以有空格,也可以有连续的空格。
值里面的空格一个也不能丢。
只有名称的行是没有值的条件,要丢掉。
对剩下的每个条件,把名称、'='、转换后的值依次连接成一个片段。
值按下面的规则逐个字符转换:英文大写字母、小写字母、数字以及 - _ .
~ 四个字符保持不变,其他字符变成 '%' 后面跟着该字符编码的两位十六进制数。
十六进制的 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()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

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

这道题同时用到两件事:收集字符串片段并只在中间放分隔符,以及把字符变成编码再写成十六进制。
连接:Python "&".join(片段)、JS·TS 片段.join("&")、C# string.Join("&", 片段)、Craykon 片段.连接("&") 都只在中间放分隔符。
用 Java StringBuilder 或 C++ std::string += 逐个追加时,要自己决定第一个片段前放 ?、之后放 &。
C 不做拼接,直接把片段输出。
两位十六进制: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++ 和 Craykon 从 "0123456789ABCDEF" 中取第 编码 / 16 个和第 编码 % 16 个字符(Craykon 用 字符.编码(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]);}