Tab completion from array of strings - javascript

I am building an IRC client and I am hoping to implement a solution to tab complete names. I have a list of users in the form of an array. When the user presses the tab key it completes the username. When they press the key again it completes with the next user.
I have a working solution here, but I feel like it could be a little more optimized and terse. I would be grateful for any suggestions.
// Get Active Window
var channel = irc.chatWindows.getActive();
// Get users input
var sentence = $('#chat-input').val().split(' ');
// Get the last word in the sentence
var partialMatch = sentence.pop();
// Get list of users
var users = channel.userList.getUsers();
// Set default index for loop
var userIndex=0;
//Persist the match
//Store the partialMatch to persist next time the user hits tab
if(window.partialMatch === undefined) {
window.partialMatch = partialMatch;
} else if(partialMatch.search(window.partialMatch) !== 0){
window.partialMatch = partialMatch;
} else {
if (sentence.length === 0) {
userIndex = users.indexOf(partialMatch.substr(0, partialMatch.length-1));
} else {
userIndex = users.indexOf(partialMatch);
}
}
//Cycle through userlist from last user or beginning
for (var i=userIndex; i<users.length; i++) {
var user = users[i] || '';
//Search for match
if (window.partialMatch.length > 0 && user.search(window.partialMatch, "i") === 0) {
//If no match found we continue searching
if(user === partialMatch || user === partialMatch.substr(0, partialMatch.length-1)){
continue;
}
//If we find a match we return our match to our input
sentence.push(user);
//We decide whether or not to add colon
if (sentence.length === 1) {
$('#chat-input').val(sentence.join(' ') + ":");
} else {
$('#chat-input').val(sentence.join(' '));
}
//We break from our loop
break;
}
}

You may want to look into the trie data structure, which is excellently structured for exactly this problem. With a trie, you can list off all of the strings that start with a given prefix very efficiently and without having to look at all the words in the word list. You can also do other nice operations with the trie, such as fast lookups, fast successor and predecessor search, and fast insertion/deletion.
Hope this helps!

Related

Can anyone please show me how to use the .push() method for an array?

Ask the user to input names of people they would like to invite to a dinner party.
Each name should be added to an array.
The user can only invite a maximum of ten people. If they try to add more than 10 names, the program should state, “You have already added 10 people to your guest list.”
The program should then output the list.
I am trying to solve this task, I am not sure if I am doing it properly. I am a beginner so I will appreciate any help or hints.
{
let list = [];
list.length = 10
while(true){
let input = prompt("Add a guest");
if(input <= 10 || input == null){
break; //arr.slice(0,10)
}
list.push(String(input));
console.log(list);
}
}
Loop through; if the user inputs nothing I assume they are "done" - not stated in the requirements how to manage this.
I put this in a function to call passing the max count but that was not specifically stated here in the requirements.
Just loop until the conditions are met.
function addGuests(maxGuests = 10) {
const tooMany = "You have already added 10 people to your guest list.";
const promptText = "Add a guest";
let list = [];
let hasGuest = true;
while (list.length < maxGuests && hasGuest) {
let guests = prompt(promptText);
if (guests != null && guests.trim().length > 0) {
list.push(String(guests));
console.log("L:", list);
} else {
hasGuest = false;
}
if (list.length >= maxGuests) {
alert(tooMany);
}
}
return list;
}
let guests = addGuests(10);
console.log(guests, guests.length);
Try something like:
const readLineSync = require('readline-sync');
const arr = [];
while (arr.length < 10) {
const guest = readLineSync.question('Add a guest: ');
if (guest) {
arr.push(guest);
}
}
console.log('You have added 10 people...');
console.log(arr);
This uses the readline-sync module to get input from the user, starts off with an empty array and keeps pushing non-empty input values to it until the length of that array is 10. It then prints out the items in the array to the console.

Filter options by reading character length inside for loop

