{ }codelper
等価比較と型変換null 値P0124

同じかを測る四つの方法

最初の行に n が、二行目に値 n 個が空白一つで区切って与えられる。
それぞれの値は JavaScript のコードにそのまま書ける文字列の断片である。
断片は整数(例:5, -12, -0)、二重引用符で囲んだ文字列(例:"5", "", "abc")、または true, false, null, undefined, NaN のいずれかである。
断片をその JavaScript の値に変えたあと、同じ値を取り除く作業を四つの等価の規則で別々に行え。
規則は順に a == b、a === b、Object.is(a, b)、そして Set が使う等価(new Set に入れたとき一つにまとまるか)である。
値を前から一つずつ見ていき、それまでに残した値のうちその規則で等しいものが一つもなければ残し、あれば捨てる。
規則ごとに一行に、残した値の個数と、残した値の位置(1 から数えた入力の順)を小さいものから空白で区切って出力せよ。

入力
1 ≤ n ≤ 2000。
整数は -1000 以上 1000 以下で、先頭に余分な 0 は付かない(-0 は出ることがある)。
引用符の中の文字列は空か、数字 1〜4 文字(0 で始まることがある)か、英小文字 1〜5 文字である。
出力
四行(==、===、Object.is、Set の順)
例 1 · stdin
8
0 -0 NaN NaN "0" null undefined false
stdout
4 1 3 4 6
7 1 3 4 5 6 7 8
7 1 2 3 5 6 7 8
6 1 3 5 6 7 8
例 2 · stdin
3
"" 0 "0"
stdout
2 1 3
3 1 2 3
3 1 2 3
3 1 2 3
main.py
入力(stdin)

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

$ 準備完了

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

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

この問題は、同じかを測る方法が四つ(==・===・Object.is・Set)もある JavaScript と TypeScript だけを受け付けます(Python の ==・is、Java の ==・equals、C# の ==・Equals は型をこのように変換せず、Craykon にはこうした比較がありません)。
まず断片を値に変える必要があります。
文字列のままだと 0 と -0、5 と "5" はただの別の文字列です。
四つの規則は NaN と 0/-0 で分かれます。
=== では NaN が自分自身とも等しくないので、すべての NaN が残ります。
Object.is は NaN どうしを等しいとみなしますが 0 と -0 は区別し、Set・Map・includes が使う SameValueZero は NaN どうしも 0 と -0 も等しいとみなします。
== は null と undefined を互いにだけ等しいとみなし(null == 0、null == false は偽)、推移的でもありません("" == 0 で 0 == "0" なのに "" == "0" は偽)。
そのため == の行は、どの値を先に残したかで変わります。
落とし穴は三つ:① indexOf は === で探すので NaN を見つけられず、includes は SameValueZero なので見つけられます。
そのため indexOf で Set をまねると NaN が重ねて残り、includes で Object.is の代わりをすると -0 が 0 に埋もれます。
② 普通のオブジェクト {} を集合のように使うと、キーが文字列に変わるので 5 と "5"、null と "null" が同じキーになります。
③ JSON.stringify をキーにすると NaN が "null" になって null と混ざり、-0 も "0" になります。
TypeScript は string と number に絞り込まれた二つの値を == で比べると TS2367 エラーを出すので、合併型(string | number | boolean | null | undefined)で受け取ります。
実行結果は JavaScript とまったく同じです。

JavaScriptmain.js · 41 行
const lines = require("fs").readFileSync(0, "utf8").split("\n"); // 문자열 토막 하나를 JavaScript 값으로 바꾼다function parse(token) {  if (token.startsWith('"')) return token.slice(1, -1); // "5" → 문자열 5  if (token === "true") return true;  if (token === "false") return false;  if (token === "null") return null;  if (token === "undefined") return undefined;  if (token === "NaN") return NaN;  return Number(token); // -0 도 그대로 -0} const values = lines[1].trim().split(" ").map(parse); // 앞에서부터 보며, 이미 남긴 값 가운데 같은 것이 없으면 남긴다. 남긴 자리(1부터)를 돌려준다function keepFirst(same) {  const kept = [];  values.forEach((v, i) => {    if (!kept.some((j) => same(values[j], v))) kept.push(i);  });  return kept.map((i) => i + 1);} // Set은 SameValueZero: NaN끼리 같고, 0과 -0도 같다function keepFirstBySet() {  const seen = new Set();  const kept = [];  values.forEach((v, i) => {    if (!seen.has(v)) { seen.add(v); kept.push(i + 1); }  });  return kept;} const results = [  keepFirst((a, b) => a == b),  keepFirst((a, b) => a === b),  keepFirst(Object.is), // NaN끼리 같고, 0과 -0은 다르다  keepFirstBySet(),];console.log(results.map((kept) => [kept.length, ...kept].join(" ")).join("\n"));
TypeScriptmain.ts · 45 行
import { readFileSync } from "node:fs"; type Value = string | number | boolean | null | undefined; // 문자열 토막 하나를 JavaScript 값으로 바꾼다function parse(token: string): Value {  if (token.startsWith('"')) return token.slice(1, -1);  if (token === "true") return true;  if (token === "false") return false;  if (token === "null") return null;  if (token === "undefined") return undefined;  if (token === "NaN") return NaN;  return Number(token); // -0 도 그대로 -0} const lines: string[] = readFileSync(0, "utf8").split("\n");const values: Value[] = lines[1].trim().split(" ").map(parse); // 앞에서부터 보며, 이미 남긴 값 가운데 같은 것이 없으면 남긴다. 남긴 자리(1부터)를 돌려준다function keepFirst(same: (a: Value, b: Value) => boolean): number[] {  const kept: number[] = [];  values.forEach((v, i) => {    if (!kept.some((j) => same(values[j], v))) kept.push(i);  });  return kept.map((i) => i + 1);} // Set은 SameValueZero: NaN끼리 같고, 0과 -0도 같다function keepFirstBySet(): number[] {  const seen = new Set<Value>();  const kept: number[] = [];  values.forEach((v, i) => {    if (!seen.has(v)) { seen.add(v); kept.push(i + 1); }  });  return kept;} // 매개변수가 합친 타입(Value)이라 TS도 == 를 허락한다const results: number[][] = [  keepFirst((a, b) => a == b),  keepFirst((a, b) => a === b),  keepFirst(Object.is), // NaN끼리 같고, 0과 -0은 다르다  keepFirstBySet(),];console.log(results.map((kept) => [kept.length, ...kept].join(" ")).join("\n"));