{ }codelper
条件つき並べ替え文字列の比較null 値P0308

列の基準を入力で受け取る表の並べ替え

行がn個、列がc個の表がある。
行には入力された順に1番からn番まで番号がついている。
列ごとに種類が決まっている。
Nは整数の列、Sは文字列の列である。
セルに?と書いてあれば、そのセルは空である。
?はどちらの種類の列にも現れうる。
並べ替えの基準がk個与えられる。
基準は+2や-3のように符号と列番号をつなげて書いたものである。
+は昇順(小さい順)、-は降順(大きい順)である。
2つの行は、まず1番目の基準の列で比べ、同じなら2番目の基準の列で比べる、というように順に比べる。
整数は数の大きさで比べる。
文字列は文字コード順で比べる。
つまり、前から見て最初に違う文字のコードが小さい方が小さく(数字 < 大文字 < 小文字)、一方が他方の先頭部分なら短い方が小さい。
空のセルは、昇順でも降順でも、いつも空でないものより後ろに来る。
空のセルどうしは同じとみなす。
すべての基準で同じ行は、入力された順を保つ。
表を並べ替えて、行番号を並べ替えた順に出力せよ。

入力
1行目にn、c、kが与えられる。
2行目に列の種類c個(NまたはS)、3行目に基準k個が与えられる。
次のn行に、行ごとのセルc個(値または?)が与えられる。
1行の値は空白1つで区切られる。
1 ≤ n ≤ 10,000、1 ≤ k ≤ c ≤ 6。
基準の列番号は1以上c以下で、互いに異なる。
整数のセルは−2,000,000,000以上2,000,000,000以下。
文字列のセルは英大文字・英小文字・数字からなる1〜8文字
出力
行番号n個を並べ替えた順に1行で、空白1つで区切って出力する
例 1 · stdin
7 3 2
S N S
-2 +1
kim 30 x
Lee ? y
lee 30 z
Zed 30 w
Kim 25 d
park 41 e
kim 30 q
stdout
6 4 1 7 3 5 2
main.py
入力(stdin)

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

$ 準備完了

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

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

基準が入力で来るので、比較関数を実行中に組み立てます。
基準を順に見て「空か → 値が違うか → 向きを反転するか」を調べる関数1つで足ります。
例:2列目の降順なので41(6番)、30の4行、25(5番)、空(2番)の順で、30の行は1列目の昇順でZed < kim < lee(大文字が小文字より前)、1番と7番はどちらもkim 30なので入力順です。
罠1:降順を「比較結果全体に−1を掛ける」で作ると空のセルまで前に来ます。
空のセルは向きを反転する前に別に返します。
罠2:数をa − bで比べると、2,000,000,000 −(−2,000,000,000)が32ビットのintを超えて順序が逆になります。
JavaはInteger.compare、C#はCompareTo、Cは(a > b) − (a < b)を使います。
JavaScript・TypeScript・Craykonの数は浮動小数点なので、この範囲では引き算で比べてもあふれません。
罠3:数の列を数に変えずに文字列で比べると"10" < "9"になります(Craykonは数に())。
罠4:文字列を「辞書順」で比べる関数は文字コード順ではありません。
JavaScriptのlocaleCompareはappleをZebraより前に置き、C#のstring.Compare・CompareToも文字コードではなくカルチャ(言語設定)の規則に従います。
文字コード順はJavaScript・TypeScript・Python・C++・Craykonの<、JavaのcompareTo、C#のstring.CompareOrdinal、Cのstrcmpです。
罠5:同じ行の順を保つ(安定な)ソートはPythonのsorted・list.sort、JavaScript・TypeScriptのsort(ES2019から)、JavaのList.sort、C++のstd::stable_sort、Craykonの並べ替え(比較関数)です。
Cのqsort、C++のstd::sort、C#のArray.Sort・List.Sortは保証しないので、行番号を最後の基準に入れます。
罠6:空のセルを0や空文字列として読むだけで印をつけないと、本当の0と混ざります。
言語ごとの形:Python 3のsortには比較関数を渡す場所がないのでfunctools.cmp_to_keyで包み、Javaは基準ごとにComparatorを作ってthenComparingでつなぎます。
C++の比較関数は−1・0・1ではなく「前か」をboolで答え、同じときにtrueを返してはいけません。