I have a widget (the widget code in the pen linked below is not the actual code, please just pay attention to the filtering function jQuery.fn.doFilterOptions(){..}).
Use case:
I have a non-native selectbox. I need to extend its functionality to accept an onclick event which allows the user to type data into the selectbox (not targeting a traditional <select>), it should filter the .options available by simply showing or hiding them based on its inner HTML value, if no match is found at any point during the loop through the string being entered by the user, I need the options to continue not being displayed.
Issue:
Right now it works 95% of the way, the only issue is that if an invalid char is found, the loop keeps checking the rest of the users entries char by char, and if the next char is a match to any of the options in the same index, it re-display's this as a valid .option.
$('.selectbox .selected').on('keyup', function(){
var theseOptions = $(this).parent('.selectbox').find('.option');
var defaultPlaceholder = $(this).data('placeholder');
var filterOptions = (function(curSelectedVal){
if (curSelectedVal === ' ' || curSelectedVal.length === 0 || curSelectedVal === defaultPlaceholder){
theseOptions.show();
}
var optionsVal;
var doInputOptionsComparison = (function(){
var invalidOption = false;
for (var letterPos = 0; letterPos < curSelectedVal.length; letterPos++){
theseOptions.each(function(optionIteration){
var thisOption = $(this);
thisOptionsVal = thisOption.html();
if (curSelectedVal.length > thisOptionsVal.length ){ // If a longer string has been input by the user than exists in the option being iterated over, hide this option
thisOption.hide();
invalidOption = true;
}
else if ((thisOptionsVal[letterPos].toLowerCase().trim() === curSelectedVal[letterPos].toLowerCase().trim()) && invalidOption === false){ // If the input string matches this option and no invalid options have been found in the letterPos prior to it, show this option
thisOption.show();
}
else { // If the string does not match any option
invalidOptionFound = true;
thisOption.hide();
}
});
}
})();
})($(this).html());
});
Here is the demo, try selecting then typing abz you will see the filter working properly.
Now erase that input data, and now type azc. You will see the abc option comes available again because the c matches in that same index (user input[i] = optionsHtml[i] = show();), resulting the the above described undesirable effect.
http://codepen.io/nicholasabrams/pen/KwwMPG?editors=001
BONUS:
Would this be easier by using regEx to do the filtering?
I managed to use a dynamic regEx filter function it it cut the code down big time! Wow what a better solution.
$.fn.filterOptionsByUserInput = function(optionSelector){
var curInput = $(this).html().trim().replace(/ /g, '').toLowerCase();
$(optionSelector).each(function(optionIndex){
var userInputRegEx = new RegExp('^'+curInput+'.*');
if ($(this).html().toLowerCase().trim().match(userInputRegEx)){
$(this).fadeIn('slow');
}
else {
$(this).fadeOut('slow');
}
});
};
http://codepen.io/nicholasabrams/pen/LEEwrm?editors=001

JavaScript Help (Loops and Arrays in Particular)

