There is a file where there are many lines with a random set of letters.
Scanning each line, you need to find the number of lines that contain exactly two letters of any letter, and then separately count the lines that contain exactly three letters of any letter. You need to get the result of multiplying the rows from the first case by the number of rows in the second.
How can I optimize this code or make it better??
Strings in file example (we need sort it all and file can has infiniy amout of strings) : "prtkqyluibmtcwqaezjmhgfndx
prtkqylusbsmcwvaezjmhgfndt
prgkqyluibsocwvamzjmhgkndx
prjkqyluibsocwvahzjmhgfnsx
prtkqylcibsocwvzezjohgfndx
prtkqyluiksocwziezjmhgfndx
prikqyluiksocwvaezjmkgfndx
prtkgyluibsocwvwezjehgfndx
prtkqyluiysocwvaezjghxfndx
prtkqwluibsoxwvaezjmhgfhdx
prtkqylgibsocwvabzjmhzfndx
prtknyltibnocwvaezjmhgfndx
prdkqyluibrocwvaezjmhgnndx
prtwqyluibsoctvcezjmhgfndx
mrtkqyluibgocwvakzjmhgfndx"
My code:
function solution(arrOfStrings) {
let sumOfTwo = 0;
let sumOfThree = 0;
for (const string of arrOfStrings) {
const chars = {};
for (const char of string) {
chars[char] = (chars[char] || 0) + 1;
}
console.log(chars);
if (
Object.entries(chars)
.filter((char) => char[1] == 2)
.map((char) => char[0]).length > 0
) {
sumOfTwo += 1;
}
if (
Object.entries(chars)
.filter((char) => char[1] == 3)
.map((char) => char[0]).length > 0
) {
sumOfThree += 1;
}
}
return sumOfTwo * sumOfThree;
}
let fs = require("fs");
const text = fs.readFileSync("./someTextFile.txt").toString("utf-8");
const arr = text.split("\n");
solution(arr);
Here is one of possible optimizations:
function solution(arrOfStrings) {
const out = arrOfStrings.reduce((o, s) =>
(o[s.length]++ || (o[s.length] = 1), o), {});
return (out[2] ?? 0) * (out[3] ?? 0);
}
let fs = require("fs");
const text = fs.readFileSync("./someTextFile.txt").toString("utf-8");
const arr = text.split("\n");
solution(arr);
Related
Example:
aza asssa axxxa rrra -> a!a a!!!a a!!!a rrra
So far I've come up with this solution:
const argument = "aza asssa axxxa rrra";
const amount_of_spaces = [...argument].filter(x => x === " ").length;
let j = 0;
const argument__clone = [...argument];
const space__indices = [];
function do__stuff() {
while (j < amount_of_spaces) {
space__indices.push(argument__clone.indexOf(" ") + j);
argument__clone.splice((argument__clone.indexOf(" ")), 1);
j++;
do__stuff();
}
};
do__stuff();
const words = [];
let word = '';
for (let i = 0; i < argument.length; i++) {
if (!(space__indices.includes(i))) {
word += argument[i];
}
else {
words.push(word);
word = '';
}
}
words.push(word);
let new__word = '';
const new__words = [];
const words__static = [];
for (i of words) {
if (i[0] === 'a' && i[i.length - 1] === 'a') {
for (let j = 1; j < i.length - 1; j++) {
new__word += "!";
}
new__words.push(new__word);
new__word = '';
}
else {
words__static.push(i);
}
}
new__words.map(i => "a" + i + "a");
console.log(new__words);
console.log(words__static);
So one array stores the indices of spaces and the other one stores the words from the given string. We can separate the words because we know when one ends because we have the array with space indices. Then we check for each word whether it starts with 'a' and ends with 'a'. If the requirements are met we change all the letters within the word for "!" (excluding the very first and the very last ones). If the requirements are not met we store the word into the other array.
Eventually we have two arrays that I want to concatenate into one. The problems is if I was given something like this:
aza asssa rrra axxxa
It wouldn't have worked because of the order
Is there any better solution?
A regular expression would be simpler. Match an a after a word boundary, match more non-space characters, and finally match another a followed by a word boundary.
const input = 'aza asssa axxxa rrra';
const output = input.replace(
/(?<=\ba)\S+(?=a\b)/g,
interiorWord => '!'.repeat(interiorWord.length)
);
console.log(output);
For a more manual approach, split the input by spaces so you have an array of words, then for each word, check if it begins and ends with an a - if so, construct a new word by checking the old word's length. Then turn the array back into a single string.
const input = 'aza asssa axxxa rrra';
const words = input.split(' ');
const replacedWords = words.map(word => (
word[0] === 'a' && word[word.length - 1] === 'a' && word.length >= 3
? 'a' + '!'.repeat(word.length - 2) + 'a'
: word
));
const output = replacedWords.join(' ');
console.log(output);
I got this one that looks hairy to me, but I'm confident you guys can crack it while having fun.
The problem:
Check of Company exists in the range
If not, get the latest ID prefix, which looks like AA, AB, etc
Generate a new prefix, which would be the following, according to item above: AC
If that company occurs more than once, then increment the ID number sufix XX001, XX002, etc.
This is what I've come up with so far:
function generateID() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const clientSheet = ss.getSheetByName('Clients');
const dataRng = clientSheet.getRange(8, 1, clientSheet.getLastRow(), clientSheet.getLastColumn());
const values = dataRng.getValues();
const companies = values.map(e => e[0]);//Gets the company for counting
for (let a = 0; a < values.length; a++) {
let company = values[a][0];
//Counts the number of occurrences of that company in the range
var companyOccurrences = companies.reduce(function (a, b) {
return a + (b == company ? 1 : 0);
}, 0);
if (companyOccurrences > 1) {
let clientIdPrefix = values[a][2].substring(0, 2);//Gets the first 2 letters of the existing company's ID
} else {
//Generate ID prefix, incrementing on the existing ID Prefixes ('AA', 'AB', 'AC'...);
let clientIdPrefix = incrementChar(values[a][2].substring(0,1));
Logger.log('Incremented Prefixes: ' + clientIdPrefix)
}
}
}
//Increment passed letter
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
function incrementChar(c) {
var index = alphabet.indexOf(c)
if (index == -1) return -1 // or whatever error value you want
return alphabet[index + 1 % alphabet.length]
}
...and this is borrowing from tckmn's answer, which deals with one letter only.
This is the expected result:
This is the link to the file, should anyone want to give it a shot.
Thank you!
In your situation, how about the following modification?
Modified script:
// Please run this function.
function generateID() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Clients');
const dataRng = sheet.getRange(8, 1, sheet.getLastRow() - 7, 1);
const values = dataRng.getValues();
let temp = "";
let init = "";
let count = 0;
const res = values.map(([a], i) => {
count++;
if (temp != a) {
count = 1;
temp = a;
init = i == 0 ? "AA" : wrapper(init);
}
return [`${init}${count.toString().padStart(3, "0")}`];
});
console.log(res)
sheet.getRange(8, 4, res.length, 1).setValues(res);
}
//Increment
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
function incrementChar(c) {
var index = alphabet.indexOf(c)
if (index == -1) return -1 // or whatever error value you want
return alphabet[index + 1 % alphabet.length]
}
// I added this function.
function wrapper(str) {
const [a, b] = [...str];
const r1 = incrementChar(a);
const r2 = incrementChar(b);
return (r2 ? [a, r2] : (r1 ? [r1, "A"] : ["over"])).join("");
}
In this modification, I added a wrapper function. This wrapper function uses your showing script of incrementChar.
When this script is run to your sample Spreadsheet, console.log(res) shows [["AA001"],["AA002"],["AA003"],["AA004"],["AA005"],["AB001"],["AB002"],["AB003"],["AC001"]]. And this value is put to the column "D".
Note:
This modified sample is for your provided Spreadsheet. So please be careful this.
Reference:
map()
I'm starting my adventure with javascript and i got one of first tasks.
I must create function that count letter that most occur in string and write this in console.
For example:
var string = "assssssadaaaAAAasadaaab";
and in console.log should be (7,a) <---
the longest string is 7 consecutive identical characters (yes, before count i use .toLowerCase();, because the task requires it)
So far I have it and I don't know what to do next.
Someone want to help?
var string = "assssssadaaaAAAasadaaab";
var string = string.toLowerCase();
function writeInConsole(){
console.log(string);
var count = (string.match(/a/g) || []).length;
console.log(count);
}
writeInConsole();
One option could be matching all consecutive characters using (.)\1* and sort the result by character length.
Then return an array with the length of the string and the character.
Note that this will take the first longest occurrence in case of multiple characters with the same length.
function writeInConsole(s) {
var m = s.match(/(.)\1*/g);
if (m) {
var res = m.reduce(function(a, b) {
return b.length > a.length ? b : a;
})
return [res.length, res.charAt(0)];
}
return [];
}
["assssssadaaaAAAasadaaab", "a", ""].forEach(s => {
s = s.toLowerCase();
console.log(writeInConsole(s))
});
Another example when you have multiple consecutive characters with the same length
function writeInConsole(s) {
let m = s.match(/(.)\1*/g);
if (m) {
let sorted = m.sort((a, b) => b.length - a.length)
let maxLength = sorted[0].length;
let result = [];
for (let i = 0; i < sorted.length; i++) {
if (sorted[i].length === maxLength) {
result.push([maxLength, sorted[i].charAt(0)]);
continue;
}
break;
}
return result;
}
return [];
}
[
"assssssadaaaAAAasadaaab",
"aaabccc",
"abc",
"yyzzz",
"aa",
""
].forEach(s => {
s = s.toLowerCase();
console.log(writeInConsole(s))
});
I'm no sure if this works for you:
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source)
if (c == '/') count++;
The answer given by using regular expressions is more succinct, but since you say you are just starting out with programming, I will offer a verbose one that might be easier to follow.
var string = "assssssadaaaAAAasadaaab";
var string = string.toLowerCase();
function computeLongestRun(s) {
// we set up for the computation at the first character in the string
var longestRunLetter = currentLetter = string[0]
var longestRunLength = currentRunLength = 1
// loop through the string considering one character at a time
for (i = 1; i < s.length; i++) {
if (s[i] == currentLetter) { // is this letter the same as the last one?
currentRunLength++ // if yes, reflect that
} else { // otherwise, check if the current run
// is the longest
if (currentRunLength > longestRunLength) {
longestRunLetter = currentLetter
longestRunLength = currentRunLength
}
// reset to start counting a new run
currentRunLength = 1
currentLetter = s[i]
}
}
return [longestRunLetter, longestRunLength]
}
console.log(computeLongestRun(string))
The two tests that aren't passing are as follows I'm having trouble figuring out how to implement this in code and pass them.
When the decimal element is clicked, a "." should append to the currently displayed value; two "." in one number should not be accepted
If 2 or more operators are entered consecutively, the operation performed should be the last operator entered (excluding the negative (-) sign.
I've used 3 callback functions to come this far and have a calculator in working order.
Any ideas? Thanks in advance.
Here is the codepen link:
https://codepen.io/nezmustafa123/pen/oNXwxmo
The javscript code is here.
//start with string
var tempMem = "";
const display = document.querySelector('#display');
document.querySelectorAll('[data-value]').forEach(el => {
el.onclick = e => {
if(display.innerText === "0") {
display.innerText = el.dataset.value
} else {
display.innerText += el.dataset.value;
}
}
})
document.querySelector('#equals').onclick = () => {
let result = eval(display.innerText);
display.innerText = result;
}
document.querySelector('#clear').onclick = () => {
display.innerText = 0;
}
It might be too late but just it helps some-else. I am at the moment not yet conversant with JS DOM and therefore I will provide my following solution in simple JS code.
When the decimal element is clicked, a "." should append to the currently displayed value; two "." in one number should not be accepted
use the following function to process the input to reduce multiple 0s or decimals to one 0 or one decimal
const reduceMultipleZerosAndDecimalsToOne = (input) => {
const processedInput = [];
for (let i = 0; i < input.length; i++) {
const item = input[i];
if ((item.match(/[.]/g) || []).length > 1) {
// if item has multiple decimals between numbers 5.5.5
if (item.match(/^[0-9]\.[0-9]\.[0-9]$/)) {
const item2 = item.replace(/[.]/g, " ").trim().replace(/\s+/, "."); // replace multiple ... with one .
const item3 = item2.replace(/\s+/, ""); // replace remaning whitespace
// console.log(item3);
if (item3) {
processedInput.push(item3);
}
} else {
//if item has multiple decimals between numbers eg 5...55
// console.log(item);
const item2 = item.replace(/[.]/g, " ").trim().replace(/\s+/, "."); // replace multiple ... with one .
// console.log(item2);
if (item2) {
processedInput.push(item2);
}
}
} else if (item.match(/^0+/g)) {
const item2 = item.replace(/^0+/g, 0); // replace multiple 0ss before a number to one zero
if (item2) {
processedInput.push(Number(item2));
// console.log(item2);
}
} else if (Number(item) == 0) {
const item2 = Number(item); // convert multiple 0s to one 0
if (item2) {
processedInput.push(item2);
}
} else {
processedInput.push(item);
}
}
return processedInput;
};
If 2 or more operators are entered consecutively, the operation performed should be the last operator entered (excluding the negative (-) sign.
here too you could used a function to make input to be -input if preceded by - sign after the / or X or + or - operations. Note I have only tested the function on '5X-5' therefore adjust as appropriate
let regex1;
let regex2;
let unWanted;
let wanted;
// a function to make input to be -X if preceded by - sign after /X+-
const correctFormatNegativeNumbers = (input, clickedMethods) => {
const regex1 = /[0-9],[\/|X|+|-],-,[0-9]/g; // test if input has negative number and is preceded with /X+-
const regex2 = /^(-,[0-9],[\/|X|+|-],[0-9])/g; // test if input has negative number and is followed with /X+-
const regex3 = /^(-,[0-9],[\/|X|+|-](.*?))/g; // test if input has a starting negative number and is followed with /X+- then anything
const regex4 = /((.*?)[\/|X|+|-],-,[0-9](.*?))/g; // test if input has negative number that is preceded with anyhting and /X+- and is followed with /X+-
if (regex3.test(input) || regex4.test(input)) {
const unWanted1 = "-,";
const wanted1 = "-";
const unWanted2 = ",-,";
const wanted2 = ",-";
const input2 = input
.slice()
.toString()
.replace(unWanted1, wanted1)
.replace(unWanted2, wanted2);
//drop - from methods
const newMethods = input2
.replace(/[0-9]|-[0-9]/g, "")
.replace(/,-,/g, ",")
.replace(/-,/g, "");
const processedItems = [input2.split(","), newMethods];
return processedItems;
// change -, input to -input
} else if (regex1.test(input)) {
console.log("Regex is regex1");
const unWanted = ",-,";
const wanted = ",-";
const input2 = input.slice().toString().replace(unWanted, wanted);
console.log(input2);
//drop - from methods
const newMethods = input2
.replace(/[0-9]|-[0-9]/g, "")
.replace(/,-,/g, ",")
.replace(/-,/g, "");
const processedItems = [input2.toString().split(","), newMethods];
return processedItems;
// change -, input to -input
} else if (regex2.test(input)) {
console.log("Regex is regex2");
const unWanted = "-,";
const wanted = "-";
const input2 = input.slice().toString().replace(unWanted, wanted);
// console.log(input2);
//drop - from methods
const newMethods = input2
.replace(/[0-9]|-[0-9]/g, "")
.replace(/,-,/g, ",")
.replace(/-,/g, "");
// console.log(newMethods);
const processedItems = [input2.split(","), newMethods];
return processedItems;
// change -, input to -input
} else if (
regex1.test(input) == false ||
regex2.test(input) == false ||
regex3.test(input) == false ||
regex4.test(input) == false
) {
console.log(input + " doesnt have regex");
// console.log(input);
const processedItems = [input.toString().split(","), clickedMethods];
return processedItems;
}
};
Write a function that returns an integer indicating number of times a group of string "pzmcb" appears in a string in no particualr orther. for example
input string 1 -> "abdpsclmhz"
output 1 -> 1
input string 2 : pzmcbcbpzmpcm
output 2: 2
I have written the code but it is not efficient and cannot handle large input string. I will appreciate it if an efficent way of writing this function can be provided
'use strict';
//pmzcbpmzcbpmz [0 -4] [5 - 9] returns 2
function matchGroup(word) {
let regex = /[pzmcb]/g
let stringArray = word.match(regex);
//console.log(stringArray);
let cloneArray = [...stringArray];
let stored = [];
let searchString = "";
let secondString = "";
let temp = "";
let tempArray = [];
stringArray.forEach(item => {
if (cloneArray.indexOf(item) >= 0 && searchString.indexOf(item) === -1) {
searchString += item;
if (searchString.length === 5) {
stored.push(searchString);
searchString = "";
}
} else if(secondString.indexOf(item) === -1){
secondString += item;
if (secondString.length === 5) {
stored.push(searchString);
secondString = "";
}
}else {
temp += item;
if (temp.length === 5) {
tempArray.push(temp);
temp = "";
}
}
});
return stored.length;
// return integer
}
var paragraph = 'pzmcbpdfbcmz';
let result = matchGroup("abcdefpfklmhgzpzmcbpdfbcmzjklmoplzdsaklmcxheqgexcmpzdhgiwqertyhgdsbnmkjilopazxcsdertijuhgbdmlpoiqarstiguzcmnbgpoimhrwqasfgdhuetiopmngbczxsgreqad");
console.log(result);
I expect that the matchGroup function to return exact integers for large inputs
I'd build up a map of character counts:
function countChars(str) {
const count = {};
for(const char of str) count[char] = (count[char] || 0) + 1;
return count;
}
Now we can easily build up count maps of the string to find and the source:
const toFind = countChars("pzmbc"),
source = countChars("pzmcbpdfbcmz");
Now we can find the smallest relationship of chars to find and chars that are there:
const result = Math.min(
...Object.entries(toFind).map(([char, need]) => Math.floor((source[char] || 0) / need))
);
function countChar(char, string) {
return (string.match(new RegExp(char, "g")) || []).length;
}
function countDistinctSubstring(sub, string) {
firstChars = {};
for (i = 0; i < sub.length; i++) {
if (sub[i] in firstChars)
firstChars[sub[i]]++;
else
firstChars[sub[i]] = 1;
}
return Math.min(...Object.keys(firstChars).map(key => Math.floor(countChar(key, string) / firstChars[key])));
}
> countDistinctSubstring("pzmcb", "abdpsclmhz");
< 1
> countDistinctSubstring("pzmcb", "pzmcbcbpzmpcm");
< 2
> countDistinctSubstring("pzmcbpdfbcmz", "abcdefpfklmhgzpzmcbpdfbcmzjklmoplzdsaklmcxheqgexcmpzdhgiwqertyhgdsbnmkjilopazxcsdertijuhgbdmlpoiqarstiguzcmnbgpoimhrwqasfgdhuetiopmngbczxsgreqad");
< 3
I can't tell for sure, but I think this is what you are looking for. It counts the number of occurrences of each letter in the small string, then finds the minimum ratio of occurrences in the large string to those in the small string for each character. This minimum ratio is the maximum number of distinct times the small string can be composed of letters from the larger one.
Note that this answer was used in making the countChar function.