{ }codelper
部分文字列文字列の検索P0395

長いファイル名を真ん中で縮める

ファイル一覧の画面は名前を w 文字までしか表示できないので、長い名前は真ん中を "..." にして縮める。
長さが w 以下の名前はそのまま出力する。
長さが w より長い名前は、次のようにしてちょうど w 文字に縮める。
名前に '.' があれば最後の '.' から終わりまでが拡張子であり、'.' がなければ拡張子はない。
拡張子を除いた前の部分から残す文字数 r は、w から 3 と拡張子の長さを引いた値である。
r が 1 以上なら、前の部分の最初の ⌈r/2⌉ 文字、"..."、前の部分の最後の ⌊r/2⌋ 文字、拡張子を順につなげる。
r が 1 より小さければ拡張子を分けず、名前全体を前の部分とみなし、r を w − 3 として同じ方法で縮める。
名前を入力に現れた順に 1 行に 1 つずつ出力せよ。
最後の行には縮めた名前の数を出力せよ。

入力
最初の行に w と名前の数 n(4 ≤ w ≤ 100、1 ≤ n ≤ 1,000)。
続く n 行に名前が 1 つずつ与えられる。
名前は 1〜200 文字の表示可能な ASCII 文字からなり、途中に空白があることはあるが、空白で始まったり終わったりはしない
出力
名前ごとに 1 行、最後の行に縮めた名前の数
例 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()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

切り出す道具の約束が言語ごとに違います。
Python s[a:b]、JS・TS slice(a, b)、Java substring(a, b) は開始位置と終了位置を受け取り、C# Substring(a, 長さ)、C++ substr(a, 長さ)、Craykon 切り出す(a, 長さ) は開始位置と長さを受け取ります。
C は printf("%.*s", 長さ, 開始) で切れ端を直接出力します。
最後の '.' は Python rfind、JS・TS・Java lastIndexOf、C# LastIndexOf、C strrchr、C++ rfind で探し、Craykon では末尾から 1 文字ずつ見て探します。
落とし穴その一:後ろの切れ端を負の位置で切ると、残す文字が 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:] で切ると最後の 1 文字が拡張子になり、例の Makefile_for_release が Mak...ease になります。
落とし穴その三:拡張子は最後の '.' からです。
Python find で最初の '.' から切ると、例の archive.tar.gz が ar...ar.gz ではなく arch....gz になります。
落とし穴その四:r から拡張子の長さを引かないと w 文字を超えます。
例の 2 番目の名前が 14 文字の quar...nal.txt になります。
落とし穴その五:r が 1 より小さいときの規則を抜かすと、例の a.tar.gz.backup が a.ta...kup ではなく ....backup になります。
落とし穴その六:前の切れ端が切り上げで、後ろの切れ端が切り捨てです。
逆にすると、例の 2 番目の名前が qu...l.txt ではなく q...al.txt になります。
落とし穴その七:長さがちょうど w の名前はそのままです。
< で比べると、例の report.txt が re...t.txt になります。
落とし穴その八:開始・終了と開始・長さを混ぜてはいけません。
Java substring(len - tail, tail) のように長さを渡すと、例の 2 番目の名前で開始が終了より大きくなって StringIndexOutOfBoundsException で止まり、Craykon 切り出す(len - tail, len) のように終了位置を渡すと、切る位置が文字列の外だというエラーで止まります。
落とし穴その九:名前に空白があることがあるので、1 行を丸ごと読みます。
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);