{ }codelper
格式化输出P0396

打印字节转储表

为了用眼睛查看文件里的字节,要打印一张转储表。
第一行给出字节数 n、每行放的字节数 w、第一个字节的地址 s。
第二行依次给出 n 个字节。
把字节从前往后每 w 个分成一组,每组写成一行。
最后一行可能少于 w 个。
从 0 数起,第 k 个字节的地址是 s + k。
一行由下面四个部分依次连着写成。
第一,把该行第一个字节的地址写成八位小写十六进制数,后面加冒号(:)。
位数不够时在前面补 0。
第二,对每个字节先写一个空格,再把该字节写成两位大写十六进制数(10 写成 0A)。
第三,该行的字节少于 w 个时,每缺一个字节写三个空格。
第四,写两个空格和 |,然后每个字节写一个字符,最后用 | 结束。
字节的值在 32 到 126 之间时写该编码的 ASCII 字符,否则写句点(.)。
字节的个数和值都与紧挨着的前一组完全相同的组,不写这一行。
这样没写的行连续出现时,不管有几行,都在那个位置只写一次仅含 * 的一行。
最后写一行 n bytes, sum S 形式的内容。
n 和 S 是十进制数,S 是所有字节的值之和。

输入
1 ≤ n ≤ 6000,1 ≤ w ≤ 16,0 ≤ s,s + n ≤ 4294967296。
字节是 0 到 255 的整数,用一个空格隔开给出
输出
转储的各行(没写的行的位置写 *)和最后的 n bytes, sum S 一行
样例 1 · stdin
19 8 65528
72 101 108 108 111 44 32 119 111 114 108 100 33 10 0 127 255 9 7
stdout
0000fff8: 48 65 6C 6C 6F 2C 20 77  |Hello, w|
00010000: 6F 72 6C 64 21 0A 00 7F  |orld!...|
00010008: FF 09 07                 |...|
19 bytes, sum 1569
样例 2 · stdin
14 4 0
0 0 0 0 0 0 0 0 0 0 0 0 65 66
stdout
00000000: 00 00 00 00  |....|
*
0000000c: 41 42        |AB|
14 bytes, sum 131
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

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

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

把数写成十六进制并对齐位数,各语言的写法不同。
Python 用 f"{b:02X}" 和 f"{a:08x}",C 用 printf("%02X", b) 和 printf("%08llx", a),Java 用 String.format("%02X", b) 和 String.format("%08x", a),C# 用 b.ToString("X2") 和 a.ToString("x8"),一个格式就同时定下进制、位数和大小写。
JS、TS 的 b.toString(16) 是小写,也不对齐位数,所以要接着写 toUpperCase() 和 padStart(2, "0")。
C++ 像 std::cout << std::hex << std::setfill('0') << std::setw(2) << b 这样挂上操纵符。
Craykon 没有十六进制格式,所以用 剩下 % 16 取出一位,用 数学.商(剩下, 16) 缩小,自己拼出来,字符用 字符.从编码(b) 得到。
陷阱一:去掉补 0(Python {b:X}),小于 16 的字节就只有一位。
第一个示例的 10 会输出成 A 而不是 0A,后面的内容都向前挪一格。
陷阱二:JS 的 toString(16) 是小写,去掉 toUpperCase() 后 6C 会变成 6c。
地址是小写,字节是大写。
陷阱三:C++ 的 std::hex、std::setfill、std::uppercase 挂上一次就一直有效(只有 std::setw 只作用于紧接着的一个值)。
不用 std::dec 改回来,第一个示例的最后一行就变成 13 bytes, sum 621。
陷阱四:最后一行较短时,不给每个缺少的字节补三个空格,| 就会向前挪(第一个示例的第三行)。
陷阱五:只有到 126 的编码才写成字符。
127(DEL)不是可见字符。
写成 b <= 127 的话,第一个示例第二行里会原样放进 127 号字符而不是 .。
陷阱六:相同的组不管连续多少次,* 只写一次。
第二个示例里没写的行有两行,但 * 只有一行。
陷阱七:地址最大到 4294967295,放不进 32 位的 int(约到 21 亿)。
在 C# 里用 int.Parse 读地址会因 OverflowException 而停止。
C、C++ 用 long long,Java、C# 用 long 来存。
这种情况不在示例里(s 不小于 2147483648 时才会出现)。

