Using values from dictionary to code a string - javascript

I am trying to write a function -encode(text, keyText)- that takes a string, generally a word and encodes it by changing every letter of the text with that letter's place in the keyText.
If a letter occurs multiple times in the keyText, then it should use every occurrence in encoding process in the same order they occur in the keyText. If it occurs more times in the text, than it loops back to the first, so the first letter is replaced by that letter's place in the keyText, and the second occurrence of that letter is replaced by the second place in the keyText, if the letter comes a third time in the text but only occurred in the keyText twice, than it is replaced by the first place again and so on.
I also wrote a function, getKeys, that takes a string and returns a "dictionary" that gives the places in that sentence for each letter.
Let's say keyText is "And not just the men, but the women and the children, too"
getKeys(keyText) would return : {"A":[1, 29], "N":[2, 4], "D":[3], "O":[5, 25, 44 ,45] ...}
so encode("anna", keyText) should return this:
[1, 2, 4, 29]
function encode(text, keyText) {
text = text.toUpperCase();
var key = getKeys(keyText);
var list = [];
var counter = 1;
for(let char of text){
var len = key[char].length;
if(counter > len){
counter = 0;
}
list.push(key[char][counter])
counter++;
}
}return list;
The obvious problem with my code is that counter is incremented for all the letters, and not just when it needs to get the second or third value. But if i put the counter in the for loop, than it's not going to work either. I couldn't find a way to tie counter to each character of Text so that it is only incremented if that character has come up more than once.

Once getKeys is called, and you have an object, you need separate counters for each property in the object. You could use the keys object to construct another one with the same keys, but whose values are indicies indicating the current counter for that key. On each character iteration, look up the associated counter for the character, save the value to return, and increment the counter:
function encode(text, keyText) {
// var key = getKeys(keyText);
const keysObj = {"A":[1, 29], "N":[2, 4], "D":[3], "O":[5, 25, 44 ,45] };
const counters = Object.fromEntries(
Object.keys(keysObj).map(key => [key, 0])
);
return [...text.toUpperCase()]
.map((char) => {
const thisCharNumArr = keysObj[char];
const num = thisCharNumArr[counters[char]];
counters[char] = (counters[char] + 1) % thisCharNumArr.length;
return num;
})
}
console.log(encode('anna'));
console.log(encode('annannnnn'));

Related

Iterate over two separate arrays of different length and populate a third using a condition in JavaScript?

