{ }codelper
n行の読み込み1行読み込み文字列の分割P0295

手紙の束を要約する

n 通の手紙が一つのファイルに続けてつながっています。
手紙一通は、ヘッダー行 1 行、本文 k 行、添付 a 行がこの順に続いたものです。
ヘッダー行は "名前 k a" の形です。
名前は差出人、k は本文の行数、a は添付の行数です。
本文の行は書かれたままの 1 行です。
英字、数字、空白、そして .
, !
?
だけからなり、長さは 0 文字から 80 文字までです。
本文の行は 1 文字もない空行のことがあり、空白で始まったり終わったりすることがあり、空白がいくつも続くことがあり、空白だけのこともあります。
本文の行がヘッダー行や数のように見えても本文です。
どこまでが本文かは k だけで決まります。
添付の行は "ファイル名 サイズ" の形です。
本文で単語とは、空白でない文字が続いた一かたまりのことです。
手紙ごとに次の四つを求めてください。
一つ目、単語が一つでもある本文の行の数。
二つ目、本文全体の単語の数。
三つ目、いちばん長い本文の行の長さ。
長さには前後の空白を含め、その行のすべての文字を数えます。
本文がなければ 0 です。
四つ目、添付のサイズの合計。
添付がなければ 0 です。
手紙が与えられた順に 1 行に一つずつ、名前と上の四つの数を空白一つで区切って出力してください。

入力
1 行目に n が与えられます。
続いて手紙 n 通が順に与えられます。
ヘッダー行の名前、k、a と、添付の行のファイル名、サイズは空白一つで区切られ、行の前後に空白はありません。
1 ≤ n ≤ 1,000、0 ≤ k ≤ 100、0 ≤ a ≤ 20、1 ≤ サイズ ≤ 1,000,000。
名前は英小文字 1〜10 文字、ファイル名は英小文字・数字・ピリオドからなる 1〜20 文字です。
入力は全部で 10,000 行以下です
出力
手紙ごとに 1 行ずつ n 行。
名前、単語のある本文の行数、単語数、いちばん長い本文の行の長さ、添付のサイズの合計
例 1 · stdin
3
kim 3 2
Hello there, team.

  see you at 9
plan.txt 1200
map.png 30500
lee 0 0
park 2 1
lee 0 0
7
notes.md 8
stdout
kim 2 7 18 31700
lee 0 0 0 0
park 2 4 7 8
例 2 · stdin
2
choi 3 0
a  b   c

ok
yun 1 1
12
photo.png 1000000
stdout
choi 2 4 8 0
yun 1 1 2 1000000
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

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

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

行数を先に知らせてその分だけ行を読ませる入力が二重になっています:n 通を回りながら、一通ごとに k 行、a 行を読みます。
入力を空白でまとめて分割してはいけません。
本文は行そのままが必要で、一つ目の例の lee 0 0 のようにヘッダー行そっくりの本文もあるので、数えながら読まないと区別できません。
1 行を丸ごと読む方法は Python の input()、Java の BufferedReader.readLine()、C# の Console.ReadLine()、C++ の getline(cin, line)、C の fgets、Craykon の コンソール.行読み() です。
JavaScript・TypeScript は入力全体を split("\n") で行の配列にして、添字を一つずつ進めます。
落とし穴一:数を読む方法と行を読む方法を混ぜると行がずれます。
C の scanf("%d")、C++ の cin >>、Java の Scanner の nextInt() は数の後ろの改行を残すので、すぐ次の fgets・getline・nextLine() が空行を読みます。
そこで数の入った行も 1 行として読んでから sscanf、istringstream、split で取り出します。
落とし穴二:空行も本文の 1 行です。
空行を取り除いて読むと、その後ろが 1 行ずつずれます。
落とし穴三:JavaScript で入力全体に trim() をかけると、最後の手紙の本文が空行で終わるときにその行が消えます。
落とし穴四:行の長さには前後の空白も入ります。
Python の strip()、JavaScript の trim()、Craykon の 空白除去() をしてから長さを測ると短くなります(一つ目の例の see you at 9 は 14 文字です)。
落とし穴五:単語を数えるとき空白一つで分割すると、空白が続くところで空文字列が出ます。
JavaScript の "a b".split(" ") は 3 個になり、C# の Split(' ') も同じなので StringSplitOptions.RemoveEmptyEntries を渡します。
Python の split() と Craykon の 分ける() は区切りを渡さなければ続いた空白を一つとみなし、単語だけを返します。
Java は StringTokenizer の countTokens()、C++ は istringstream から >> で取り出せた回数、C は空白の次に文字が来る場所を数えます。
落とし穴六:空白だけの行は空文字列ではありませんが、単語のない行です。
line != "" で比べると間違いで、単語数が 0 かどうかを見ます。
落とし穴七:C の fgets(line, 81, stdin) は 80 文字までしか読みません。
80 文字の行だと改行が残って次の fgets が空行を読むので、配列には改行と終端の '\0' の分まで必要です。