Pythonmain.py · 22 行
n, w, s = map(int, input().split())data = list(map(int, input().split())) out = []prev = None        # 바로 앞 줄의 바이트들starred = False    # 생략 표시 *를 이미 썼는가for i in range(0, n, w):    row = data[i:i + w]    if row == prev:        if not starred:            out.append("*")            starred = True        continue    prev = row    starred = False    # 바이트는 대문자 16진수 두 자리, 모자란 칸은 빈칸 세 개    hexes = "".join(f" {b:02X}" for b in row) + "   " * (w - len(row))    text = "".join(chr(b) if 32 <= b <= 126 else "." for b in row)    # 주소는 소문자 16진수 여덟 자리    out.append(f"{s + i:08x}:{hexes}  |{text}|")out.append(f"{n} bytes, sum {sum(data)}")print("\n".join(out))
JavaScriptmain.js · 26 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [n, w, s] = lines[0].trim().split(/\s+/).map(Number);const data = lines[1].trim().split(/\s+/).map(Number); const out = [];let prev = "";       // 바로 앞 줄의 바이트 부분(주소 뺀 문자열)let starred = false; // 생략 표시 *를 이미 썼는가let sum = 0;for (let i = 0; i < n; i += w) {  const row = data.slice(i, i + w);  for (const b of row) sum += b;  // 바이트는 대문자 16진수 두 자리, 모자란 칸은 빈칸 세 개  const hexes = row.map((b) => " " + b.toString(16).toUpperCase().padStart(2, "0")).join("") + "   ".repeat(w - row.length);  if (hexes === prev) {    if (!starred) out.push("*");    starred = true;    continue;  }  prev = hexes;  starred = false;  const text = row.map((b) => (b >= 32 && b <= 126 ? String.fromCharCode(b) : ".")).join("");  // 주소는 소문자 16진수 여덟 자리  out.push(`${(s + i).toString(16).padStart(8, "0")}:${hexes}  |${text}|`);}out.push(`${n} bytes, sum ${sum}`);console.log(out.join("\n"));
TypeScriptmain.ts · 29 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const [n, w, s]: number[] = lines[0].trim().split(/\s+/).map(Number);const data: number[] = lines[1].trim().split(/\s+/).map(Number); const hex = (value: number, width: number): string => value.toString(16).padStart(width, "0"); const out: string[] = [];let prev = "";       // 바로 앞 줄의 바이트 부분(주소 뺀 문자열)let starred = false; // 생략 표시 *를 이미 썼는가let sum = 0;for (let i = 0; i < n; i += w) {  const row: number[] = data.slice(i, i + w);  for (const b of row) sum += b;  // 바이트는 대문자 16진수 두 자리, 모자란 칸은 빈칸 세 개  const hexes: string = row.map((b) => " " + hex(b, 2).toUpperCase()).join("") + "   ".repeat(w - row.length);  if (hexes === prev) {    if (!starred) out.push("*");    starred = true;    continue;  }  prev = hexes;  starred = false;  const text: string = row.map((b) => (b >= 32 && b <= 126 ? String.fromCharCode(b) : ".")).join("");  // 주소는 소문자 16진수 여덟 자리  out.push(`${hex(s + i, 8)}:${hexes}  |${text}|`);}out.push(`${n} bytes, sum ${sum}`);console.log(out.join("\n"));
JavaMain.java · 45 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        int n = Integer.parseInt(st.nextToken());        int w = Integer.parseInt(st.nextToken());        long s = Long.parseLong(st.nextToken());   // 주소는 2^31을 넘을 수 있어 long        st = new StringTokenizer(in.readLine());        int[] data = new int[n];        long sum = 0;        for (int i = 0; i < n; i++) {            data[i] = Integer.parseInt(st.nextToken());            sum += data[i];        }         StringBuilder out = new StringBuilder();        boolean starred = false;   // 생략 표시 *를 이미 썼는가        for (int i = 0; i < n; i += w) {            int len = Math.min(w, n - i);            // 바로 앞 줄(i - w부터 w개)과 바이트가 모두 같은가            if (i > 0 && len == w && Arrays.equals(data, i, i + w, data, i - w, i)) {                if (!starred) out.append("*\n");                starred = true;                continue;            }            starred = false;            out.append(String.format("%08x:", s + i));          // 소문자 16진수 여덟 자리            for (int j = 0; j < w; j++) {                if (j < len) out.append(String.format(" %02X", data[i + j]));   // 대문자 두 자리                else out.append("   ");            }            out.append("  |");            for (int j = 0; j < len; j++) {                int b = data[i + j];                out.append(b >= 32 && b <= 126 ? (char) b : '.');            }            out.append("|\n");        }        out.append(n).append(" bytes, sum ").append(sum).append('\n');        System.out.print(out);    }}
C#Main.cs · 43 行
using System;using System.Text; class Program{    static void Main()    {        string[] head = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(head[0]);        int w = int.Parse(head[1]);        long s = long.Parse(head[2]);   // 주소는 2^31을 넘을 수 있어 long        int[] data = Array.ConvertAll(Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries), int.Parse);         var output = new StringBuilder();        bool starred = false;   // 생략 표시 *를 이미 썼는가        long sum = 0;        for (int i = 0; i < n; i += w)        {            int len = Math.Min(w, n - i);            var row = new ReadOnlySpan<int>(data, i, len);            foreach (int b in row) sum += b;            // 바로 앞 줄(i - w부터 w개)과 바이트가 모두 같은가            if (i > 0 && row.SequenceEqual(new ReadOnlySpan<int>(data, i - w, w)))            {                if (!starred) output.Append("*\n");                starred = true;                continue;            }            starred = false;            output.Append((s + i).ToString("x8")).Append(':');   // 소문자 16진수 여덟 자리            for (int j = 0; j < w; j++)            {                if (j < len) output.Append(' ').Append(data[i + j].ToString("X2"));   // 대문자 두 자리                else output.Append("   ");            }            output.Append("  |");            foreach (int b in row) output.Append(b >= 32 && b <= 126 ? (char)b : '.');            output.Append("|\n");        }        output.Append($"{n} bytes, sum {sum}\n");        Console.Write(output);    }}
C++main.cpp · 45 行
#include <algorithm>#include <iomanip>#include <iostream>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n, w;    long long s;   // 주소는 2^31을 넘을 수 있어 long long    std::cin >> n >> w >> s;    std::vector<int> data(n);    long long sum = 0;    for (int& b : data) {        std::cin >> b;        sum += b;    }     bool starred = false;   // 생략 표시 *를 이미 썼는가    for (int i = 0; i < n; i += w) {        int len = std::min(w, n - i);        // 바로 앞 줄(i - w부터 w개)과 바이트가 모두 같은가        if (i > 0 && len == w && std::equal(data.begin() + i, data.begin() + i + w, data.begin() + i - w)) {            if (!starred) std::cout << "*\n";            starred = true;            continue;        }        starred = false;        // hex, setfill, uppercase는 한 번 걸면 계속 남고, setw는 바로 다음 값 하나에만 걸린다        std::cout << std::hex << std::setfill('0');        std::cout << std::nouppercase << std::setw(8) << s + i << ':';        std::cout << std::uppercase;        for (int j = 0; j < w; j++) {            if (j < len) std::cout << ' ' << std::setw(2) << data[i + j];            else std::cout << "   ";        }        std::cout << "  |";        for (int j = 0; j < len; j++) {            int b = data[i + j];            std::cout << (b >= 32 && b <= 126 ? static_cast<char>(b) : '.');        }        std::cout << "|\n";    }    std::cout << std::dec << n << " bytes, sum " << sum << '\n';   // 10진수로 되돌린다}
Cmain.c · 42 行
#include <stdio.h>#include <string.h> static unsigned char data[6000]; int main(void) {    int n, w;    long long s;   /* 주소는 2^31을 넘을 수 있어 long long */    scanf("%d %d %lld", &n, &w, &s);    long long sum = 0;    for (int i = 0; i < n; i++) {        int b;        scanf("%d", &b);        data[i] = (unsigned char)b;        sum += b;    }     int starred = 0;   /* 생략 표시 *를 이미 썼는가 */    for (int i = 0; i < n; i += w) {        int len = n - i < w ? n - i : w;        /* 바로 앞 줄(i - w부터 w개)과 바이트가 모두 같은가 */        if (i > 0 && len == w && memcmp(data + i, data + i - w, (size_t)w) == 0) {            if (!starred) puts("*");            starred = 1;            continue;        }        starred = 0;        printf("%08llx:", (unsigned long long)(s + i));   /* 소문자 16진수 여덟 자리 */        for (int j = 0; j < w; j++) {            if (j < len) printf(" %02X", data[i + j]);     /* 대문자 두 자리 */            else printf("   ");        }        printf("  |");        for (int j = 0; j < len; j++) {            unsigned char b = data[i + j];            putchar(b >= 32 && b <= 126 ? b : '.');        }        puts("|");    }    printf("%d bytes, sum %lld\n", n, sum);    return 0;}
Craykonmain.cray · 65 行
// 수를 16진수 문자열로 바꾼다(자리 수만큼 앞을 0으로 채움)십육진수(값, 자리, 숫자들){    결과 = "";    남은 = 값;    循环 (k = 0; k < 자리; k++)    {        결과 = 숫자들[남은 % 16] + 결과;        남은 = 数学.商(남은, 16);    }    返回 결과;} 첫줄 = 控制台.读数字();n = 첫줄[0];w = 첫줄[1];s = 첫줄[2];data = 控制台.读数字(); 이전 = ""; // 바로 앞 줄의 바이트 부분(주소 뺀 문자열)별표씀 = 假; // 생략 표시 *를 이미 썼는가합 = 0;out = "";循环 (i = 0; i < n; i += w){    바이트들 = "";    글자들 = "";    循环 (j = 0; j < w; j++)    {        如果 (i + j < n)        {            b = data[i + j];            합 += b;            바이트들 = 바이트들 + " " + 십육진수(b, 2, "0123456789ABCDEF");            如果 (b >= 32 && b <= 126)            {                글자들 = 글자들 + 字符.从编码(b);            }            否则            {                글자들 = 글자들 + ".";            }        }        否则        {            바이트들 = 바이트들 + "   ";        }    }    如果 (바이트들 == 이전)    {        如果 (!별표씀)        {            out = out + "*\n";        }        별표씀 = 真;    }    否则    {        이전 = 바이트들;        별표씀 = 假;        out = out + 십육진수(s + i, 8, "0123456789abcdef") + ":" + 바이트들 + "  |" + 글자들 + "|\n";    }}out = out + n + " bytes, sum " + 합 + "\n";控制台.接着输出(out);