Finding a jumbled character sequence in minimum steps - javascript

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.

Related

How to check if an element is present in all of the subarrays passed into the function? JS

function intersection(...bigArr)
{
let results = [];
let compiledArr = [];
for(let i = 0; i < bigArr.length; i++) {//bigArr.length is the number of subarrays
compiledArr.push(...bigArr[i]);//takes each subarray, deconstructs it and the elements are pushed into compiledArr.
}
const frequencyObj = {};
let endBound = bigArr[0].length;
let k = 1
for(let i = 0; i < compiledArr.length; i++)
{
//if the element exists, increase its frequency by 1. If it doesn't, create it and initialize it to 1. After, check if its frequency value === bigArr.length. If so, push to results
let currentElement = compiledArr[i];
if(i === endBound)//the program has reached the next subarray
{
endBound += bigArr[k].length;
k++;
turnTrue(frequencyObj) //turn the boolean value for each property to true. This is because we are analyzing a different subarray.
}
if(!frequencyObj[compiledArr[i]])//if the element DNE in the object, then add it
frequencyObj[compiledArr[i]] = [1, false];
else if( frequencyObj[currentElement] && frequencyObj[currentElement][1] === true)//if the element already exists, we need to make sure that we only increment its frequency iff we are in a different subarray within compiledArr
{
frequencyObj[currentElement][0] += 1;
frequencyObj[currentElement][1] = false;
//check if the frequency of that element === bigArr.length, it means it appears in all subarrays.
if(frequencyObj[currentElement][0] === bigArr.length)
results.push(currentElement);
}
}
return results;
}
function turnTrue(obj)
{
for(let key in obj)
obj[key][1] = true;
}
let result = intersection([1,2,1], [4,1,3,1,4], [3, 1, 2,6]);
console.log(result);
The program above has the the purpose of outputting an array with elements that are present in all of the subarrays passed into the intersection function.
This program accounts for duplicates within one subarray. The frequency is only marked when an element appears for the first time in a subarray. For example, given the three test subarrays [1,2,1], [4,1,3,1,4], [3, 1, 2,6], any element can only have a max frequency of the number of subarrays(in this case 3). An element is only marked once per subarray.
This is the only solution I could think of and I know that there exists a more simpler solution. Can anyone rate this code and indicate what could be a better solution?
It sounds like you're asking for a basic Set intersection, which can be done using the standard JavaScript Set object. Please see Set for more info.
You could however be asking for a count of how many times each element appears, but your question isn't explicit about this (and can only be either 0 if the set is empty, or the .length of your data array)
data = [ [1,2,1], [4,1,3,1,4], [3, 1, 2,6] ]
// intersection is taken verbatim from MDN docs linked
function intersection(setA, setB) {
const _intersection = new Set();
for (const elem of setB) {
if (setA.has(elem)) {
_intersection.add(elem);
}
}
return _intersection;
}
let set = data.reduce( (a, e) => {
// test includes the possibility that the leading elements are empty
return (a && a.size)? intersection(a, new Set(e)) : new Set(e)
}, null)
console.log([...set])

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"));

Using values from dictionary to code a string

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'));

Return count of duplicates

Here is the prompt: Write a function that will return the count of distinct case-insensitive alphabetic characters and numeric digits that occur more than once in the input string. The input string can be assumed to contain only alphabets (both uppercase and lowercase) and numeric digits.
Get all non-unique values (i.e.: duplicate/more than one occurrence) in an array
Get all unique values in a JavaScript array (remove duplicates)
I used variances of the above questions/answers and tried to amend it for what I am looking for- the count of how many elements are found more than once
var arr = 'Indivisibilities';
var sorted_arr = arr.toLowerCase().split('').sort();
let count = 0;
let duplicateCount = (parm1) => {
for (var i = 0; i < sorted_arr.length - 1; i++) {
if (sorted_arr[i + 1] == sorted_arr[i]) {
count ++;
}
} return count;
}
duplicateCount(arr);
Count returns 7, what is expected is 2. Also, I would really like to avoid using a for loop. I'm hoping this can be done with .forEach or something method. ** I'm still pretty knew to code so please try not to do any one liners :) I appreciate the efficiency, but I'm still trying to understand the logic
You can use reduce and filter
Change string to lowercase
Split string by ''
Use each element as key, if it is already present increase it's count by 1 else set it to 1
Filter values which are greater than 1 to see duplicates
Get length of filtered array
var str = 'Indivisibilities';
let duplicateCount = (str) => {
let dups = str.toLowerCase().split('').reduce((op,inp)=>{
op[inp] = op[inp] || 0
op[inp]++
return op
},{})
return Object.values(dups).filter(v=>v>1).length
}
console.log(duplicateCount(str));

Getting only the match, instead of whole entry

With help of this thread I got the following piece of code, which find all occurrences of words with at least 3 letters:
arr = ["ab", "abcdef", "ab test"]
var AcceptedItems = arr.filter(function(item) {
return item.match(/[a-zA-Z]{3,}/);
});
In my case that should be abcdef and test.
But instead of the occurrences only, it gets me the whole entry of the array. So instead of just test it gets me ab test.
How can I get only the match (test), not the whole array entry.
.filter will keep an element if it matches the predicate you passed but you need to save a new value based on if that predicate is true. You could do this by first performing a map then a filter but I'd rather do this in one loop.
var AcceptedItems = [];
for (var i = 0, len = arr.length; i < len; i++) {
var match = arr[i].match(/[a-zA-Z]{3,}/);
if (match) {
AcceptedItems.push(match[0]);
}
}

Categories

Resources