So I am doing an assignment for a required javascript class and am stuck on a couple of parts specifically. We are supposed to create a guessing game with an array where we prompt the user to guess names and if they match anything in the array to tally it up as points.
Anyway here is the main code, the part that I am stuck on is figuring out how to loop the code so when the user is prompted 3 times for a guess and each guess is taken into account
var sportsArray = ["Football","Basketball","Rollerblading","Hiking","Biking","Swimming"];
var name = prompt("Please enter your name.", "Enter Here");
var arrayGuess = prompt("Guess a sport.", "Enter Here");
var counter;
for (counter = 0; counter < sportsArray.length; counter++) {
if (arrayGuess === "Football"||"Basketball"||"Rollerblading"||"Hiking"||"Biking"||"Swimming"){
alert("Good Job");
} else {
arrayGuess;
}
}
So the goal is to prompt the user to guess a part of the original array and if they do let them know that, but if they don't take points away and make them guess again until they have guessed 3 times.
Anyway if someone could lend a hand it would be appreciated.
You cannot simultaneously compare one item to a whole bunch of things like this:
if (arrayGuess === "Football"||"Basketball"||"Rollerblading"||"Hiking"||"Biking"||"Swimming")
Instead, you have to compare it to each individual item:
if (arrayGuess === "Football"||
arrayGuess === "Basketball"||
arrayGuess === "Rollerblading"||
arrayGuess === "Hiking"||
arrayGuess === "Biking"||
arrayGuess === "Swimming")
Or, there are more effective ways to compare to multiple items such as:
if (" Football Basketball Rollerblading Hiking Biking Swimming ".indexOf(" " + arrayGuess + " ") !== -1)
Or, using an array:
if (["Football","Basketball","Rollerblading","Hiking","Biking","Swimming"].indexOf(arrayGuess) !== -1)
Or, if this comparison happened a lot, you'd build an object ahead of time and use it for a lookup:
var items = {"Football":true,"Basketball":true,"Rollerblading":true,"Hiking":true,"Biking":true,"Swimming":true};
if (items[arrayGuess] === true)
If you want to compare without regards for proper case, then you can lowercase what the user entered and compare that to lower case test values:
var items = {"football":true,"basketball":true,"rollerblading":true,"hiking":true,"biking":true,"swimming":true};
if (items[arrayGuess.toLowerCase()] === true)
FYI, it's also not clear why you're using a loop here at all. No loop is needed to prompt once and test against all the possible sports values.
If you have to cycle through an array with a loop, then you can do this:
var items = ["football","basketball","rollerblading","hiking","biking","swimming"];
var testVal = arrayGuess.toLowerCase();
var match = -1;
for (var i = 0; i < items.length; i++) {
if (testVal === items[i]) {
// found a match
match = i;
break;
}
}
if (match !== -1) {
// items[match] was the match
} else {
// no match
}
I see a couple of things wrong here, as was already mentioned, your comparison in the if statement needs to reference the variable each time it is compared. But additionally, since you are in a loop based on the length of your sportsArray variable, it would be better to not reference strings at all in the if statement, and instead do something more like the following:
if (arrayGuess === sportsArray[counter]) {
// Do stuff here
} else {
// Do other stuff here
}
Additionally, your else clause isn't going to behave quite like you are expecting it to. You are going to have to assign a new value to it, probably by way of another call to prompt. As of now you are only referencing the variable, which will do nothing. If you need to take three guesses, I would add an 'else if' clause into the mix where you get a new value for the variable, an let the else clause display a score and break out of the loop.
if (arrayGuess === sportsArray[counter]) {
// Add to the score
} else if (counter < 2) {
// We prompted for the first guess before the loop,
// so take the second and third here
arrayGuess = prompt("Guess a sport.", "Enter Here");
} else {
// Display score then break to exit the loop
break;
}

Press Certain Keys To Make Something Appear

http://jsfiddle.net/alnitak/R4rWn/
$(document).keypress(function(ev) {
if (ev.which === 97 || ev.which === 65) { // 'a' or 'A'
$('#mydiv').toggle();
}
});
I have an example up top to show what I mean, but I'm looking for something more diverse. As in if you type the word "example" all within a certain amount of time the set div will show and stay until "example" is typed again. I don't have much JavaScript knowledge so I don't know how to achieve this.
Here's an implementation I made. This is in no way the best implementation, and I'm sure there's a bug. There is the fear that as the user is typing the buffer will clear, but I'm sure you could find a way around that. But it should hopefully give you enough ideas to continue.
// Stores keys pressed in order
var buffer = [];
// Every keypress shove the key in a buffer and call the compleyKeyCheck() function which will assess the buffer
$(document).keypress(function(ev) {
buffer.push(ev.which);
console.log("Just pushed " + buffer[buffer.length - 1]);
complexKeyListen('example');
});
// Compares the buffer to the required string
function complexKeyListen(string){
var i;
for(i = 0; i < string.length; i++){
console.log("Char in buffer " + buffer[i]);
if (buffer[i] !== string.charCodeAt(i)) {
break;
}
}
if(i == string.length){
$('#mydiv').toggle();
buffer= [];
}
}
// Auto clear the buffer every ten seconds.
setInterval(function(window){
console.log('Cleared buffer');
buffer = [];}
,10000);

