Need to generate a new string in javascript - javascript

I'm trying to solve a question to create a new compressed string. Each character has a count next to it and I was supposed to come with up a new string in alphabetical order followed by the total count of that character within the string.
For eg:
Input: "a10b1a5c1"
Output: "a15b1c1"
Input: "b10a1b5c1"
Output: "a1b15c1"
How I approached this question?
Have an object with key as char and value as count
Convert the object to string
function stringCompression(s) {
const object={}
for(var i=0; i<s.length-1; i++) {
if(s[i].match(/[0-9]/)!==null && s[i+1].match(/[a-zA-Z]/) === null) {
if(object.hasOwnProperty("s[i]")){
// Add previous and current value
} else {
object[s[i-1]]=parseInt(s[i]+s[i+1])
}
}
}
return object
}
// output should be a15b1c1
const output= stringCompression("a10b1a5c1");
const final = Object.keys(output).map(key => `${key}${output[key]}`).join("");
console.log(final)
Question:
Can't think of a way to sort alphabetically
The function I wrote doesn't handle if the character has a single digit count
Is there any other way I can optimise the solution?

You could reduce() everything to a single "counter" object, and then use Object.entries() to convert that object to an array and perform sorting, flattening and joining on that array:
const compress = (s) => Object.entries(
[...s.matchAll(/([A-Za-z])(\d+)/g)].reduce((a, [_, char, number]) => ({
...a,
[char]: (a[char] || 0) + +number
}), {})
).sort(([a], [b]) => a.localeCompare(b)).flat().join('');
console.log(compress('b10a1b5c1'));

const STRING = 'a10c8b3a4'
const isDigit = (charCode) => {
return charCode >= 48 && charCode <=57
}
const isAlphabet = (charCode) => {
return charCode >= 97 && charCode <= 122
}
const getSortedObject = (obj) => {
return Object.keys(obj).sort().reduce(
(object, key) => {
object[key] = obj[key];
return object;
}, {}
);
}
const getCompressedString = (string) => {
let obj = {}
let alphabet = ''
let digit = ''
let i = 0
while(i<string.length) {
const char = string[i]
const charCode = string.charCodeAt(i)
if(isAlphabet(charCode)) {
alphabet = char
if(!obj[alphabet]) {
obj[alphabet] = '0'
}
i++
} else if(isDigit(charCode)) {
digit = '0'
while(isDigit(string.charCodeAt(i)) && i<string.length) {
digit += string[i]
i++
}
obj[alphabet] = parseInt(obj[alphabet]) + parseInt(digit)
}
}
let compressedString = ''
Object.keys(getSortedObject(obj)).forEach(key => {compressedString += key + obj[key]})
return compressedString
}
console.log(getCompressedString(STRING))

Related

Longest prefix that match more than 50% of items in an array