Pythonmain.py · 32 行
import sysfrom functools import cmp_to_key lines = sys.stdin.read().split("\n")n, c, k = map(int, lines[0].split())types = lines[1].split()# 기준마다 (열 번호, 내림차순인가)keys = [(int(t[1:]) - 1, t[0] == "-") for t in lines[2].split()] rows = []for i in range(n):    cells = lines[3 + i].split()    # 값이 없는 칸은 None, 수 열은 int, 문자열 열은 그대로    rows.append([None if v == "?" else (int(v) if types[j] == "N" else v) for j, v in enumerate(cells)])  def compare(x, y):    for col, desc in keys:        a, b = rows[x][col], rows[y][col]        if a is None or b is None:            if a is None and b is None:                continue            return 1 if a is None else -1   # 값이 없는 칸은 방향과 상관없이 뒤로        if a != b:            r = -1 if a < b else 1          # 문자열의 < 는 글자 코드 순서다            return -r if desc else r    return 0  # sorted는 안정 정렬이라 모든 기준이 같으면 입력 순서가 남는다order = sorted(range(n), key=cmp_to_key(compare))print(" ".join(str(i + 1) for i in order))
JavaScriptmain.js · 32 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [n, c, k] = lines[0].split(" ").map(Number);const types = lines[1].trim().split(" ");// 기준마다 열 번호와 방향const keys = lines[2].trim().split(" ").map((t) => ({ col: Number(t.slice(1)) - 1, desc: t[0] === "-" })); const rows = [];for (let i = 0; i < n; i++) {  const cells = lines[3 + i].trim().split(" ");  // 값이 없는 칸은 null, 수 열은 수로, 문자열 열은 그대로  rows.push(cells.map((v, j) => (v === "?" ? null : types[j] === "N" ? Number(v) : v)));} function compare(x, y) {  for (const { col, desc } of keys) {    const a = rows[x][col];    const b = rows[y][col];    if (a === null || b === null) {      if (a === null && b === null) continue;      return a === null ? 1 : -1; // 값이 없는 칸은 방향과 상관없이 뒤로    }    if (a !== b) {      const r = a < b ? -1 : 1; // 문자열의 < 는 글자 코드 순서다(localeCompare가 아니다)      return desc ? -r : r;    }  }  return 0;} // Array.prototype.sort는 안정 정렬이라(ES2019부터) 모든 기준이 같으면 입력 순서가 남는다const order = Array.from({ length: n }, (_, i) => i).sort(compare);console.log(order.map((i) => i + 1).join(" "));
TypeScriptmain.ts · 40 行
import { readFileSync } from "node:fs"; type Cell = number | string | null;interface Key {  col: number;  desc: boolean;} const lines: string[] = readFileSync(0, "utf8").split("\n");const [n] = lines[0].split(" ").map(Number);const types: string[] = lines[1].trim().split(" ");// 기준마다 열 번호와 방향const keys: Key[] = lines[2].trim().split(" ").map((t) => ({ col: Number(t.slice(1)) - 1, desc: t[0] === "-" })); const rows: Cell[][] = [];for (let i = 0; i < n; i++) {  const cells: string[] = lines[3 + i].trim().split(" ");  // 값이 없는 칸은 null, 수 열은 수로, 문자열 열은 그대로  rows.push(cells.map((v, j): Cell => (v === "?" ? null : types[j] === "N" ? Number(v) : v)));} function compare(x: number, y: number): number {  for (const { col, desc } of keys) {    const a: Cell = rows[x][col];    const b: Cell = rows[y][col];    if (a === null || b === null) {      if (a === null && b === null) continue;      return a === null ? 1 : -1; // 값이 없는 칸은 방향과 상관없이 뒤로    }    if (a !== b) {      const r: number = a < b ? -1 : 1; // 문자열의 < 는 글자 코드 순서다(localeCompare가 아니다)      return desc ? -r : r;    }  }  return 0;} // Array.prototype.sort는 안정 정렬이라(ES2019부터) 모든 기준이 같으면 입력 순서가 남는다const order: number[] = Array.from({ length: n }, (_, i) => i).sort(compare);console.log(order.map((i) => i + 1).join(" "));
JavaMain.java · 52 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(br.readLine());        int n = Integer.parseInt(st.nextToken());        int c = Integer.parseInt(st.nextToken());        String[] types = br.readLine().trim().split(" ");        String[] keyTokens = br.readLine().trim().split(" ");         boolean[][] empty = new boolean[n][c];        int[][] num = new int[n][c];       // 수 열의 값        String[][] text = new String[n][c]; // 문자열 열의 값        for (int i = 0; i < n; i++) {            String[] cells = br.readLine().trim().split(" ");            for (int j = 0; j < c; j++) {                if (cells[j].equals("?")) empty[i][j] = true;                else if (types[j].equals("N")) num[i][j] = Integer.parseInt(cells[j]);                else text[i][j] = cells[j];            }        }         // 기준 하나마다 Comparator를 만들고 thenComparing으로 잇는다        Comparator<Integer> chain = null;        for (String t : keyTokens) {            final int col = Integer.parseInt(t.substring(1)) - 1;            final boolean desc = t.charAt(0) == '-';            final boolean numeric = types[col].equals("N");            Comparator<Integer> one = (x, y) -> {                boolean ex = empty[x][col], ey = empty[y][col];                if (ex || ey) return Boolean.compare(ex, ey); // 값이 없는 칸은 방향과 상관없이 뒤로                // 뺄셈(a - b)은 넘칠 수 있으니 Integer.compare를 쓴다. compareTo는 글자 코드 순서다                int r = numeric ? Integer.compare(num[x][col], num[y][col]) : text[x][col].compareTo(text[y][col]);                return desc ? -r : r;            };            chain = chain == null ? one : chain.thenComparing(one);        }         List<Integer> order = new ArrayList<>();        for (int i = 0; i < n; i++) order.add(i);        order.sort(chain); // List.sort는 안정 정렬이라 모든 기준이 같으면 입력 순서가 남는다         StringBuilder sb = new StringBuilder();        for (int i = 0; i < n; i++) {            if (i > 0) sb.append(' ');            sb.append(order.get(i) + 1);        }        System.out.println(sb);    }}
C#Main.cs · 49 行
using System;using System.Linq; class Program{    static void Main()    {        var head = Console.ReadLine()!.Split(' ');        int n = int.Parse(head[0]), c = int.Parse(head[1]);        var types = Console.ReadLine()!.Trim().Split(' ');        var keyTokens = Console.ReadLine()!.Trim().Split(' ');        var keyCol = keyTokens.Select(t => int.Parse(t.Substring(1)) - 1).ToArray();        var keyDesc = keyTokens.Select(t => t[0] == '-').ToArray();         var empty = new bool[n, c];        var num = new int[n, c];        // 수 열의 값        var text = new string[n, c];    // 문자열 열의 값        for (int i = 0; i < n; i++)        {            var cells = Console.ReadLine()!.Trim().Split(' ');            for (int j = 0; j < c; j++)            {                if (cells[j] == "?") empty[i, j] = true;                else if (types[j] == "N") num[i, j] = int.Parse(cells[j]);                else text[i, j] = cells[j];            }        }         var order = Enumerable.Range(0, n).ToArray();        Array.Sort(order, (x, y) =>        {            for (int q = 0; q < keyCol.Length; q++)            {                int col = keyCol[q];                bool ex = empty[x, col], ey = empty[y, col];                if (ex || ey)                {                    if (ex && ey) continue;                    return ex ? 1 : -1; // 값이 없는 칸은 방향과 상관없이 뒤로                }                // 뺄셈(a - b)은 넘칠 수 있으니 CompareTo를 쓴다. CompareOrdinal은 글자 코드 순서다                int r = types[col] == "N" ? num[x, col].CompareTo(num[y, col]) : string.CompareOrdinal(text[x, col], text[y, col]);                if (r != 0) return keyDesc[q] ? -r : r;            }            return x.CompareTo(y); // Array.Sort는 안정 정렬이 아니라서 입력 순서를 마지막 기준으로 넣는다        });        Console.WriteLine(string.Join(" ", order.Select(i => i + 1)));    }}
C++main.cpp · 63 行
#include <algorithm>#include <iostream>#include <numeric>#include <string>#include <vector>using namespace std; struct Cell {    bool empty = false;    int num = 0;     // 수 열의 값    string text;     // 문자열 열의 값}; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, c, k;    cin >> n >> c >> k;    vector<char> types(c);    for (auto &t : types) cin >> t;    vector<int> keyCol(k);    vector<bool> keyDesc(k);    for (int q = 0; q < k; q++) {        string t;        cin >> t;        keyDesc[q] = t[0] == '-';        keyCol[q] = stoi(t.substr(1)) - 1;    }    vector<vector<Cell>> rows(n, vector<Cell>(c));    for (auto &row : rows) {        for (int j = 0; j < c; j++) {            string v;            cin >> v;            if (v == "?") row[j].empty = true;            else if (types[j] == 'N') row[j].num = stoi(v);            else row[j].text = v;        }    }     vector<int> order(n);    iota(order.begin(), order.end(), 0);    // stable_sort는 모든 기준이 같으면 입력 순서를 지킨다(sort는 지키지 않는다)    // 비교 함수는 'x가 y보다 앞인가'를 bool로 답한다    stable_sort(order.begin(), order.end(), [&](int x, int y) {        for (int q = 0; q < k; q++) {            const Cell &a = rows[x][keyCol[q]];            const Cell &b = rows[y][keyCol[q]];            if (a.empty || b.empty) {                if (a.empty && b.empty) continue;                return b.empty; // 값이 없는 칸은 방향과 상관없이 뒤로            }            if (types[keyCol[q]] == 'N') {                if (a.num != b.num) return keyDesc[q] ? a.num > b.num : a.num < b.num;            } else {                if (a.text != b.text) return keyDesc[q] ? a.text > b.text : a.text < b.text; // 글자 코드 순서            }        }        return false;    });    for (int i = 0; i < n; i++) cout << (i ? " " : "") << order[i] + 1;    cout << "\n";    return 0;}
Cmain.c · 61 行
#include <stdio.h>#include <stdlib.h>#include <string.h> #define MAXN 10000#define MAXC 6 static char types[MAXC];static int keyCol[MAXC], keyDesc[MAXC], k;static char empty[MAXN][MAXC];static int num[MAXN][MAXC];       /* 수 열의 값 */static char text[MAXN][MAXC][9];  /* 문자열 열의 값(8글자까지) */static int order[MAXN]; static int compare(const void *pa, const void *pb) {    int x = *(const int *)pa, y = *(const int *)pb;    for (int q = 0; q < k; q++) {        int col = keyCol[q];        if (empty[x][col] || empty[y][col]) {            if (empty[x][col] && empty[y][col]) continue;            return empty[x][col] ? 1 : -1; /* 값이 없는 칸은 방향과 상관없이 뒤로 */        }        int r;        if (types[col] == 'N') {            /* 뺄셈(a - b)은 넘칠 수 있으니 비교 두 번으로 -1, 0, 1을 만든다 */            r = (num[x][col] > num[y][col]) - (num[x][col] < num[y][col]);        } else {            r = strcmp(text[x][col], text[y][col]); /* 글자 코드 순서 */        }        if (r != 0) return keyDesc[q] ? -r : r;    }    return (x > y) - (x < y); /* qsort는 안정 정렬이 아니라서 입력 순서를 마지막 기준으로 넣는다 */} int main(void) {    int n, c;    char tok[16];    if (scanf("%d %d %d", &n, &c, &k) != 3) return 1;    for (int j = 0; j < c; j++) {        scanf("%15s", tok);        types[j] = tok[0];    }    for (int q = 0; q < k; q++) {        scanf("%15s", tok);        keyDesc[q] = tok[0] == '-';        keyCol[q] = atoi(tok + 1) - 1;    }    for (int i = 0; i < n; i++) {        for (int j = 0; j < c; j++) {            scanf("%15s", tok);            if (strcmp(tok, "?") == 0) empty[i][j] = 1;            else if (types[j] == 'N') num[i][j] = atoi(tok);            else strcpy(text[i][j], tok);        }        order[i] = i;    }    qsort(order, n, sizeof order[0], compare);    for (int i = 0; i < n; i++) printf(i ? " %d" : "%d", order[i] + 1);    printf("\n");    return 0;}
Craykonmain.cray · 78 行
첫줄 = コンソール.数読み();n = 첫줄[0];c = 첫줄[1];k = 첫줄[2];types = コンソール.行読み().分ける(" ");// 기준마다 열 번호와 방향keyCol = [];keyDesc = [];それぞれ (t の中 コンソール.行読み().分ける(" ")){    keyCol.追加(t.切り出す(1).数に() - 1);    keyDesc.追加(t[0] == "-");} rows = [];order = [];繰り返す (i = 0; i < n; i++){    cells = コンソール.行読み().分ける(" ");    繰り返す (j = 0; j < c; j++)    {        もし (cells[j] == "?")        {            cells[j] = なし;        }        でなければ もし (types[j] == "N")        {            cells[j] = cells[j].数に(); // 수로 바꿔 두지 않으면 "10" < "9"처럼 문자열로 비교된다        }    }    rows.追加(cells);    order.追加(i);} // 앞이 먼저면 음수, 같으면 0, 뒤가 먼저면 양수compareRows(x, y){    繰り返す (q = 0; q < k; q++)    {        a = rows[x][keyCol[q]];        b = rows[y][keyCol[q]];        もし (a == なし || b == なし)        {            もし (a == なし && b == なし)            {                次へ;            }            もし (a == なし)            {                返す 1; // 값이 없는 칸은 방향과 상관없이 뒤로            }            返す -1;        }        もし (a != b)        {            r = 1;            もし (a < b)            {                r = -1;            }            もし (keyDesc[q])            {                返す -r;            }            返す r;        }    }    返す 0;} // 정렬(비교함수)는 안정 정렬이라 모든 기준이 같으면 입력 순서가 남는다order.並べ替え(compareRows);out = [];それぞれ (i の中 order){    out.追加(i + 1);}コンソール.書く(out.つなぐ(" "));