Pythonmain.py · 18 行
n = int(input())out = []for _ in range(n):    # 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a    name, k, a = input().split()    filled = words = longest = 0    for _ in range(int(k)):        line = input()  # 줄 끝의 줄바꿈만 떼고 앞뒤 빈칸은 그대로 둔다        count = len(line.split())  # split()은 빈칸이 이어져도 낱말만 돌려준다        if count > 0:            filled += 1        words += count        longest = max(longest, len(line))    total = 0    for _ in range(int(a)):        total += int(input().split()[1])    out.append(f"{name} {filled} {words} {longest} {total}")print("\n".join(out))
JavaScriptmain.js · 24 行
// 줄 단위로 읽어야 하니 빈칸으로 쪼개지 않는다. 통째로 trim()하지도 않는다: 끝의 빈 줄도 본문일 수 있다const lines = require("fs").readFileSync(0, "utf8").split("\n");let at = 0;const n = Number(lines[at++]);const out = [];for (let i = 0; i < n; i++) {  // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a  const [name, k, a] = lines[at++].split(" ");  let filled = 0;  let words = 0;  let longest = 0;  for (let j = 0; j < Number(k); j++) {    const line = lines[at++];    // split(" ")은 빈칸이 이어지면 빈 문자열을 돌려주니 걸러 낸다    const count = line.split(" ").filter((w) => w !== "").length;    if (count > 0) filled++;    words += count;    longest = Math.max(longest, line.length);  }  let total = 0;  for (let j = 0; j < Number(a); j++) total += Number(lines[at++].split(" ")[1]);  out.push(`${name} ${filled} ${words} ${longest} ${total}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 26 行
import { readFileSync } from "node:fs"; // 줄 단위로 읽어야 하니 빈칸으로 쪼개지 않는다. 통째로 trim()하지도 않는다: 끝의 빈 줄도 본문일 수 있다const lines: string[] = readFileSync(0, "utf8").split("\n");let at = 0;const n = Number(lines[at++]);const out: string[] = [];for (let i = 0; i < n; i++) {  // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a  const [name, k, a] = lines[at++].split(" ");  let filled = 0;  let words = 0;  let longest = 0;  for (let j = 0; j < Number(k); j++) {    const line: string = lines[at++];    // split(" ")은 빈칸이 이어지면 빈 문자열을 돌려주니 걸러 낸다    const count = line.split(" ").filter((w) => w !== "").length;    if (count > 0) filled++;    words += count;    longest = Math.max(longest, line.length);  }  let total = 0;  for (let j = 0; j < Number(a); j++) total += Number(lines[at++].split(" ")[1]);  out.push(`${name} ${filled} ${words} ${longest} ${total}`);}console.log(out.join("\n"));
JavaMain.java · 31 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        // readLine은 줄바꿈만 떼고 한 줄을 통째로 돌려준다: 앞뒤 빈칸도 빈 줄도 그대로다        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(br.readLine());        StringBuilder sb = new StringBuilder();        for (int i = 0; i < n; i++) {            // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a            String[] head = br.readLine().split(" ");            int k = Integer.parseInt(head[1]);            int a = Integer.parseInt(head[2]);            int filled = 0, words = 0, longest = 0;            for (int j = 0; j < k; j++) {                String line = br.readLine();                // StringTokenizer는 빈칸이 이어져도 낱말만 센다                int count = new StringTokenizer(line).countTokens();                if (count > 0) filled++;                words += count;                longest = Math.max(longest, line.length());            }            int total = 0;            for (int j = 0; j < a; j++) total += Integer.parseInt(br.readLine().split(" ")[1]);            sb.append(head[0]).append(' ').append(filled).append(' ').append(words).append(' ')              .append(longest).append(' ').append(total).append('\n');        }        System.out.print(sb);    }}
C#Main.cs · 32 行
using System;using System.Text; class Program{    static void Main()    {        // ReadLine은 줄바꿈만 떼고 한 줄을 통째로 돌려준다: 앞뒤 빈칸도 빈 줄도 그대로다        int n = int.Parse(Console.ReadLine()!);        var sb = new StringBuilder();        for (int i = 0; i < n; i++)        {            // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a            string[] head = Console.ReadLine()!.Split(' ');            int k = int.Parse(head[1]), a = int.Parse(head[2]);            int filled = 0, words = 0, longest = 0;            for (int j = 0; j < k; j++)            {                string line = Console.ReadLine()!;                // RemoveEmptyEntries가 없으면 빈칸이 이어질 때 빈 문자열이 낱말로 세어진다                int count = line.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;                if (count > 0) filled++;                words += count;                longest = Math.Max(longest, line.Length);            }            int total = 0;            for (int j = 0; j < a; j++) total += int.Parse(Console.ReadLine()!.Split(' ')[1]);            sb.Append($"{head[0]} {filled} {words} {longest} {total}\n");        }        Console.Write(sb);    }}
C++main.cpp · 45 行
#include <algorithm>#include <iostream>#include <sstream>#include <string>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    // 모든 줄을 getline으로 읽는다: cin >> 와 섞으면 >> 가 남긴 줄바꿈 때문에 getline이 빈 줄을 읽는다    string line;    getline(cin, line);    int n = stoi(line);    for (int i = 0; i < n; i++) {        // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a — 읽은 줄을 istringstream에 넣어 >> 로 꺼낸다        getline(cin, line);        istringstream head(line);        string name;        int k, a;        head >> name >> k >> a;        int filled = 0, words = 0, longest = 0;        for (int j = 0; j < k; j++) {            getline(cin, line);            // >> 는 빈칸이 이어져도 낱말만 하나씩 꺼낸다            istringstream body(line);            string word;            int count = 0;            while (body >> word) count++;            if (count > 0) filled++;            words += count;            longest = max(longest, (int)line.size());        }        int total = 0;        for (int j = 0; j < a; j++) {            getline(cin, line);            istringstream file(line);            string fileName;            int size;            file >> fileName >> size;            total += size;        }        cout << name << ' ' << filled << ' ' << words << ' ' << longest << ' ' << total << '\n';    }    return 0;}
Cmain.c · 39 行
#include <stdio.h>#include <string.h> int main(void) {    // 한 줄은 80자까지지만 줄바꿈과 끝 표시('\0')까지 들어갈 자리가 있어야 한다    char line[128];    int n;    // 수도 fgets로 한 줄을 통째로 읽고 sscanf로 꺼낸다: scanf("%d")는 줄바꿈을 남겨 다음 fgets가 빈 줄을 읽는다    if (!fgets(line, sizeof line, stdin)) return 0;    sscanf(line, "%d", &n);    for (int i = 0; i < n; i++) {        // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a        char name[11];        int k = 0, a = 0;        fgets(line, sizeof line, stdin);        sscanf(line, "%10s %d %d", name, &k, &a);        int filled = 0, words = 0, longest = 0;        for (int j = 0; j < k; j++) {            fgets(line, sizeof line, stdin);            int len = (int)strcspn(line, "\n"); // 줄바꿈 앞까지의 길이: 앞뒤 빈칸은 그대로 센다            // 빈칸 다음에 빈칸이 아닌 글자가 나올 때마다 낱말 하나가 시작된다            int count = 0;            for (int c = 0; c < len; c++)                if (line[c] != ' ' && (c == 0 || line[c - 1] == ' ')) count++;            if (count > 0) filled++;            words += count;            if (len > longest) longest = len;        }        int total = 0;        for (int j = 0; j < a; j++) {            int size = 0;            fgets(line, sizeof line, stdin);            sscanf(line, "%*s %d", &size); // %*s: 파일 이름은 읽고 버린다            total += size;        }        printf("%s %d %d %d %d\n", name, filled, words, longest, total);    }    return 0;}
Craykonmain.cray · 29 行
// 줄읽기는 줄바꿈만 떼고 한 줄을 통째로 돌려준다: 앞뒤 빈칸도 빈 줄도 그대로다n = コンソール.行読み().数に();繰り返す (i = 0; i < n; i++){    // 머리 줄: 이름, 본문 줄 수 k, 첨부 줄 수 a    head = コンソール.行読み().分ける();    k = head[1].数に();    a = head[2].数に();    filled = 0;    words = 0;    longest = 0;    繰り返す (j = 0; j < k; j++)    {        line = コンソール.行読み();        count = line.分ける().個数; // 기준 없이 나누면 빈칸이 이어져도 낱말만 돌려준다        もし (count > 0)        {            filled += 1;        }        words += count;        longest = 数学.大きい方(longest, line.長さ);    }    total = 0;    繰り返す (j = 0; j < a; j++)    {        total += コンソール.行読み().分ける()[1].数に();    }    コンソール.書く(head[0] + " " + filled + " " + words + " " + longest + " " + total);}