{ }codelper
複数の戻り値文字列の走査64ビット整数P0366

かかった時間の表記を読む

かかった時間を 1h30m のように書いた表記が n 個、一行に一つずつ与えられる。
表記は「かたまり」を空白なしで一つ以上つなげたもので、かたまり一つは「数」の後に「単位」一文字が続く形である。
数は数字(0 ~ 9)一個以上で書き、先頭に 0 が付いていてもよい(007 は 7 である)。
単位は d(日、86400 秒)、h(時間、3600 秒)、m(分、60 秒)、s(秒)の四つである。
表記を左からかたまり一つずつ読みながら、次の規則を順に検査する。
規則 1:かたまりが始まる位置の文字は数字でなければならない。
そうでなければその位置が誤りの位置である。
規則 2:数の後には文字がなければならない。
数で表記が終わるなら、誤りの位置は(表記の長さ + 1)である。
規則 3:数の次の文字は d、h、m、s のどれかでなければならない。
そうでなければその文字の位置が誤りの位置である。
規則 4:二つ目のかたまりからは、単位が直前のかたまりの単位より小さくなければならない(大きさは d > h > m > s)。
同じか大きければ、その単位の文字の位置が誤りの位置である。
規則 5:二つ目のかたまりからは、単位が h なら数は 23 以下、m なら 59 以下、s なら 59 以下でなければならない。
超えていれば、その数が始まる位置が誤りの位置である。
最初のかたまりの数はいくつでもよい(90m は正しい表記である)。
一つのかたまりで複数の規則が破られたら番号が小さい規則の誤りの位置を使い、誤ったかたまりに出会ったらその後は読まない。
位置は表記の最初の文字を 1 と数える。
表記ごとに一行ずつ、正しい表記なら "ok T"(T はすべてのかたまりの 数 × 単位の秒 を足した値)を、誤った表記なら "error p"(p は誤りの位置)を出力せよ。
数を読む関数が「読んだ値」と「数が終わった次の位置」を一緒に返し、表記一つを検査する関数が「正しいかどうか」と「秒または誤りの位置」を一緒に返すように作ってみよう(採点は出力だけを見る)。

入力
一行目に n(1 ≤ n ≤ 1,000)、続く n 行に表記が一つずつ与えられる。
表記は長さ 1 以上 40 以下で、数字と英小文字だけからなる。
数字が続く長さは 9 以下である
出力
n 行。
各行は "ok T" または "error p"。
T と p はコンマなしで数字だけを書く
例 1 · stdin
6
1h30m
90m
1h75m
30000d
15
1m2h
stdout
ok 5400
ok 5400
error 3
ok 2592000000
error 3
error 4
例 2 · stdin
5
1h2h
0d007h
h5
2d24h
1h30x
stdout
error 4
ok 25200
error 1
error 3
error 5
main.py
入力(stdin)

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

$ 準備完了

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

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