I've done something similar in the past with a nested loop, but for some reason I can't get my mind around this to get it to work. I keep running into problems that involve indexing of two separate arrays and it's been a continual stumbling block
In this case, I'm trying to sort a string. The string includes letters and numbers, the task is to sort the letters in reverse alphabetical order while keeping the numbers at their same index.
I've come up with this solution (probably not the most efficient), but can't get the sortString array to come together so that I can join the letters and numbers back into a string.
function reverse(str) {
// split the str into an array
const arr = [...str]
// converts each element in arr to a number, letters are string 'NaN'
const numArray = arr.map(x=> Number(x)).map(x=> x >= 0 ? x : String(x))
// array of the sorted letters
const letters = arr.filter(x=> !/[0-9]/g.test(x)).reverse()
// initiate empty array to hold the combined numbers and letters
let sortString = []
// Use for loop to cycle through and replace elements that are 'NaN' with letters from the letter array. All pushed to sortString.
for (let i=0; i<arr.length; i++) {
sortString.push(numArray[i] === 'NaN' ? letters[0] : numArray[i])
}
return sortString
}
reverse("ab89c") // output should be "cb89a"
You could get an array of non digits, sort it and map the splitted string with the sorted letters in places if not a digit.
const
reverse = string => {
const
array = Array.from(string),
letters = array
.filter(v => /\D/.test(v))
.sort();
return array
.map(v => /\D/.test(v) ? letters.pop() : v)
.join('');
};
console.log(reverse("ab89c"));
A slightly different approach takes a Proxy for the wanted items of sorting:
How to sort only part of array? between given indexes
Here's code that works:
Explanation of how the code works is in-line as comments.
Basically it takes the numbers out, sorts the letters in reverse, and puts the sorted letters back in the right place.
Because it's step-by-step, you could add console log on each variable after it's assigned to see how it works step by step.
function reverse(input) {
// This function from: https://stackoverflow.com/a/32567789/569302
function testIsLetter(c) {
return c.toLowerCase() != c.toUpperCase();
}
// Convert from array to string to process character by character
let inputAsArray = input.split('');
// This is where we'll lookup where to put the letters when we're done
let mapped = inputAsArray.map((s) => {
return {
s,
isLetter: testIsLetter(s)
}
})
// Now that we've captured where the letters are, take the numbers (non-letters) out
let filtered = mapped.filter(m => m.isLetter)
// Convert the object into just letters so they're easily compared when we sort
let filteredAsLettersArray = filtered.map(f => f.s)
// Sort ascending
filteredAsLettersArray.sort()
// Reverse to sort descending
filteredAsLettersArray.reverse()
// Now we need to put the letters back.
let resultAsArray = [];
let letterIndex = 0;
mapped.forEach(m => {
// If it's a letter, we use the sorted result (incrementing letterIndex each time)
if (m.isLetter) {
resultAsArray.push(filteredAsLettersArray[letterIndex]);
letterIndex++;
} else {
// Otherwise we use the number
resultAsArray.push(m.s);
}
});
let result = resultAsArray.join('');
return result;
}
console.log(reverse("ab89c"));
console.log(reverse("1a2eb8f9c"));

Format casing of names when first letter is repeated

I have a function which takes an array of names and returns a new array with each name's first letter transformed to uppercase (and the remaining letters lowercase).
function capMe(arr) {
let newArr = [];
arr.forEach(function(name) {
name = name.replace(name[0], name[0].toUpperCase());
for(let i = 1; i < name.length; i++) {
name = name.replace(name[i], name[i].toLowerCase());
}
newArr.push(name);
});
return newArr;
}
This works for the vast majority of the time. For example, if I call the function like this:
console.log(capMe(['jACKSON', 'pAM', 'wiLliAm']));
I will receive the desired output of > Array ["Jackson", "Pam", "William"].
However, there is one case in which this function does not work. If I call the function with a name which has the first letter repeated, every letter will be lowercase except for the second occurrence of the letter.
For example, If I add gEORGANN to the previous example, I will receive this output:
> Array ["georGann", "Jackson", "Pam", "William"]
How do I solve this issue?
Your current logic is not doing what you think it is. Consider the following line which appears in the loop over each name:
name = name.replace(name[i], name[i].toLowerCase());
This is replacing the first character only which is equal to name[i] with the lowercase version of that letter. Here is how this is throwing off your logic with the input gEORGANN. First, you uppercase the first g, to get GEORGANN. Then, when the loop hits the fourth position, which is a capital G, it replaces the first G with a lowercase g. This leaves us with gEORGANN, but it is not what you intend. The reason for this is that replace just hits the first occurrence.
You might be able to remedy this by doing a replace all instead of a replace. But why not just take the first character in uppercase concatenated with the remainder of the string in lowercase:
arr = ['georGann', 'jACKSON', 'pAM', 'wiLliAm'];
newArr = [];
arr.forEach(function(name) {
newName = name.charAt(0).toUpperCase() + name.substring(1).toLowerCase()
newArr.push(newName);
});
console.log(arr);
console.log(newArr);
Here you go:
const capMe = (arr = []) => {
return arr.map((name) => {
const [first, ...rest] = name;
return `${first.toUpperCase()}${rest.join('').toLowerCase()}`;
});
}
console.log(capMe(['foo', 'foZBaz']));
Your coding logic is fine. The problem is the order of operations. Please see below;
function capMe(arr) {
let newArr = [];
arr.forEach(function(name) {
name = name.toLowerCase();
name = name.replace(name[0], name[0].toUpperCase());
newArr.push(name);
});
return newArr;
}
console.log(capMe(['gEORGANN', 'pAM', 'wiLliAm', 'AA']));

