{ }codelper
문자열 자르기문자열 찾기P0395

긴 파일 이름 가운데 줄이기

파일 목록 화면은 이름을 w글자까지만 보여 줄 수 있어서, 긴 이름은 가운데를 "..."으로 줄인다. 길이가 w 이하인 이름은 그대로 출력한다. 길이가 w보다 긴 이름은 다음과 같이 정확히 w글자로 줄인다. 이름에 '.'이 있으면 마지막 '.'부터 끝까지가 확장자이고, '.'이 없으면 확장자는 없다. 확장자를 뺀 앞부분에서 남길 글자 수 r은 w에서 3과 확장자의 길이를 뺀 값이다. r이 1 이상이면 앞부분의 처음 ⌈r/2⌉글자, "...", 앞부분의 마지막 ⌊r/2⌋글자, 확장자를 차례로 이어 붙인다. r이 1보다 작으면 확장자를 따로 두지 않고, 이름 전체를 앞부분으로 보고 r을 w − 3으로 하여 같은 방법으로 줄인다. 이름을 입력에 나온 차례대로 한 줄에 하나씩 출력하라. 마지막 줄에는 줄인 이름의 수를 출력하라.

입력
첫 줄에 w와 이름의 수 n(4 ≤ w ≤ 100, 1 ≤ n ≤ 1,000).
이어서 n줄에 이름이 하나씩 주어진다.
이름은 1~200글자이고 출력 가능한 ASCII 글자로 이루어지며, 가운데에 빈칸이 있을 수 있지만 빈칸으로 시작하거나 끝나지 않는다
출력
이름마다 한 줄, 마지막 줄에 줄인 이름의 수
예시 1 · stdin
10 7
report.txt
quarterly report final.txt
a.tar.gz.backup
Makefile_for_release
interface-font.woff2
archive.tar.gz
notes
stdout
report.txt
qu...l.txt
a.ta...kup
Make...ase
i....woff2
ar...ar.gz
notes
5
main.py
입력(stdin)

실행하면 이 줄들이 차례로 input()·표준 입력으로 들어가요(실행 중에 묻지 않아요).

$ 준비됐어요

코드를 입력해 풀려면 PC에서 열어 주세요. 모바일에서는 문제와 풀이를 볼 수 있어요.

다른 언어로는?언어별 풀이 보기 · 8개 언어 풀이

자르는 도구의 약속이 언어마다 달라요.
Python s[a:b], JS·TS slice(a, b), Java substring(a, b)는 시작과 끝 위치를 받고, C# Substring(a, 길이), C++ substr(a, 길이), 크레이콘 부분문자열(a, 길이)는 시작과 길이를 받아요.
C는 printf("%.*s", 길이, 시작)으로 조각을 바로 찍어요.
마지막 '.'은 Python rfind, JS·TS·Java lastIndexOf, C# LastIndexOf, C strrchr, C++ rfind로 찾고, 크레이콘은 끝에서부터 한 글자씩 보며 찾아요.
함정 하나: 뒤 조각을 음수 위치로 자르면 남길 글자가 0개일 때 틀려요.
Python stem[-0:]과 JS slice(-0)은 빈 문자열이 아니라 문자열 전체예요.
예시의 interface-font.woff2는 r이 1이라 뒤 조각이 0글자인데, 이렇게 자르면 i...interface-font.woff2가 나와요.
stem[len(stem) - tail:]처럼 앞에서 센 위치로 잘라요.
함정 둘: '.'이 없으면 Python rfind와 JS·TS·Java lastIndexOf, C# LastIndexOf는 -1을, C++ rfind는 npos를, C strrchr는 NULL을 돌려줘요.
확인하지 않고 Python name[-1:]로 자르면 마지막 한 글자가 확장자가 돼서 예시의 Makefile_for_release가 Mak...ease가 돼요.
함정 셋: 확장자는 마지막 '.'부터예요.
Python find로 첫 '.'에서 자르면 예시의 archive.tar.gz가 ar...ar.gz가 아니라 arch....gz가 돼요.
함정 넷: r에서 확장자 길이를 빼지 않으면 w글자를 넘어요.
예시 둘째 이름이 14글자인 quar...nal.txt가 돼요.
함정 다섯: r이 1보다 작을 때의 규칙을 빠뜨리면 예시의 a.tar.gz.backup이 a.ta...kup이 아니라 ....backup이 돼요.
함정 여섯: 앞 조각이 올림이고 뒤 조각이 내림이에요.
바꾸면 예시 둘째 이름이 qu...l.txt가 아니라 q...al.txt가 돼요.
함정 일곱: 길이가 정확히 w인 이름은 그대로예요.
<로 비교하면 예시의 report.txt가 re...t.txt가 돼요.
함정 여덟: 시작·끝과 시작·길이를 섞으면 안 돼요.
Java substring(len - tail, tail)처럼 길이를 넘기면 예시 둘째 이름에서 시작이 끝보다 커서 StringIndexOutOfBoundsException으로 멈추고, 크레이콘 부분문자열(len - tail, len)처럼 끝 위치를 넘기면 자르는 자리가 문자열 밖이라는 오류로 멈춰요.
함정 아홉: 이름에 빈칸이 있을 수 있어서 한 줄을 통째로 읽어요.
C++에서 std::cin >> w >> n 바로 다음에 getline을 부르면 첫 줄에 남은 줄 바꿈만 읽어서, 빈 줄이 먼저 나오고 마지막 이름을 놓쳐요.
첫 줄도 getline으로 읽어요.