数を読んだ後に知りたいことは二つあります:読んだ値と、どこまで読んだか(次の位置)。
値を一つだけ返す関数ではどちらかを失うので、二つの値を一緒に返す方法が言語ごとに違います。
Python はタプル return value, i を value, i = read_number(s, i) で受け取ります。
JavaScript は配列 return [value, i] を const [value, next] = readNumber(s, i) でほどき、TypeScript は戻り値の型をタプル [number, number] と書きます。
C# はタプル (long value, int next) を返し、(value, i) = ReadNumber(s, i) のようにすでにある変数へ直接ほどけます。
C++ は std::pair<long long, int> を auto [value, next] = readNumber(s, i);(C++17)で受け取ります。
Java は値を一つしか返せないので record Num(long value, int next) {} にまとめて num.value() で取り出します。
C も値を一つしか返せないので、値は return で返し、次の位置はポインタ int *next に書き込みます。
Craykon はリスト 返す [値, i]; を r[0]、r[1] で取り出します。
表記一つを検査する関数も(正しいかどうか、秒または誤りの位置)の二つを同じ方法で返します。
罠その一:数を読んだ後は、位置がすでに単位へ進んでいます。
数が始まった位置を別に取っておかないと、規則 5 で単位の位置を知らせてしまいます。
例 1 の 1h75m は 75 が始まる 3 が答えですが、5 が出ます。
罠その二:上限は二つ目のかたまりからです。
最初のかたまりにもかけると、例 1 の 90m が error 1 になります。
罠その三:数の後に文字が残っているかを先に見ます。
例 1 の 15 でそのまま s[i] を読むと、Python は IndexError で止まります(Java・C# も例外で止まり、JavaScript は undefined、C は終端の '\0' を読みます)。
答えは長さ + 1 の error 3 です。
罠その四:規則 4 は「小さくなければ」なので、同じ単位もだめです。
<= を < と書くと、例 2 の 1h2h が ok 10800 になります。
罠その五:合計は 999999999d23h59m59s で 86,399,999,999,999 秒まで大きくなります。
例 1 の 30000d だけでも 2,592,000,000 秒で int(約 21 億)を超えるので、Java・C# の int で足すと -1702967296 が出ます(C・C++ では未定義動作です)。
Java・C# は long、C・C++ は long long を使います。
Python の整数には大きさの制限がなく、JavaScript・TypeScript・Craykon の数は 2^53(約 9 × 10^15)まで正確なので、そのままで合います。

Pythonmain.py · 50 行
import sys UNITS = "dhms"                    # 큰 단위부터SECONDS = [86400, 3600, 60, 1]LIMIT = [0, 23, 59, 59]           # 첫 덩이가 아닐 때 넘으면 안 되는 값(d는 늘 첫 덩이라 쓰이지 않는다)  def read_number(s, i):    """i번 자리부터 숫자를 읽어 (값, 숫자가 끝난 다음 자리)를 함께 돌려준다."""    value = 0    while i < len(s) and "0" <= s[i] <= "9":        value = value * 10 + int(s[i])        i += 1    return value, i  def parse(s):    """(True, 모두 몇 초) 또는 (False, 처음 틀린 자리)를 돌려준다."""    total = 0    last = -1                     # 바로 앞 덩이의 단위 번호    i = 0    while i < len(s):        start = i        value, i = read_number(s, i)        if i == start:            # 숫자가 와야 할 자리            return False, start + 1        if i == len(s):           # 단위 없이 끝났다            return False, i + 1        u = UNITS.find(s[i])        if u < 0 or u <= last:    # 단위가 아니거나, 앞 단위보다 작지 않다            return False, i + 1        if last >= 0 and value > LIMIT[u]:            return False, start + 1   # 틀린 곳은 수가 시작한 자리        total += value * SECONDS[u]        last = u        i += 1    return True, total  def main():    lines = sys.stdin.read().split()    n = int(lines[0])    out = []    for s in lines[1:1 + n]:        ok, x = parse(s)        out.append(("ok " if ok else "error ") + str(x))    print("\n".join(out))  main()
JavaScriptmain.js · 44 行
const lines = require("fs").readFileSync(0, "utf8").trim().split(/\s+/); const UNITS = "dhms"; // 큰 단위부터const SECONDS = [86400, 3600, 60, 1];const LIMIT = [0, 23, 59, 59]; // 첫 덩이가 아닐 때 넘으면 안 되는 값 // i번 자리부터 숫자를 읽어 [값, 숫자가 끝난 다음 자리]를 배열로 돌려준다function readNumber(s, i) {  let value = 0;  while (i < s.length && s[i] >= "0" && s[i] <= "9") {    value = value * 10 + Number(s[i]);    i++;  }  return [value, i];} // [true, 모두 몇 초] 또는 [false, 처음 틀린 자리]function parse(s) {  let total = 0;  let last = -1; // 바로 앞 덩이의 단위 번호  let i = 0;  while (i < s.length) {    const start = i;    const [value, next] = readNumber(s, i);    i = next;    if (i === start) return [false, start + 1]; // 숫자가 와야 할 자리    if (i === s.length) return [false, i + 1]; // 단위 없이 끝났다    const u = UNITS.indexOf(s[i]);    if (u < 0 || u <= last) return [false, i + 1];    if (last >= 0 && value > LIMIT[u]) return [false, start + 1]; // 수가 시작한 자리    total += value * SECONDS[u];    last = u;    i++;  }  return [true, total];} const n = Number(lines[0]);const out = [];for (let k = 1; k <= n; k++) {  const [ok, x] = parse(lines[k]);  out.push((ok ? "ok " : "error ") + x);}console.log(out.join("\n"));
TypeScriptmain.ts · 45 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").trim().split(/\s+/); const UNITS = "dhms"; // 큰 단위부터const SECONDS: number[] = [86400, 3600, 60, 1];const LIMIT: number[] = [0, 23, 59, 59]; // 첫 덩이가 아닐 때 넘으면 안 되는 값 // i번 자리부터 숫자를 읽어 [값, 숫자가 끝난 다음 자리]를 튜플로 돌려준다function readNumber(s: string, i: number): [number, number] {  let value = 0;  while (i < s.length && s[i] >= "0" && s[i] <= "9") {    value = value * 10 + Number(s[i]);    i++;  }  return [value, i];} // [true, 모두 몇 초] 또는 [false, 처음 틀린 자리]function parse(s: string): [boolean, number] {  let total = 0;  let last = -1; // 바로 앞 덩이의 단위 번호  let i = 0;  while (i < s.length) {    const start = i;    const [value, next] = readNumber(s, i);    i = next;    if (i === start) return [false, start + 1]; // 숫자가 와야 할 자리    if (i === s.length) return [false, i + 1]; // 단위 없이 끝났다    const u = UNITS.indexOf(s[i]);    if (u < 0 || u <= last) return [false, i + 1];    if (last >= 0 && value > LIMIT[u]) return [false, start + 1]; // 수가 시작한 자리    total += value * SECONDS[u];    last = u;    i++;  }  return [true, total];} const n = Number(lines[0]);const out: string[] = [];for (let k = 1; k <= n; k++) {  const [ok, x] = parse(lines[k]);  out.push((ok ? "ok " : "error ") + x);}console.log(out.join("\n"));
JavaMain.java · 53 行
import java.io.*; public class Main {    static final String UNITS = "dhms"; // 큰 단위부터    static final long[] SECONDS = {86400, 3600, 60, 1};    static final long[] LIMIT = {0, 23, 59, 59}; // 첫 덩이가 아닐 때 넘으면 안 되는 값     // Java 메서드는 값 하나만 돌려주니 record로 묶는다    record Num(long value, int next) {}    record Result(boolean ok, long x) {}     // i번 자리부터 숫자를 읽어 (값, 숫자가 끝난 다음 자리)를 돌려준다    static Num readNumber(String s, int i) {        long value = 0;        while (i < s.length() && Character.isDigit(s.charAt(i))) {            value = value * 10 + (s.charAt(i) - '0');            i++;        }        return new Num(value, i);    }     // (true, 모두 몇 초) 또는 (false, 처음 틀린 자리)    static Result parse(String s) {        long total = 0;        int last = -1; // 바로 앞 덩이의 단위 번호        int i = 0;        while (i < s.length()) {            int start = i;            Num num = readNumber(s, i);            i = num.next();            if (i == start) return new Result(false, start + 1); // 숫자가 와야 할 자리            if (i == s.length()) return new Result(false, i + 1); // 단위 없이 끝났다            int u = UNITS.indexOf(s.charAt(i));            if (u < 0 || u <= last) return new Result(false, i + 1);            if (last >= 0 && num.value() > LIMIT[u]) return new Result(false, start + 1); // 수가 시작한 자리            total += num.value() * SECONDS[u];            last = u;            i++;        }        return new Result(true, total);    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int k = 0; k < n; k++) {            Result r = parse(in.readLine().trim());            out.append(r.ok() ? "ok " : "error ").append(r.x()).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 56 行
using System;using System.Text; class Program{    const string Units = "dhms"; // 큰 단위부터    static readonly long[] Seconds = { 86400, 3600, 60, 1 };    static readonly long[] Limit = { 0, 23, 59, 59 }; // 첫 덩이가 아닐 때 넘으면 안 되는 값     // i번 자리부터 숫자를 읽어 (값, 숫자가 끝난 다음 자리)를 튜플로 돌려준다    static (long value, int next) ReadNumber(string s, int i)    {        long value = 0;        while (i < s.Length && s[i] >= '0' && s[i] <= '9')        {            value = value * 10 + (s[i] - '0');            i++;        }        return (value, i);    }     // (true, 모두 몇 초) 또는 (false, 처음 틀린 자리)    static (bool ok, long x) Parse(string s)    {        long total = 0;        int last = -1; // 바로 앞 덩이의 단위 번호        int i = 0;        while (i < s.Length)        {            int start = i;            long value;            (value, i) = ReadNumber(s, i);            if (i == start) return (false, start + 1); // 숫자가 와야 할 자리            if (i == s.Length) return (false, i + 1); // 단위 없이 끝났다            int u = Units.IndexOf(s[i]);            if (u < 0 || u <= last) return (false, i + 1);            if (last >= 0 && value > Limit[u]) return (false, start + 1); // 수가 시작한 자리            total += value * Seconds[u];            last = u;            i++;        }        return (true, total);    }     static void Main()    {        int n = int.Parse(Console.ReadLine().Trim());        var sb = new StringBuilder();        for (int k = 0; k < n; k++)        {            var (ok, x) = Parse(Console.ReadLine().Trim());            sb.Append(ok ? "ok " : "error ").Append(x).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 54 行
#include <iostream>#include <string>#include <utility>using namespace std; const string UNITS = "dhms"; // 큰 단위부터const long long SECONDS[] = {86400, 3600, 60, 1};const long long LIMIT[] = {0, 23, 59, 59}; // 첫 덩이가 아닐 때 넘으면 안 되는 값 // i번 자리부터 숫자를 읽어 (값, 숫자가 끝난 다음 자리)를 pair로 돌려준다pair<long long, int> readNumber(const string& s, int i) {    long long value = 0;    while (i < (int)s.size() && s[i] >= '0' && s[i] <= '9') {        value = value * 10 + (s[i] - '0');        i++;    }    return {value, i};} // (true, 모두 몇 초) 또는 (false, 처음 틀린 자리)pair<bool, long long> parse(const string& s) {    int len = (int)s.size();    long long total = 0;    int last = -1; // 바로 앞 덩이의 단위 번호    int i = 0;    while (i < len) {        int start = i;        auto [value, next] = readNumber(s, i); // C++17 구조적 바인딩        i = next;        if (i == start) return {false, start + 1}; // 숫자가 와야 할 자리        if (i == len) return {false, i + 1};       // 단위 없이 끝났다        int u = (int)UNITS.find(s[i]);             // 없으면 npos → -1        if (u < 0 || u <= last) return {false, i + 1};        if (last >= 0 && value > LIMIT[u]) return {false, start + 1}; // 수가 시작한 자리        total += value * SECONDS[u];        last = u;        i++;    }    return {true, total};} int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n;    cin >> n;    string s;    for (int k = 0; k < n; k++) {        cin >> s;        auto [ok, x] = parse(s);        cout << (ok ? "ok " : "error ") << x << "\n";    }    return 0;}
Cmain.c · 54 行
#include <stdio.h>#include <string.h> static const char UNITS[] = "dhms"; /* 큰 단위부터 */static const long long SECONDS[] = {86400, 3600, 60, 1};static const long long LIMIT[] = {0, 23, 59, 59}; /* 첫 덩이가 아닐 때 넘으면 안 되는 값 */ /* i번 자리부터 숫자를 읽어 값을 돌려주고, 숫자가 끝난 다음 자리는 *next에 써 준다.   C 함수는 값 하나만 돌려주니 둘째 값은 포인터로 내보낸다 */static long long read_number(const char *s, int i, int *next) {    long long value = 0;    while (s[i] >= '0' && s[i] <= '9') {        value = value * 10 + (s[i] - '0');        i++;    }    *next = i;    return value;} /* 맞으면 1을 돌려주고 *x에 모두 몇 초인지, 틀리면 0을 돌려주고 *x에 처음 틀린 자리 */static int parse(const char *s, long long *x) {    int len = (int)strlen(s);    long long total = 0;    int last = -1; /* 바로 앞 덩이의 단위 번호 */    int i = 0;    while (i < len) {        int start = i;        long long value = read_number(s, i, &i);        if (i == start) { *x = start + 1; return 0; } /* 숫자가 와야 할 자리 */        if (i == len) { *x = i + 1; return 0; }       /* 단위 없이 끝났다 */        const char *p = strchr(UNITS, s[i]);        int u = p ? (int)(p - UNITS) : -1;        if (u < 0 || u <= last) { *x = i + 1; return 0; }        if (last >= 0 && value > LIMIT[u]) { *x = start + 1; return 0; } /* 수가 시작한 자리 */        total += value * SECONDS[u];        last = u;        i++;    }    *x = total;    return 1;} int main(void) {    int n;    char s[64];    if (scanf("%d", &n) != 1) return 0;    for (int k = 0; k < n; k++) {        long long x;        if (scanf("%63s", s) != 1) return 0;        if (parse(s, &x)) printf("ok %lld\n", x);        else printf("error %lld\n", x);    }    return 0;}
Craykonmain.cray · 65 行
단위들 = "dhms"; // 큰 단위부터초 = [86400, 3600, 60, 1];한도 = [0, 23, 59, 59]; // 첫 덩이가 아닐 때 넘으면 안 되는 값 // i번 자리부터 숫자를 읽어 [값, 숫자가 끝난 다음 자리]를 목록으로 돌려준다수읽기(s, i){    값 = 0;    間 (i < s.長さ && s[i] >= "0" && s[i] <= "9")    {        값 = 값 * 10 + s[i].数に();        i = i + 1;    }    返す [값, i];} // [참, 모두 몇 초] 또는 [거짓, 처음 틀린 자리]풀기(s){    합 = 0;    앞단위 = -1; // 바로 앞 덩이의 단위 번호    i = 0;    間 (i < s.長さ)    {        시작 = i;        r = 수읽기(s, i);        값 = r[0];        i = r[1];        もし (i == 시작)        {            返す [偽, 시작 + 1]; // 숫자가 와야 할 자리        }        もし (i == s.長さ)        {            返す [偽, i + 1]; // 단위 없이 끝났다        }        u = 단위들.位置検索(s[i]);        もし (u < 0 || u <= 앞단위)        {            返す [偽, i + 1];        }        もし (앞단위 >= 0 && 값 > 한도[u])        {            返す [偽, 시작 + 1]; // 수가 시작한 자리        }        합 = 합 + 값 * 초[u];        앞단위 = u;        i = i + 1;    }    返す [真, 합];} n = コンソール.行読み().数に();繰り返す (k = 0; k < n; k++){    r = 풀기(コンソール.行読み().空白除去());    もし (r[0])    {        コンソール.書く("ok " + r[1]);    }    でなければ    {        コンソール.書く("error " + r[1]);    }}