{ }codelper
equality and coercionnull valuesP0124

Four ways to be the same

The first line has n, and the second line has n values separated by single spaces.
Each value is a string token that can be written as is in JavaScript code. A token is an integer (e.g.
5, -12, -0), a string in double quotes (e.g.
"5", "", "abc"), or one of true, false, null, undefined, NaN. Turn each token into that JavaScript value, then remove duplicates separately under four equality rules. The rules are, in order, a == b, a === b, Object.is(a, b), and the equality a Set uses (whether two values merge into one when put in a new Set). Look at the values one at a time from the front.
Keep a value if none of the values kept so far is equal to it under the rule; otherwise drop it. For each rule, print on one line the number of kept values and then their positions (1-based input order) from smallest, separated by spaces.

Input
1 ≤ n ≤ 2000.
Integers are between -1000 and 1000 and have no leading zeros (-0 may appear).
The string inside the quotes is empty, 1 to 4 digits (it may start with 0), or 1 to 5 lowercase English letters.
Output
four lines (==, ===, Object.is, Set, in that order)
Example 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
Example 2 · stdin
3
"" 0 "0"
stdout
2 1 3
3 1 2 3
3 1 2 3
3 1 2 3
main.py
Input (stdin)

When you run, these lines are fed to input()/stdin in order (the program doesn't stop to ask).

$ Ready

Open this on a computer to write and run code. On mobile you can read the problem and the solutions.

In other languagesShow solutions by language · 2 languages

This problem takes only JavaScript and TypeScript, because these two languages have four ways to measure sameness: ==, ===, Object.is and Set (Python == / is, Java == / equals and C# == / Equals do not convert between types like this, and Craykon has no such comparisons).
First turn the tokens into values.
Left as strings, 0 and -0, or 5 and "5", are simply different strings.
The four rules split on NaN and 0/-0.
Under ===, NaN is not even equal to itself, so every NaN is kept.
Object.is treats NaNs as equal but separates 0 and -0, and SameValueZero, used by Set, Map and includes, treats both NaNs and 0/-0 as equal.
== treats null and undefined as equal only to each other (null == 0 and null == false are false), and it is not transitive ("" == 0 and 0 == "0", yet "" == "0" is false).
So the == line depends on which values were kept first.
Three traps: (1) indexOf searches with === and cannot find NaN, while includes uses SameValueZero and can.
So imitating a Set with indexOf keeps repeated NaNs, and replacing Object.is with includes buries -0 under 0.
(2) Using a plain object {} as a set turns keys into strings, so 5 and "5", or null and "null", become the same key.
(3) Using JSON.stringify as the key turns NaN into "null", mixing it with null, and -0 becomes "0" too.
TypeScript reports TS2367 when two values narrowed to string and number are compared with ==, so take them as a union type (string | number | boolean | null | undefined).
The run-time result is exactly the same as JavaScript.

JavaScriptmain.js · 41 lines
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 lines
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"));