Javascript if value is in array else in next array

I have found a few posts on here with similar questions but not entirely the same as what I am trying. I am currently using a simple if statement that checks the data the user enters then checks to see if it starts with a number of different values. I am doing this with the following:
var value = string;
var value = value.toLowerCase();
country = "NONE";
county = "NONE";
if (value.indexOf('ba1 ') == 0 || value.indexOf('ba2 ') == 0 || value.indexOf('ba3 ') == 0) { //CHECK AVON (MAINLAND UK) UK.AVON
country = "UK";
county = "UK.AVON";
} else if(value.indexOf('lu') == 0){//CHECK BEDFORDSHIRE (MAINLAND UK) UK.BEDS
country = "UK";
county = "UK.BEDS";
}
I have about 20-30 different if, else statements that are basically checking the post code entered and finding the county associated. However some of these if statements are incredibly long so I would like to store the values inside an array and then in the if statement simply check value.indexOf() for each of the array values.
So in the above example I would have an array as follows for the statement:
var avon = new Array('ba1 ','ba 2','ba3 ');
then inside the indexOf() use each value
Would this be possible with minimal script or am I going to need to make a function for this to work? I am ideally wanting to keep the array inside the if statement instead of querying for each array value.
You can use the some Array method (though you might need to shim it for legacy environments):
var value = string.toLowerCase(),
country = "NONE",
county = "NONE";
if (['ba1 ','ba 2','ba3 '].some(function(str) {
return value.slice(0, str.length) === str;
})) {
country = "UK";
county = "UK.AVON";
}
(using a more performant How to check if a string "StartsWith" another string? implementation also)
For an even shorter condition, you might also resort to regex (anchor and alternation):
if (/^ba(1 | 2|3 )/i.test(string)) { … }
No, it doesn’t exist, but you can make a function to do just that:
function containsAny(string, substrings) {
for(var i = 0; i < substrings.length; i++) {
if(string.indexOf(substrings[i]) !== -1) {
return true;
}
}
return false;
}
Alternatively, there’s a regular expression:
/ba[123] /.test(value)
My recomendation is to rethink your approach and use regular expressions instead of indexOf.
But if you really need it, you can use the following method:
function checkStart(value, acceptableStarts){
for (var i=0; i<acceptableStarts.length; i++) {
if (value.indexOf(acceptableStarts[i]) == 0) {
return true;
}
}
return false;
}
Your previous usage turns into:
if (checkStart(value, ['ba1', ba2 ', 'ba3'])) {
country = 'UK';
}
Even better you can generalize stuff, like this:
var countryPrefixes = {
'UK' : ['ba1','ba2 ', 'ba3'],
'FR' : ['fa2','fa2']
}
for (var key in countryPrefixes) {
if (checkStart(value, countryPrefixes[key]) {
country = key;
}
}
I'd forget using hard-coded logic for this, and just use data:
var countyMapping = {
'BA1': 'UK.AVON',
'BA2': 'UK.AVON',
'BA3': 'UK.AVON',
'LU': 'UK.BEDS',
...
};
Take successive characters off the right hand side of the postcode and do a trivial lookup in the table until you get a match. Four or so lines of code ought to do it:
function getCounty(str) {
while (str.length) {
var res = countyMapping[str];
if (res !== undefined) return res;
str = str.slice(0, -1);
}
}
I'd suggest normalising your strings first to ensure that the space between the two halves of the postcode is present and in the right place.
For extra bonus points, get the table out of a database so you don't have to modify your code when Scotland gets thrown out of leaves the UK ;-)

Categories