Finding a jumbled character sequence in minimum steps

There is a string consisting of all alphabets(a-z). I have to guess the jumbled sequence in minimum number of steps. After each guess, I will know if each of my character is in the right position or not.
I'm using the following approach:
Maintaining a list of indices of where each character can go
Generating a random sequence from above and updating the list on each response
Here's my code:
var validIDs = {};
function initialise() {
let indices = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
for(let i=0;i<26;i++) {
validIDs[String.fromCharCode(97+i)] = [...indices];
}
}
// response is a bool array[26]
// indicating the matching positions of pattern with the secret jumbled sequence.
function updateIDs(pattern, response) {
let index;
for(let i=0;i<pattern.length;i++) {
if(response[i]) {
validIDs[pattern[i]] = [i];
} else {
index = validIDs[pattern[i]].indexOf(i);
validIDs[pattern[i]].splice(index,1);
}
}
}
My validIDs is an object with [a-z] as keys and stores the possible positions of each character. Example: { a: [0, 1, 2], b: [3], ...and so on till 'z' }. The aim is to tighten the constraints on this object and finally arrive at the secret pattern.
I'm trying to create a valid pattern from this object without using brute force and would like to have some randomness as well. I wrote the following function to take a random index for each letter and create a sequence, but this fails if all the available indices of a letter are already taken.
function generateNewSequence() {
let sequence = [], result = [];
let rand, index = 0;
for(let letter of Object.keys(validIDs)) {
//Finding a random index for letter which is not already used
rand = Math.floor(Math.random()*validIDs[letter].length);
while(sequence.indexOf(validIDs[letter][rand]) !== -1) rand = Math.floor(Math.random()*validIDs[letter].length);
index = validIDs[letter][rand];
sequence.push(index);
result[index] = letter;
}
return result.join('');
}
Note: Another constraint is that the generated sequence should not contain any duplicates.

Splitting an array of numbers and non-numbers into two separate arrays