Suppose I have array of strings: ["apple", "ape", "application", "item"].
I need to find the longest common prefix that matches more than 50% of the strings in that array.
For example, we got "ap" being the prefix of 3 strings in a 4 elements array -> so more than 50% of the array -> returns the prefix.
This is my attempt:
const longestPrefix = (arrStr) => {
if (arrStr.length === 0) return "";
let prefix = "";
let noPrefixMatched = {};
// loop the characters of first word
for (let i = 0; i < arrStr[0].length; i++) {
let char = arrStr[0][i];
let j = 0;
// loop all the words of the array except first one
for (j = 1; j < arrStr.length; j++) {
if (arrStr[j][i] !== char) {
// if first char not matched then set that word as notMatched
if (i === 0) {
noPrefixMatched[arrStr[j]] = true;
}
break;
}
// if the first characters are equal in all words and the loop reach the final word
if (arrStr[j][i] === char && j === arrStr.length - 1) {
prefix += char;
}
}
}
return prefix;
};
I try to get the most common prefix by vertical comparison, but it's not working with a word without any prefix in the array (like "item" in the above array). How should I do this?
One way to do this is to iterate all the words, constructing prefixes one letter at a time and counting the occurrence of each prefix as you see it. You can then filter that result based on the count being greater than 1/2 the length of the input array, and finally sort it by the prefix length descending, returning the first entry in the result:
const words = ["apple", "ape", "application", "item"]
const counts = words.reduce((acc, w) => {
prefix = ''
for (i = 0; i < w.length; i++) {
prefix += w[i]
acc[prefix] = (acc[prefix] || 0) + 1
}
return acc
}, {})
const bestPrefix = Object.entries(counts)
.filter(([_, v]) => v > words.length / 2.0)
.sort((a, b) => b[0].length - a[0].length)
[0][0]
console.log(bestPrefix)
The first word should not be considered special or be hardcoded in any way - it may not even be a match for the substring selected, after all.
A simple way to code this would be to iterate over all words and their possible prefixes and see which prefixes pass the test, while keeping the best one in an outer variable - then return it at the end.
const getPrefixes = str => Array.from(str, (_, i) => str.slice(0, i + 1));
const matches = (arr, prefix) => {
const matchCount = arr.reduce((a, str) => a + str.startsWith(prefix), 0);
return matchCount / arr.length > 0.5;
};
const longestPrefix = (arrStr) => {
let bestPrefix = '';
for (const str of arrStr) {
for (const prefix of getPrefixes(str)) {
if (prefix.length > bestPrefix.length && matches(arrStr, prefix)) {
bestPrefix = prefix;
}
}
}
return bestPrefix;
};
console.log(longestPrefix(["apple", "ape", "application", "item"]));
A less computationally complex but more complicated method would be to construct a tree structure of characters from each string in the input, and then iterate through the tree to identify which nodes have enough nested children, and then pick the longest such node. This has the advantage of only requiring iterating over each character of each string in the input once.
const getBestChild = (obj, totalRequired, prefix = '') => {
if (obj.count < totalRequired) return;
const thisResult = { count: obj.count, prefix };
if (!obj.children) {
return thisResult;
}
let bestChild = thisResult;
for (const [nextChar, child] of Object.entries(obj.children)) {
const result = getBestChild(child, totalRequired, prefix + nextChar);
if (result && result.prefix.length > bestChild.prefix.length) {
bestChild = result;
}
}
return bestChild;
};
const longestPrefix = (arrStr) => {
const root = {};
for (const str of arrStr) {
let obj = root;
for (const char of str) {
if (!obj.children) {
obj.children = {};
}
const { children } = obj;
if (!children[char]) {
children[char] = { count: 0 };
}
children[char].count++;
obj = children[char];
}
}
const { length } = arrStr;
const totalRequired = length % 2 === 0 ? (1 + length / 2) : Math.ceil(length / 2);
return getBestChild(root, totalRequired).prefix;
};
console.log(longestPrefix(["apple", "ape", "application", "item"]));

Return true if the string in the first element of the array contains all of the letters of the string in the second element

