I am learning JavaScript, and my friend recommended open Kattis to solve tasks. (I see now that it might not be the best for JS).
Anyway, I am doing this challenge Sibice where you are checking if matches will fit a box. Read the challenge linked for the context.
I am solving the problem, but I am not passing any of the tests Kattis has for the problem. I use readline to take input, since you need to take the input from terminal in open Kattis.
Attached is my code. Sorry if it's messy (will take tips on that too haha), do you have any ideas? Also my first question, so I apologize if I haven't submitted it perfectly!
This is my code, and when I run it, it works fine. But Kattis is not accepting it.
Sample input and output
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(
"Enter the number of matches and the width and height of the box, separated by spaces: ",
input => {
const [numMatches, width, height] = input
.split(" ")
.map(Number);
const length = Math.sqrt(width * width + height * height);
const matchLengths = [];
const matchArray = () => {
return new Promise(resolve => {
rl.question("match length?", matchLength => {
matchLengths.push(matchLength);
resolve();
});
});
};
const askQuestion = async numMatches => {
for (i = 0; i < numMatches; i++) {
await matchArray();
}
rl.close();
};
askQuestion(numMatches).then(() => {
matchLengths.forEach(element => {
console.log(element <= length ? "DA" : "NE");
});
});
},
);
Avoid rl.question, which prints a prompt to standard out, the stream that Kattis is looking at to determine whether your program is correct. Your program should be totally silent except for printing the exact expected output, nothing more or less. Stifle your UX instincts and focus on the algorithm.
I usually recommend the following approach for solving Kattis problems in JS:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.once("line", line => {
// process preamble
rl.on("line", line => {
// process line
});
rl.on("close", () => {
// print result of computation that requires all lines
});
});
However, for this problem, each test case can be computed and printed after each line, so you can skip the close listener and array:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.once("line", line => {
const [n, w, h] = line.split(" ").map(Number);
const length = Math.sqrt(w * w + h * h);
rl.on("line", line => console.log(line > length ? "NE" : "DA"));
});
If you really want to use promises, this was also accepted:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const read = () => new Promise(r => rl.once("line", line => r(line)));
(async () => {
const [n, w, h] = (await read()).split(" ").map(Number);
const length = Math.sqrt(w * w + h * h);
for (let i = 0; i < n; i++) {
console.log((await read()) > length ? "NE" : "DA");
}
})();
That said, I vaguely recall running into issues with promisification on other Kattis problems, and promises don't seem to offer much of an elegance boost here anyway, so I'd stick to plain callbacks.
As an aside, your for (i = 0; i < numMatches; i++) { should use let i to avoid introducing a globally-scoped variable that can easily lead to bugs.
See also:
Getting input in Kattis challenges - readline js
How to line split in Kattis problem-solving?
Regarding code formatting, the standard is Prettier, which you can install locally or use in their online playground. I already applied it to your post.
Related
I recently wrote a solution for an interview question which was rejected. Please analyze my solution and advice? is it not efficient to handle large files?
Problem
Given a data file containing scored records, in your favorite programming language, write a program to output the N highest record IDs ordered by descending
score. The output should be well-formed JSON. Consider giving thought to the
resource efficiency of your solution.
DATA File
The input data file is a series of key-value pairs (one per line) with the format
:
3830591998918656: {"id":"2208ef95-355c-53a6-96bc-206a4831f2fe","data":"Tu"}
548113328635904: {"id":"d5887987-bf5d-5813-b524-722ffff11882","data":"Vubiteh hone na dabupkof tetnut."}
4322085113430016: {"id":"8f5330b9-0279-5a67-aee3-d6acf221a53a","data":"Losu nihpe upaveitpse teblujnis."}
6348702421614592: {"id":"1fef0dbc-c75b-5959-835f-80e5f15b6da1","data":"Riliv kaliku laiza zen daze ."}
can be upto 100k lines or more
Success Conditions
Upon successful running, your solution should exit with exit code 0. If the input
data file is not valid, your solution should exit with code 2, while if the input
file is not found, your solution should exit with code 1. Empty lines in the input
file should be ignored rather than treated as invalid input.
My solution
highest.js the intry point
{
let argv = require("yargs").argv;
const yargs = require("yargs");
const handler = require("./handler");
const fileName = argv._[0];
const numberOfEntries = argv._[1];
if (!fileName || !numberOfEntries) {
console.log("Command Incorrect : --use node highest <filename> <count>");
} else {
handler.getHighestScore(fileName, numberOfEntries);
}
}
Handler.js
{
const path = require("path");
const fs = require("fs");
const es = require("event-stream");
let ln = 0;
const scoreArray = [];
exports.getHighestScore = (fileName, numberOfEntries) => {
const filePath = path.join(path.dirname(require.main.filename), fileName);
fs.stat(filePath, function (err, stat) {
if (stat && stat.isFile()) {
let s = fs
.createReadStream(filePath)
.pipe(es.split())
.pipe(
es
.mapSync(function (line) {
s.pause();
if (ln < parseInt(numberOfEntries)) {
if (line.trim().length > 0) {
let score = line.slice(0, line.indexOf(":"));
let secondPart = line.slice(line.indexOf(":") + 1);
let jsonObject = JSON.parse(secondPart);
if (
jsonObject.hasOwnProperty("id") &&
jsonObject.id.trim().length > 0
) {
let outputObject = { score: score, id: jsonObject.id };
scoreArray.push(outputObject);
} else {
process.stdout.write(
"***********File Invalid - ID Mssing************* code(2)"
);
process.exit(2);
}
}
}
if (ln == parseInt(numberOfEntries)) {
s.end();
} else {
ln += 1;
}
s.resume();
})
.on("error", function (err) {
process.stdout.write(
"***********Error while reading file************* code(2)"
);
process.exit(2);
})
.on("end", function () {
let arr = scoreArray.sort((a, b) => (b.score > a.score ? 1 : -1));
process.stdout.write(
"TOTAL LINES READ = " +
ln +
" TOTAL OUTPUT = " +
arr.length +
"\n"
);
process.stdout.write(JSON.stringify(arr));
process.stdout.write("\n");
process.exit(0);
})
);
} else {
process.stdout.write("***********FILE NOT FOUND************* code(1)");
process.exit(1);
}
});
};
}
Any advice and criticism about solution is appreciated
You are reading the entire input file into memory and storing everything. A space efficient way to do things is to stream the input one line at a time, parse it, check to see if it contains an N-highest score. If so, add it to the N-highest data structure. If not, skip it. Only retain in memory the N-highest data as you go through the whole file. This seems to be the main point that your code misses.
For efficiency reasons, this does an insertion sort into the highest array so it isn't constantly resorting the entire array every time you add a value.
Here's an implementation of that in nodejs:
const readline = require('node:readline');
const fs = require('node:fs');
// sample input data per line
// 3830591998918656: { "id": "2208ef95-355c-53a6-96bc-206a4831f2fe", "data": "Tu" }
// 548113328635904: { "id": "d5887987-bf5d-5813-b524-722ffff11882", "data": "Vubiteh hone na dabupkof tetnut." }
// 4322085113430016: { "id": "8f5330b9-0279-5a67-aee3-d6acf221a53a", "data": "Losu nihpe upaveitpse teblujnis." }
// 6348702421614592: { "id": "1fef0dbc-c75b-5959-835f-80e5f15b6da1", "data": "Riliv kaliku laiza zen daze ." }
const scoreRegex = /^\s*(\d+):\s*/;
function parseLine(line) {
// ok to skip empty lines
if (line.trim() === "") return null;
const result = {};
// parse leading digits
const scoreMatch = line.match(scoreRegex);
if (!scoreMatch) throw new Error("Missing score at beginning of line");
result.score = BigInt(scoreMatch[1], 10);
const remainingLine = line.slice(scoreMatch[0].length);
result.info = JSON.parse(remainingLine);
if (typeof result.info.id !== "string" || result.info.id === "") {
throw new Error("Missing valid id value");
}
return result;
}
// input and output files
const fileInput = "input.txt";
const fileOutput = "output.txt";
const howManyHighest = 2;
const highestScores = [];
function getLowest() {
return highestScores[highestScores.length - 1].score;
}
// do an insertion sort into the highestScores array
// highest score record first
// maintain length at no more than howManyHighest
function insertHighest(val) {
let inserted = false;
// for performance reasons, only search through the highestScores
// list if this new score is higher than the lowest score we have in the list so far
if (highestScores.length && val.score > getLowest()) {
for (let [index, item] of highestScores.entries()) {
if (val.score > item.score) {
// insert it into this position in the array, pushing the others up
highestScores.splice(index, 0, val);
inserted = true;
break;
}
}
}
if (inserted) {
// trim overflow, if any
if (highestScores.length > howManyHighest) {
highestScores.pop();
}
} else {
// didn't insert it, see if we aren't full yet
if (highestScores.length < howManyHighest) {
highestScores.push(val);
}
}
}
const rl = readline.createInterface({
input: fs.createReadStream(fileInput),
crlfDelay: Infinity
});
rl.on('error', err => {
if (err.code === 'ENOENT') {
process.exit(1);
} else {
console.log(err);
// some sort of read error
process.exit(2);
}
}).on('line', line => {
try {
const data = parseLine(line);
if (data) {
insertHighest(data);
}
} catch (e) {
console.log(e);
console.log(`Invalid line: ${line}`);
process.exit(2);
}
}).on('close', () => {
// generate array of highest scores
const output = highestScores.map(item => item.info.id)
console.log(output);
fs.writeFile(fileOutput, JSON.stringify(output), err => {
if (err) {
console.log(err);
// output write error
process.exit(3);
} else {
// all done, successfully
process.exit(0);
}
});
});
I ran into a few unspecified implementation questions, which if you bring up during your answer will show you've fully understood the problem.
What is the max score value that can exist in this data? This determines if we can use a Javascript number type or whether BigInt needs to be used to handle arbitrarily large numbers (at a cost of some run-time performance). Since this was not specified, I've used BigInt parsing and comparisons here to make the score values limitless integers.
Is the output data supposed to be only an array of ids (sorted by highest score) that belong to the highest scores. Or is it supposed to be some sorted data structure that includes the score and the id and the other data? Your question does not make that entirely clear. Showing an example of the output data for N = 3 would make this clear. This code produces an output array of id values, sorted by id with highest score first.
It is not specified what the valid structure of an id property is. This code just tests for a non-empty string value.
It is not specified what should be output if there are high tie scores or if the highest N+1 scores are all the same (e.g. there's no unique N highest scores). In case of ties at the end of the high score list, this retains only the first ones encountered in the input file (up to the N we're outputing).
exports.exec = async (client, message, args, level, settings, texts) => {
const user = args[0];
const text = args[1]
// Fires Error message that the command wasn't ran correctly.
if (!user) {
return client.emit('commandUsage', message, this.help);
}
// Fires Error message that the command wasn't ran correctly.
try {
const { body } = await snekfetch.get(`https://nekobot.xyz/api/imagegen?type=${user.toLowerCase() === "realdonaldtrump" ? "trumptweet" : "tweet"}&username=${user.startsWith("#") ? user.slice(1) : user}&text=${encodeURIComponent(text)}`);
message.channel.send("", { file: body.message });
/* * * * */
As you can see in the gif, anything past the first word (it) in this case is sliced. I'm lost to as why, am I'm unsure if it's due to const text = args[1] or not.
I don't think const text = args.join (" ") would work at all, nor have I tried but fairly positive it wouldn't.
Apologies for the noobiness, I'm new to discord.js :/
It is definitely due to args[1], assuming args is an array of words used in the message, split by space, the 1 position will always contain only the second word.
What you want is something like this:
const [user, ...restArgs] = args;
const text = restArgs.join(' ');
This will take the first element as the user, and use the rest of the array for the text, not just the second element.
In my project, I need now to take an input from the keyboard.
The user must be able to enter several words and when he presses CTRL+D you exit the program and the result is displayed.
For example we can enter on the terminal :
bob
alicia
cookie
shirley
david
We have the following code :
#!/usr/bin/env node
let chunk = "";
process.stdin.on("data", data => {
chunk += data.toString();
});
process.stdin.on("end", () => {
chunk.replace(/^\s*[\r\n]/gm,"").split(/\s+/).forEach(function (s) {
process.stdout.write(
s === 'bob'
? 'boy \n'
: s === 'alicia'
? 'girl\n'
: s === 'cookie'
? 'dog \n'
: 'unknown \n');
});
});
And when we press CTRL+D we need to obtain this result :
boy
girl
dog
unknown
unknown
Can you help me please to know, how can I code in order to take the keyboard like an input?
Here is an article that explains the basics. I made an example for you down below, you can probably figure out the rest by yourself.
const readline = require('readline');
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
let input = [];
let chunk = '';
process.stdin.on('keypress', (str, key) => {
if (key.ctrl && key.name === 'd') {
//Handle exit code here
process.exit();
}
if (key.name === 'return') {
input.push(chunk.replace('\r', ''));
chunk = '';
process.stdout.write('\n');
}
chunk+=str;
process.stdout.write(str);
});
One way of doing it would to loop input until a certain input is taken. Pseudo code example:
While (x≠q){
Take input
}
EDIT: Another way, is to not use the return key to use for spacing, instead take the items in all in one input line with a separating comma or space.
var str = "123, 124, 234,252";
var arr = str.split(",").map(val => Number(val) + 1);
console.log(arr);
I found the above from this question: How to split and modify a string in NodeJS?.
Then you can iterate over the array to find out if it's a dog or a girl!
Im trying to dig into this kattis.com challenge called counting stars using NodeJs. If you see the input example, it always start with a line of two integers, the first one represents the number of rows, the second one represents the numbers of columns per row, there are a lot of examples with C and Java, but I want to solve this with JS (Nodejs). How I can read from the terminal and iterate line by line, I tried with the next code
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
var lines = [];
rl.on('line', line => {
lines.push(line);
console.log(lines);
});
But for some reason, if I input this sample
3 10
#-########
----------
#-########
It only show me the first 3 lines, also tried with this line while (line = readline()) {}, but also not working because of readline isn't a function
In Java they use a class of Scanner(System.in);, and probably that class make things easier
You're on the right track. The missing piece is adding code to distinguish test cases. The easiest option might be to slurp all of the lines into an array, listen to "close" on the readline and then process everything synchronously in one go, chunking out tests based on the ^\d+ \d+$ pattern.
However, this approach is memory-unfriendly, so I'd prefer to only consume one test case at a time, solve it in isolation, then free the memory:
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on("line", line => {
if (/^\d+ \d+$/g.test(line)) {
if (lines.length) {
solve(lines, id++);
lines.length = 0;
}
}
else {
lines.push(line);
}
})
.on("close", () => solve(lines, id));
let id = 1;
const lines = [];
const solve = (grid, id) => {
let count = 0;
// your algorithm here
console.log(`Case ${id}: ${count}`);
};
See also:
Getting input in Kattis challenges - readline js
Kattis - readline (deleted thread--10k+ rep to view)
How to line split in Kattis problem-solving?
using readline stdin in node.js
Reading multiple lines in node.js
I have two observables and I want listen to the one that emits its first value last, is there an operator for this ? Something like that :
let obs1 = Rx.Observable.timer(500,500);
let obs2 = Rx.Observable.timer(1000,1000); // I want the values from this one
let sloth = Rx.Observable.sloth(obs1,obs2);
where the sloth observable would emit the values from obs2 as it is the one who emits its first value last.
If that's not the case, is there any other way ?
I see this possibility, for now, but I'm curious if someone find anything else :
let obs1 = Rx.Observable.timer(500,500).map(i=>`cheetah ${i}`);
let obs2 = Rx.Observable.timer(1000,1000).map(i=>`sloth ${i}`);
let sloth = Rx.Observable.merge(
obs1.take(1).mapTo(obs1),
obs2.take(1).mapTo(obs2)
).takeLast(1).mergeAll()
sloth.subscribe(data=>console.log(data))
<script src="https://unpkg.com/#reactivex/rxjs#5.3.0/dist/global/Rx.js"></script>
Edit as pointed out by #user3743222 (very nice nickname :-D ), it would not work for hot observables, but this should be fine :
let obs1 = Rx.Observable.timer(500,500).map(i=>`cheetah ${i}`).publish();
let obs2 = Rx.Observable.timer(1000,1000).map(i=>`sloth ${i}`).publish();
obs1.connect();
obs2.connect();
let sloth = Rx.Observable.merge(
obs1.take(1).map((val)=>obs1.startWith(val)),
obs2.take(1).map((val)=>obs2.startWith(val))
).takeLast(1).mergeAll();
sloth.subscribe(data=>console.log(data));
<script src="https://unpkg.com/#reactivex/rxjs#5.3.0/dist/global/Rx.js"></script>
I like your solution (though I suspect you might never see the first emitted value if you have a hot stream - if the source is cold, all seems good). Can you make a jsfiddle to check that out? If you dont miss any value, your solution is the best. If you do, it might be possible to correct it by adding the skipped value back to the source (obs1.take(1).map(val => obs1.startWith(val)).
Otherwise, for a generic lengthy solution, the key here is that you have state, so you need also the scan operator. We tag the source with an index, and we keep a state which represents the indices of the sources which already have started. When all but one have started, we know the index of the one who hasnt, and we pick only the values from that one. Please note, that this should work independently of whether the sources are hot or cold as all is made in one pass, i,e, there is no multiple subscriptions.
Rx.Observable.merge(
obs1.map(val => {val, sourceId: 1})
obs2.map(val => {val, sourceId: 2})
obsn.map(val => {val, sourceId: n})
).scan(
(acc, valueStruct) => {
acc.valueStruct = valueStruct
acc.alreadyEmitted[valueStruct.sourceId - 1] = true
if (acc.alreadyEmitted.filter(Boolean).length === n - 1) {
acc.lastSourceId = 1 + acc.alreadyEmitted.findIndex(element => element === false)
}
return acc
}, {alreadyEmitted : new Array(n).fill(false), lastSourceId : 0, valueStruct: null}
)
.map (acc => acc.valueStruct.sourceId === acc.lastSourceId ? acc.valueStruct.val : null )
.filter(Boolean)
Maybe there is shorter, I dont know. I'll try to put that in a fiddle to see if it actually works, or if you do before let me know.
How about this?
let obs1 = Rx.Observable.timer(500,500);
let obs2 = Rx.Observable.timer(1000,1000);
let sloth = Rx.Observable.race(
obs1.take(1).concat(obs2),
obs2.take(1).concat(obs1)
).skip(1);
And as a function with multiple Observables support:
let sloth = (...observables) =>
observables.length === 1 ?
observables[0] :
observables.length === 2 ?
Rx.Observable.race(
observables[0].take(1).concat(observables[1]),
observables[1].take(1).concat(observables[0])
).skip(1) :
observables.reduce((prev, current) => sloth(prev, current))[0];
I had the same issue and was able to solve it using a combination of merge and skipUntil. The pipe(last()) stops you receiving multiple results if both complete at the same time.
Try pasting the following into https://rxviz.com/:
const { timer, merge } = Rx;
const { mapTo, skipUntil, last } = RxOperators;
let obs1 = timer(500).pipe(mapTo('1'));
let obs2 = timer(1000).pipe(mapTo('2')); // I want the values from this one
let sloth = merge(
obs1.pipe(skipUntil(obs2)),
obs2.pipe(skipUntil(obs1))
).pipe(last())
sloth
Using RxJS 6 and ReplaySubject:
function lastOf(...observables) {
const replayable = observables
.map(o => {
let r = o.pipe(multicast(new ReplaySubject(1)));
r.connect();
return r;
});
const racing = replayable
.map((v, i) => v.pipe(
take(1),
mapTo(i),
))
;
return of(...racing).pipe(
mergeAll(),
reduce((_, val) => val),
switchMap(i => replayable[i]),
);
}
Use:
const fast = interval(500);
const medium = interval(1000);
const slow = interval(2000);
lastOf(fast, slow, medium).subscribe(console.log);