Pythonmain.py · 24 줄
import sys lines = sys.stdin.read().split("\n")w, n = map(int, lines[0].split())out = []count = 0for name in lines[1:n + 1]:    if len(name) <= w:                 # 길이가 w 이하면 그대로        out.append(name)        continue    dot = name.rfind(".")              # 마지막 '.', 없으면 -1    ext = name[dot:] if dot >= 0 else ""    r = w - 3 - len(ext)               # 확장자 앞부분에서 남길 글자 수    if r < 1:                          # 자리가 없으면 확장자를 따로 두지 않는다        ext = ""        r = w - 3    stem = name[:len(name) - len(ext)]    head = (r + 1) // 2                # 앞 조각은 올림    tail = r // 2                      # 뒤 조각은 내림    # stem[-tail:]은 tail이 0일 때 전체가 되므로 앞에서 센 위치로 자른다    out.append(stem[:head] + "..." + stem[len(stem) - tail:] + ext)    count += 1out.append(str(count))print("\n".join(out))
JavaScriptmain.js · 26 줄
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [w, n] = lines[0].split(" ").map(Number);const out = [];let count = 0;for (let i = 1; i <= n; i++) {  const name = lines[i];  if (name.length <= w) { // 길이가 w 이하면 그대로    out.push(name);    continue;  }  const dot = name.lastIndexOf("."); // 마지막 '.', 없으면 -1  let ext = dot >= 0 ? name.slice(dot) : "";  let r = w - 3 - ext.length; // 확장자 앞부분에서 남길 글자 수  if (r < 1) { // 자리가 없으면 확장자를 따로 두지 않는다    ext = "";    r = w - 3;  }  const stem = name.slice(0, name.length - ext.length);  const head = Math.ceil(r / 2); // 앞 조각은 올림  const tail = Math.floor(r / 2); // 뒤 조각은 내림  // slice(-tail)은 tail이 0일 때 전체가 되므로 앞에서 센 위치로 자른다  out.push(stem.slice(0, head) + "..." + stem.slice(stem.length - tail) + ext);  count++;}out.push(String(count));console.log(out.join("\n"));
TypeScriptmain.ts · 28 줄
import { readFileSync } from "node:fs"; const lines: string[] = readFileSync(0, "utf8").split("\n");const [w, n] = lines[0].split(" ").map(Number);const out: string[] = [];let count = 0;for (let i = 1; i <= n; i++) {  const name = lines[i];  if (name.length <= w) { // 길이가 w 이하면 그대로    out.push(name);    continue;  }  const dot = name.lastIndexOf("."); // 마지막 '.', 없으면 -1  let ext = dot >= 0 ? name.slice(dot) : "";  let r = w - 3 - ext.length; // 확장자 앞부분에서 남길 글자 수  if (r < 1) { // 자리가 없으면 확장자를 따로 두지 않는다    ext = "";    r = w - 3;  }  const stem = name.slice(0, name.length - ext.length);  const head = Math.ceil(r / 2); // 앞 조각은 올림  const tail = Math.floor(r / 2); // 뒤 조각은 내림  // slice(-tail)은 tail이 0일 때 전체가 되므로 앞에서 센 위치로 자른다  out.push(stem.slice(0, head) + "..." + stem.slice(stem.length - tail) + ext);  count++;}out.push(String(count));console.log(out.join("\n"));
JavaMain.java · 37 줄
import java.io.BufferedReader;import java.io.InputStreamReader;import java.util.StringTokenizer; public class Main {    public static void main(String[] args) throws Exception {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        int w = Integer.parseInt(st.nextToken());        int n = Integer.parseInt(st.nextToken());        StringBuilder out = new StringBuilder();        int count = 0;        for (int i = 0; i < n; i++) {            String name = in.readLine();            if (name.length() <= w) {              // 길이가 w 이하면 그대로                out.append(name).append('\n');                continue;            }            int dot = name.lastIndexOf('.');       // 마지막 '.', 없으면 -1            String ext = dot >= 0 ? name.substring(dot) : "";            int r = w - 3 - ext.length();          // 확장자 앞부분에서 남길 글자 수            if (r < 1) {                           // 자리가 없으면 확장자를 따로 두지 않는다                ext = "";                r = w - 3;            }            String stem = name.substring(0, name.length() - ext.length());            int head = (r + 1) / 2;                // 앞 조각은 올림            int tail = r / 2;                      // 뒤 조각은 내림            // substring(시작, 끝)과 substring(시작)            out.append(stem.substring(0, head)).append("...")               .append(stem.substring(stem.length() - tail)).append(ext).append('\n');            count++;        }        out.append(count).append('\n');        System.out.print(out);    }}
C#Main.cs · 40 줄
using System;using System.Text; class Program{    static void Main()    {        string[] first = Console.ReadLine()!.Split(' ');        int w = int.Parse(first[0]);        int n = int.Parse(first[1]);        var sb = new StringBuilder();        int count = 0;        for (int i = 0; i < n; i++)        {            string name = Console.ReadLine()!;            if (name.Length <= w)                  // 길이가 w 이하면 그대로            {                sb.Append(name).Append('\n');                continue;            }            int dot = name.LastIndexOf('.');       // 마지막 '.', 없으면 -1            string ext = dot >= 0 ? name.Substring(dot) : "";            int r = w - 3 - ext.Length;            // 확장자 앞부분에서 남길 글자 수            if (r < 1)                             // 자리가 없으면 확장자를 따로 두지 않는다            {                ext = "";                r = w - 3;            }            string stem = name.Substring(0, name.Length - ext.Length);            int head = (r + 1) / 2;                // 앞 조각은 올림            int tail = r / 2;                      // 뒤 조각은 내림            // Substring(시작, 길이)와 Substring(시작)            sb.Append(stem.Substring(0, head)).Append("...")              .Append(stem.Substring(stem.Length - tail)).Append(ext).Append('\n');            count++;        }        sb.Append(count).Append('\n');        Console.Write(sb);    }}
C++main.cpp · 38 줄
#include <iostream>#include <sstream>#include <string> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    std::string line;    std::getline(std::cin, line);              // 첫 줄도 getline으로 읽어 줄 바꿈을 남기지 않는다    std::istringstream first(line);    int w, n;    first >> w >> n;    int count = 0;    for (int i = 0; i < n; i++) {        std::string name;        std::getline(std::cin, name);        int len = (int)name.size();        if (len <= w) {                        // 길이가 w 이하면 그대로            std::cout << name << '\n';            continue;        }        std::size_t dot = name.rfind('.');     // 마지막 '.', 없으면 npos        std::string ext = dot != std::string::npos ? name.substr(dot) : "";        int r = w - 3 - (int)ext.size();       // 확장자 앞부분에서 남길 글자 수        if (r < 1) {                           // 자리가 없으면 확장자를 따로 두지 않는다            ext = "";            r = w - 3;        }        std::string stem = name.substr(0, name.size() - ext.size());        int head = (r + 1) / 2;                // 앞 조각은 올림        int tail = r / 2;                      // 뒤 조각은 내림        // substr(시작, 길이)와 substr(시작)        std::cout << stem.substr(0, head) << "..." << stem.substr(stem.size() - tail) << ext << '\n';        count++;    }    std::cout << count << '\n';    return 0;}
Cmain.c · 32 줄
#include <stdio.h>#include <string.h> int main(void) {    static char name[256];    int w, n, count = 0;    if (!fgets(name, sizeof name, stdin) || sscanf(name, "%d %d", &w, &n) != 2) return 0;    for (int i = 0; i < n; i++) {        if (!fgets(name, sizeof name, stdin)) break;        int len = (int)strcspn(name, "\r\n");        name[len] = '\0';        if (len <= w) {                          /* 길이가 w 이하면 그대로 */            puts(name);            continue;        }        const char *dot = strrchr(name, '.');    /* 마지막 '.', 없으면 NULL */        int ext = dot ? (int)(name + len - dot) : 0;        int r = w - 3 - ext;                     /* 확장자 앞부분에서 남길 글자 수 */        if (r < 1) {                             /* 자리가 없으면 확장자를 따로 두지 않는다 */            ext = 0;            r = w - 3;        }        int stem = len - ext;        int head = (r + 1) / 2;                  /* 앞 조각은 올림 */        int tail = r / 2;                        /* 뒤 조각은 내림 */        /* %.*s는 길이만큼만 찍는다. 뒤 조각과 확장자는 이어져 있으니 끝까지 찍는다 */        printf("%.*s...%s\n", head, name, name + stem - tail);        count++;    }    printf("%d\n", count);    return 0;}
Craykonmain.cray · 36 줄
first = 콘솔.수들읽기();w = first[0];n = first[1];count = 0;반복 (i = 0; i < n; i++){    name = 콘솔.줄읽기();    만약 (name.길이 <= w)    {        콘솔.쓰기(name);        건너뛰기;    }    dot = name.길이 - 1; // 끝에서부터 마지막 '.'을 찾는다. 없으면 -1    동안 (dot >= 0 && name[dot] != ".")    {        dot -= 1;    }    ext = "";    만약 (dot >= 0)    {        ext = name.부분문자열(dot);    }    r = w - 3 - ext.길이; // 확장자 앞부분에서 남길 글자 수    만약 (r < 1)    {        ext = "";        r = w - 3;    }    stem = name.부분문자열(0, name.길이 - ext.길이);    tail = 수학.몫(r, 2); // 뒤 조각은 내림    head = r - tail; // 앞 조각은 올림    // 부분문자열(시작, 길이)와 부분문자열(시작)    콘솔.쓰기(stem.부분문자열(0, head) + "..." + stem.부분문자열(stem.길이 - tail) + ext);    count += 1;}콘솔.쓰기(count);