I tried to do this by resetting loop going trough firstword every time its letter matches with secondword letter.
function mutation(arr) {
var compare = [];
var firstword = arr[0].toLowerCase();
var secondword = arr[1].toLowerCase();
var j = 0;
for (let i = 0; i < firstword.length; i++) {
if (firstword[i] === secondword[j]) {
compare.push(secondword[i]);
i = -1;
j++;
}
}
let result = compare.join("")
if (result.length === secondword.length) {
return true;
} else {
return false;
}
}
console.log(mutation(["Noel", "Ole"]));
It works in some cases but in others, like above example, it doesn't. What seems to be the problem?
You need to compare.push(secondword[j]) instead of compare.push(secondword[i])
function mutation(arr) {
var compare = [];
var firstword = arr[0].toLowerCase();
var secondword = arr[1].toLowerCase();
var j = 0;
for (let i = 0; i < firstword.length; i++) {
if (firstword[i] === secondword[j]) {
compare.push(secondword[j]); // Correction here
i = -1;
j++;
}
}
let result = compare.join("");
if (result.length === secondword.length) {
return true;
} else {
return false;
}
}
console.log(mutation(["Noel", "Ole"]));
Also, you can consider using Array.prototype.every.
const mutation = ([first, sec]) => {
const lowerCaseFirst = first.toLowerCase();
const lowerCaseSec = sec.toLowerCase();
return Array.from(lowerCaseSec).every((ch) => lowerCaseFirst.includes(ch));
};
console.log(mutation(["Noel", "Ole"]));
If the strings are small then String.prototype.includes works fine but if they are large then you should consider using a Set.
const mutation = ([first, sec]) => {
const firstSet = new Set(first.toLowerCase());
const lowerCaseSec = sec.toLowerCase();
return Array.from(lowerCaseSec).every((ch) => firstSet.has(ch));
};
console.log(mutation(["Noel", "Ole"]));
Simple ES6 Function, we check with .every() if every characters of secondword is includes inside firstword. It return true if it does.
function mutation(arr) {
const firstword = arr[0].toLowerCase();
const secondword = arr[1].toLowerCase();
return secondword.split('').every(char => firstword.includes(char));
}
console.log(mutation(["Noel", "Ole"]));
The use of Set in SSM's answer works if you don't need to account for duplicate characters in the second string. If you do, here's an implementation that uses a Map of character counts. The map key is the character from string 1 and the value is the number of occurrences. For instance, if you want ["Noel", "Ole"] to return true, but ["Noel", "Olle"] to return false (string 1 does not contain 2 "l" characters). String 2 is then iterated through and character counts decremented if they exist. As soon as a character is not present or the count falls below 1 in the map, the function returns false.
function mutation(arr: string[]): boolean {
return s1ContainsAllCharsInS2(arr[0].toLowerCase(), arr[1].toLowerCase());
}
function s1ContainsAllCharsInS2(s1: string, s2: string): boolean {
if (s2.length > s1.length) {
return false;
}
let charCountMap: Map<string, number> = new Map<string, number>();
Array.from(s1).forEach(c => {
let currentCharCount: number = charCountMap.get(c);
charCountMap.set(c, 1 + (currentCharCount ? currentCharCount : 0));
});
return !Array.from(s2).some(c => {
let currentCharCount: number = charCountMap.get(c);
if (!currentCharCount || currentCharCount < 1){
return true;
}
charCountMap.set(c, currentCharCount - 1);
});
}
A different approach.
Mapping the characters and comparing against that map.
function mutation(arr) {
const chars = {};
for (let char of arr[0].toLowerCase()) {
chars[char] = true;
}
for (let char of arr[1].toLowerCase()) {
if (!chars[char]) {
return false;
}
}
return true;
}
console.log(mutation(["Noel", "Ole"]));
console.log(mutation(["Noel", "Oleeeeeeeeeeeee"]));
If the count also matters (your code doesn't take it into account) you can count the number of occurrences of each character and comparing these counts.
function mutation(arr) {
const chars = {};
for (let char of arr[0].toLowerCase()) {
chars[char] = (chars[char] || 0) + 1;
}
for (let char of arr[1].toLowerCase()) {
// check if chars[char] contains a (not empty == positive) count
// then decrement it for future checks
if (!chars[char]--) {
return false;
}
}
return true;
}
console.log(mutation(["Noel", "Ole"]));
console.log(mutation(["Noel", "Oleeeeeeeeeeeee"]));

How do I compare two arrays for different message outcomes?

I am trying to compare two arrays( containing 3 integers) and return a hint message array that conform to the logic
-Push “Almost” when 1 digit and position match in array
-push “not quite” when 1 digit matches but different position
-push “incorrect “ when no digits match
push “correct” When exact match
Example of arrays :
Array1 = [2,7,6]
ReferenceArray= [2,9,7]
Hint= [“Almost”, “Not Quite”];
Code I have so far:
function check( array1, referenceArray ) {
let hint=[];
for(i=0;i<referenceArray.length;i++){
for (j=0;j<Array1.length;j++){
//value and position match
if ((referenceArray[i] && reference.indexOf[i]) === (Array1[j] && Array1.indexOf[j])) {
return hint.push('almost');
}
//value matches but not position
else if(( referenceArray[i] ===Array1[j]) && !(referenceArray.indexOf[i]===Array1.indexOf[j] )){
return hint.push('not quite');
}
}// end of Array1 iteration
} // end of reference interation
// if all values and position match
if(referenceArray===Array1){
return hint.push("correct");
}
//if no values match
else if (referenceArray!==Array1){
return hintArray.push("incorrect");
}
I would use some built in Array methods to help achieve this: every(), map() and findIndex().
I generally avoid using .push() because it mutates the array. Immutable code is nice to read 😉
const check = (array, referenceArray) => {
if (array.every((val, index) => val === referenceArray[index] )) {
return ['Correct']
}
const allHints = array.map((val, index) => {
const refArrayIndex = referenceArray.findIndex(refVal => val === refVal);
if (refArrayIndex === index) {
return 'Almost'
}
if (refArrayIndex !== -1) {
return 'Not Quite'
}
return undefined
});
const hints = allHints.filter((hint) => hint !== undefined);
if (hints.length > 0) {
return hints;
}
return ['Incorrect']
};
const hints = check([2,7,6],[2,9,7]);
console.log('hints', hints)
I did this code, tell me if it works or not 😁
const array1 = [2,7,6]
const ReferenceArray = [2,9,7]
function compareArrays(arr){
let perfect = true
for(let i = 0; i < ReferenceArray.length; i++){
if(ReferenceArray[i] != arr[i]) {
perfect = false
break
}
}
if(perfect) return 'correct'
let hint = []
for(let i = 0; i < ReferenceArray.length; i++){
if(arr[i] == ReferenceArray[i]) hint.push('Almost')
else if(ReferenceArray.includes(arr[i])) hint.push('Not Quite')
}
if(hint.length > 0) return hint
return 'incorrect'
}
console.log(compareArrays(array1))

How to find the longest common prefix in an array of strings?

I'm trying to solve this using the .every method but it's not returning true and therefore it's not adding onto my string and I'm not sure why.
var longestCommonPrefix = function(arr) {
if (arr.length === 0) {
return undefined;
}
let result = '';
for (let i = 0; i < arr.length; i++) {
if (arr.every(x => arr[i].charAt(i) === x)) {
result += arr[i].charAt(i);
}
}
return result
}
console.log(longestCommonPrefix(["flower", "flow", "flight"])); //fl
You need to iterate over one string, not over the whole array: check if the first character of the string is present everywhere, then the second character, etc:
var longestCommonPrefix = function(arr) {
if (arr.length === 0) {
return undefined;
}
let result = '';
for (let i = 0; i < arr[0].length; i++) {
if (arr.every(x => x.charAt(i) === arr[0][i])) {
result += arr[i].charAt(i);
} else break;
}
return result;
}
console.log(longestCommonPrefix(["flower", "flow", "flight"])); //fl
Your use of Array.every is along the right lines. You want to check that every string in the array has the same character at position i. I think you got confused when you named the parameter x, when it is in fact a string :)
var longestCommonPrefix = function(words) {
if (words.length === 0) {
return "";
}
const letters = [];
const lengthOfShortestWord = Math.min(...words.map(word => word.length));
for (let i = 0; i < lengthOfShortestWord; i++) {
const char = words[0][i];
if (words.every(word => word[i] === char)) {
letters.push(char);
} else {
break;
}
}
return letters.join("");
}
console.log(longestCommonPrefix(["flower", "flow", "flight"])); //fl
Unless I am mistaken the longest prefix is never going to be greater than the smallest string in the array.
In this case "fl" is both the smallest string and the longest common prefix:
["flower", "fl", "flight"]
So start with finding the smallest string in arr:
let [sm] = [...arr].sort((a, b) => a.length - b.length);
Then check that all strings in arr start with sm:
arr.every(str => str.startsWith(sm));
If that isn't the case then shorten sm by one character:
sm = sm.slice(0, -1);
And keep going until you eventually find the longest prefix or sm becomes an empty string:
const prefix = arr => {
let [sm] = [...arr].sort((a, b) => a.length - b.length);
while (sm && !arr.every(str => str.startsWith(sm))) sm = sm.slice(0, -1);
return sm;
};

Dynamically update value of a key in Map in JavaScript

I currently have a function below to find the first nonrepeating letter. For example, for the string carro, that letter would be c; for the string total, that letter would be o.
I have the following code that works:
function findFirstNonrepeatedChar(str) {
const store = {};
const arr = str.split('');
arr.forEach(item => {
if(!store[item]) {
store[item] = 1;
} else {
store[item] = store[item] + 1;
}
})
for(let char in store) {
if(store[char] === 1) return char;
}
}
However, now I want to use a Map instead of just a plain object, and I'm having difficulty to update the frequency of the duplicate word like below:
function findFirstNonrepeatedChar(str) {
const store = new Map();
const arr = str.split('');
arr.forEach(item => {
if(!store.has(item)) {
store.set(item, 1);
} else {
store[item]++;
}
})
console.log(store, 'store')
for(let char in store) {
if(store[char] === 1) return char;
}
}
What would be the best way to do so?
There are 2 things here:
you set to save the key-value to store, use get to get the value by key
store.set(item, (store.get(item) || 0) + 1);
you iterate the key-value pairs of Map by for..of, not for..in
function findFirstNonrepeatedChar(str) {
const store = new Map();
const arr = str.split("");
arr.forEach((item) => {
store.set(item, (store.get(item) || 0) + 1);
});
for (let [char, occurrences] of store) {
if (occurrences === 1) {
return char;
}
}
}
console.log(findFirstNonrepeatedChar("carro"));
console.log(findFirstNonrepeatedChar("total"));
Here's how I'd do it using Array.prototype.find() if you're interested in alternative solutions.
const findFirstNonrepeatedChar = (str) => str.split('').find(
(val) => str.match(new RegExp(val, 'g')).length === 1
);
console.log(findFirstNonrepeatedChar('total'));
console.log(findFirstNonrepeatedChar("carro"));
console.log(findFirstNonrepeatedChar("aabbcc"));

Categories

Resources