I'm very new to javascript and I'm trying to create a function that takes a mixed input of numbers and then loops through the array in order to sort them into two newer arrays so I can manipulate them further.
So if I have an input of
1,a,2,b,3,c,4,d,
I want to make two new arrays of
1,2,3,4 and a,b,c,d
So far I've been able to split the input string at the comma, so that I now have
1a2b3c4d
all I need to be able to do now is just separate the numbers and non-numbers. I was thinking of using two loops: one that checks
if(isNan.array[n] == True )
and then trying to find a way to store the letters into a variable using the loop and then using another loop to do so for the numbers using another if function.
Is this how I should do it, and do you have any advice on how to go about it?
Edit:
I now have this code to check for letters:
if (isNaN(splitResult[L])) {
for (i = 0; i < splitResult; i++) {
letterArray.add(splitResult[L]);
L = L + 1
When I try to output the results to a box to count letters (using the variable L), nothing shows up. I doubt I've made a new array
just for completion, split the string into array first :)
let yourString = '1a2b3c4d';
let yourArray = yourString.split('');
let letterArray = [];
let numberArray = [];
yourArray.forEach(item => {
if(isNaN(item) && typeof item === 'string'){
letterArray.push(item);
}
else if(!isNaN(item) {
numberArray.push(item);
}
});
console.log(letterArray, numberArray);
All you need to do is loop through the array, you can use the Array prototypes forEach. Or you can use the normal for loop to check through each element of the array. You can now check if each element isNaN and then push into the right array appropriately. You can use the snippet below
const yourArray = ['1','a','2','b','3','c','4','d'];
const letterArray = [];
const numberArray = [];
yourArray.forEach((eachItem) => {
if(isNaN(eachItem)){
letterArray.push(eachItem);
} else {
numberArray.push(eachItem);
}
});
console.log(letterArray, numberArray);

First common element across multiple arrays in Javascript

I need to find the first common element across a group of arrays. The number of arrays may vary, but they are always in sequential order (small->large). My arrays are all properties of myObj.
This is what I have so far:
function compare(myObj,v,j) {
if (myObj[j].indexOf(v)>-1) return true;
else return false;
}
function leastCommon ([1,5]) {
var myObj = { //This is filled by code, but the finished result looks like this
1: [1, 2,...,60,...10k]
2: [2, 4,...,60,...20k]
3: [3, 6,...,60,...30k]
4: [4, 8,...,60,...40k]
5: [5,10,...,60,...50k]
};
var key = [1,2,3,4,5]; //also filled by code
var lcm = myObj[key[key.length-1]].forEach(function(v) { //Iterate through last/largest multiple array
var j=key[key.length-2];
while (j>=0) {
if (compare(myObj,v,j)) { //check to see if it is in the next lower array, if yes, check the next one.
j--;
}
if (j>0 && (compare(myObj,v,j+1))===true) return v; //before the loop exits, return the match
}
});
return lcm;
}
I'm not sure what is wrong, but it is returning undefined.
Note: yes, I know a forEach returns undefined, and I tried modifying my code, and I get a "potential infinite loop" error from my editor. Modified code looks like this:
function leastCommon ([1,5]) {
var myObj = { //This is filled by code, but the finished result looks like this
1: [1, 2,...,60,...10k]
2: [2, 4,...,60,...20k]
3: [3, 6,...,60,...30k]
4: [4, 8,...,60,...40k]
5: [5,10,...,60,...50k]
};
var key = [1,2,3,4,5]; //also filled by code
var lcm = 0;
myObj[key[key.length-1]].forEach(function(v) { //Iterate through last/largest multiple array
var j=key[key.length-2];
while (j>=0) {
if (compare(myObj,v,j)) { //check to see if it is in the next lower array, if yes, check the next one.
j--;
}
if (j>0 && (compare(myObj,v,j+1))===true) lcm = v; //before the loop exits, set lcm = v
}
});
return lcm;
}
I would not use forEach since there is no way to exit from the method when you find the first match/failure. You would need to keep looping. Instead you should look into a regular for loop with every and indexOf. This code also assumes that the array is sorted so smallest number comes first. If not, a simple sort() with a clone of the array can solve that.
//pass in arrays, assumes array is sorted
function getFirstCommon (arrs) {
//Loop over the elements in the first array
for (var i=0; i<arrs[0].length; i++) {
//get the value for the current index
var val = arrs[0][i];
//make sure every array has the value
//if every does not find it, it returns false
var test = arrs.every( function (arr) {
//check the array to see if it has the element
return arr.indexOf(val)!==-1;
});
//If we find it, than return the current value
if (test) {
return val;
}
}
//if nothing was found, return null
return null;
}
//test numbers
var nums = [
[1,2,3,4,5,6],
[2,3,4,5,6],
[3,4,5,6],
[4,5,6,7,8,9],
[6,7,8,9]
];
console.log(getFirstCommon(nums)); //6
var nums2 = [
[1,2,3,4,5,6],
[2,3,4,5,6],
[3,4,5,6],
[4,5,6,7,8,9],
[5,7,8,9]
];
console.log(getFirstCommon(nums2)); //5
var nums3 = [
[1,2,3,4,5,6],
[7,8,9,10,11,12],
[7,8,9,10,11,12]
];
console.log(getFirstCommon(nums3)); //null
The code could be improved where it does not check itself
First of all, you do have an infinite loop. If first compare() fails, you never decrease j, and keep checking the same number.
Second: you key array never decreases, so you always compare two last arrays.

Categories

Resources