How can we take like an input the keyboard in JS? - javascript

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!

Related

Reading very large data files using nodejs? Interview question

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).

How to find similarities between a text string and an array?

I have a string array, which I want to compare with a text string, if it finds similarities I want to be able to mark in black the similarity found within the string
example:
context.body: 'G-22-6-04136 - PatientName1'
newBody: ['G-22-6-04136 - PatientName1' , 'G-22-6-04137 - PatientName2']
When finding the similarity between the two the result would be something like this
newBody: [**'G-22-6-04136 - PatientName1'** , 'G-22-6-04137 - PatientName2']
How could I do it? Is this possible to do? In advance thank you very much for the help
const totalSize: number = this.getSizeFromAttachments(attachments);
const chunkSplit = Math.floor(isNaN(totalSize) ? 1 : totalSize / this.LIMIT_ATTACHMENTS) + 1;
const attachmentsChunk: any[][] = _.chunk(attachments, chunkSplit);
if ((totalSize > this.LIMIT_ATTACHMENTS) && attachmentsChunk?.length >= 1) {
const result = attachment.map(element => this.getCantidad.find(y => element.content === y.content))
const aux = this.namePatient
const ans = []
result.forEach(ele => {
const expected_key = ele["correlative_soli"];
if (aux[expected_key]) {
const newItem = { ...ele };
newItem["name_Patient"] = aux[expected_key]
newItem["fileName"] = `${expected_key}${aux[expected_key] ? ' - ' + aux[expected_key] : null}\n`.replace(/\n/g, '<br />')
ans.push(newItem)
}
});
let newBody: any;
const resultFilter = attachment.map(element => element.content);
const newArr = [];
ans.filter(element => {
if (resultFilter.includes(element.content)) {
newArr.push({
fileNameC: element.fileName
})
}
})
newBody = `• ${newArr.map(element => element.fileNameC)}`;
const date = this.cleanDateSolic;
const amoung= attachmentsChunk?.length;
const getCurrent = `(${index}/${attachmentsChunk?.length})`
const header = `${this.cleanDateSolic} pront ${attachmentsChunk?.length} mail. This is the (${index}/${attachmentsChunk?.length})`;
console.log('body', context.body);
// context.body is the body I receive of frontend like a string
const newContext = {
newDate: date,
amoung: amoung,
getCurrent: getCurrent,
newBody: newBody,
...context
}
return this.prepareEmail({
to: to,
subject: ` ${subject} (Correo ${index}/${attachmentsChunk?.length})`,
template: template,
context: newContext,
}, attachment);
}
I have a string array, which I want to compare with a text string, if it finds similarities I want to be able to mark in black the similarity found within the string
First of all the text will be in black by default, I think you are talking about to make that bold if matched. But again it depends - If you want to render that in HTML then instead of ** you can use <strong>.
Live Demo :
const textString = 'G-22-6-04136 - PatientName1';
let arr = ['G-22-6-04136 - PatientName1', 'G-22-6-04137 - PatientName2'];
const res = arr.map(str => str === textString ? `<strong>${str}<strong>` : str);
document.getElementById('result').innerHTML = res[0];
<div id="result">
</div>
Based on your example, it looks like the string needs to be an exact match, is that correct?
Also, "mark it in bold" is going to depend on what you're using to render this, but if you just want to wrap the string in ** or <b> or whatever, you could do something like this:
newBody.map(str => str === context.body ? `**${str}**` : str);
You can just see if string exists in the array by doing
if arr.includes(desiredStr) //make text bold

Wild card filtering

I am filtering on the cached query result to see if it has the search value.
return this.cachedResults.filter(f => f.name.toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1);
This works great if the searchvalue is exactly same as the f.name. I want to filter even if it has the partial value. Like a wild card filtering. How can I do that here?
What you're doing will match partially as well in case when f.name includes whole of searchValue doesn't matter at what position.
What you might also want is, it should match even when searchValue includes whole of f.name and not just the other way around.
return this.cachedResults.filter(f => {
return f.name.toLowerCase().indexOf(this.searchValue.toLowerCase()) !== -1)
|| this.searchValue.toLowerCase().indexOf(f.name.toLowerCase()) !== -1)
}
Also consider checking out String.prototype.includes()
Try to use regexp (below support for ? and * wildcards)
let cachedResults = ['abcdef', 'abc', 'xyz', 'adcf', 'abefg' ];
let wild = 'a?c*';
let re = new RegExp('^'+wild.replace(/\*/g,'.*').replace(/\?/g,'.')+'$');
let result = cachedResults.filter( x => re.test(x.toLowerCase()) );
console.log(result);

How to filter data with an array of strings matching at least one?

I'm quite new to ReactJS and work on a simple application which shows many different data (called apps). I want to implement a live free text search-filter. For that I'm using an Input Element, calling a JavaScript function if the value is changing. It's working quite good. But only for one string. If I want to filter for more words it's handled as an "AND" filter. But I want an "OR" filter.
I have the apps and filter them with a searchString. The User has to input at least three characters. If the user enters two words with three characters f.e. 'app' and 'red', I want to filter for the elements which has the words 'app' OR 'red' in the title. If min. one of the filter-strings matches, the app is shown. That's the plan.
I tried with .indexOf(), .includes() and nothing matches, even in the documentation I found nothing like an "OR" filter-search.
Here is my code, working for one string:
updateSearch(event) {
let searchString = event.target.value.toLowerCase()
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } ); //Only words with at least 3 chars are allowed in the array
if (searchString.length >= 3) { //Check if the minimun amount of characters is fullfilled
let allApps = this.props.applications;
let apps = allApps.filter(app =>
app.title.toLowerCase().includes(searchString)
);
this.props.updateApplications(apps);
} else {
this.clearSearch()
}
}
my Input element:
<Input
id="freeTextSearch"
className="searchInput"
onChange={this.updateSearch.bind(this)}
autoComplete="off"
action={
<Button
id="freeTextSearchButton"
name="search"
icon="search"
onClick={() => this.clearSearch()}
/>
}
placeholder="Search"
/>
Thanks for the help
Yvonne
ANSWER:
Thank you 'Silvio Biasiol'. Your Solution gave me the right hint. Now I have an 'OR' filter-search matching at least one word. The function now looks like:
updateSearch(event) {
let searchString = event.target.value.toLowerCase()
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } )
if (searchStringSplit.length >=1) { //Check if there is at least on word with tree letters
let allApps = this.props.applications
// If at least a word is matched return it!
let apps = allApps.filter(app => {
let containsAtLeastOneWord = false
searchStringSplit.forEach(searchWord => {
if (app.title.toLowerCase().includes(searchWord))
containsAtLeastOneWord = true;
})
if (containsAtLeastOneWord)
return app
}
);
this.props.updateApplications(apps)
} else { // user deletes manually every word
this.clearSearch()
}
}
Thanks at everyone
If you just want to match at least one word than it's pretty easy :)
let string = "My cool string"
let possibleStrings = [
'My cool string',
'My super cool string',
'Another',
'I am lon but sadly empty',
'Bruce Willis is better than Pokemon',
'Another but with the word string in it',
'Such string much wow cool']
// Split spaces
let searchString = string.toLowerCase().split(' ')
// Log the result, just wrap it in your react script
console.log(possibleStrings.filter(string => {
let containsAtLeastOneWord = false;
// If at least a word is matched return it!
searchString.forEach(word => {
if (string.toLowerCase().includes(word))
containsAtLeastOneWord = true;
})
if (containsAtLeastOneWord)
return string
}))
You are not using your searchStringSplit array. Using this array you could do the following:
const searchRegex = new RegExp(searchStringSplit.join("|"))
let apps = allApps.filter(app =>
searchRegex.test(app.title.toLowerCase())
);
You join your searchStringSplit array into a regex with the form term1|term2|term3... and match it aginst the title.
Another option would be to use the Array.prototype.some() function like this:
let apps = allApps.filter(app =>
searchStringSplit.some(searchString => app.title.toLowerCase().includes(searchString))
);
You fitler all your apps and for each app you check if it's title includes 'some' of the search strings.
trying to understand your code, suppose that searchString is "facebook twitter youtube"
let searchStringSplit = searchString.split(/(\s+)/).filter( function(e) { return e.trim().length >=3; } );
//searchStringSplit is like [ "facebook", "twitter", "youtube" ]
//allApps is like ["TWITTER","INSTAGRAM"]
allApps.filter(app=> searchStringSplit.includes(app.toLowerCase()))
//returning me all apps in allApps that are includes in searchStringSplit
// so return ["TWITTER"]
not sure if it's exactly what you need...if it's not please let me know so I can change the answer...

Args are being sliced after first word | Discord.js

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.

Categories

Resources