Javascript: Generic get next item in array - javascript

I am trying to make a JavaScript function that will search an array of strings for a value and return the next string. For example, if an array is built such that an item is followed by its stock code, I want to search for the item and have the stock code written.
var item = (from user input); //some code to get the initial item from user
function findcode(code){
var arr = ["ball", "1f7g", "spoon", "2c8d", "pen", "9c3c"]; //making the array
for (var i=0; i<arr.lenth; i++){ //for loop to look through array
arr.indexOf(item); //search array for whatever the user input was
var code = arr(i+1); //make the variable 'code' whatever comes next
break;
}
}
document.write(code); //write the code, I.e., whatever comes after the item
(I'm sure it's obvious I'm new to JavaScript, and while this is similar to a number of other questions I found, those seemed to have more involved arrays or more complex searches. I can't seem to simplify them for my needs.)

You've almost got it right, but the syntax is arr[x], not arr(x):
index = array.indexOf(value);
if(index >= 0 && index < array.length - 1)
nextItem = array[index + 1]
BTW, using an object instead of an array might be a better option:
data = {"ball":"1f7g", "spoon":"2c8d", "pen":"9c3c"}
and then simply
code = data[name]

Cycled items from array this might be useful
const currentIndex = items.indexOf(currentItem);
const nextIndex = (currentIndex + 1) % items.length;
items[nextIndex];
The first item will be taken from the beginning of the array after the last item

Try this String.prototype function:
String.prototype.cycle = function(arr) {
const i = arr.indexOf(this.toString())
if (i === -1) return undefined
return arr[(i + 1) % arr.length];
};
Here is how you use it:
"a".cycle(["a", "b", "c"]); // "b"
"b".cycle(["a", "b", "c"]); // "c"
"c".cycle(["a", "b", "c"]); // "a"
"item1".cycle(["item1", "item2", "item3"]) // "item2"
If you want to do it the other way round, you can use this Array.prototype function:
Array.prototype.cycle = function(str) {
const i = this.indexOf(str);
if (i === -1) return undefined;
return this[(i + 1) % this.length];
};
Here is how you use it:
["a", "b", "c"].cycle("a"); // "b"
["a", "b", "c"].cycle("b"); // "c"
["a", "b", "c"].cycle("c"); // "a"
["item1", "item2", "item3"].cycle("item1") // "item2"

I think that an object could be probably a better data structure for this kind of task
items = {
ball : "1f7g",
spoon: "2c8d",
pen : "9c3c"
}
console.log(items['ball']); // 1f7g

You may pass array to function as argument and return found value from function:
var item = "spoon"; // from user input
var arr = ["ball", "1f7g", "spoon", "2c8d", "pen", "9c3c"]; //making the array
function findcode(item, arr){
var idx = arr.indexOf(item); //search array for whatever the user input was
if(idx >=0 && idx <= arr.length - 2) { // check index is in array bounds
return arr[i+1]; // return whatever comes next to item
}
return '';
}
document.write(findcode(item, arr)); //write the code, i.e., whatever comes after the item

Answer for 2022
This question came up when I was searching for a modern way to do this. I had been using the technique described in SerzN1's excellent answer without the wraparound (because I don't want that to happen). It winds up being quite a bit of code to make it safe, so I wanted something more modern.
As it turns out, there is a feature that has been available in every major browser since 2016. If someone hasn't updated their browser in six years, that's their loss, right?
ES2015 Array.find()
This function is used for finding a specific element in an array. That's exactly what we want to do here. The only problem is it doesn't maintain state for you, so you can only find a matching element, not the one after (or before, for that matter). To get around that, we use a closure.
Here's the short version:
let trigger = false;
const found = arr.find(element => trigger || (trigger = element === value) && !trigger);
You start with trigger set to false because we need to keep track of when the element is found (if at all). Then we use the Array.find() on the list we should be searching. The single argument to that function is a search function, which we define in-line as a closure so it has access to trigger.
The search function is the tricky part: element => trigger || (trigger = element === query.value) && !trigger. It might be easier to read if I break it apart into a more conventional function just so we can evaluate it. I'll describe what's happening in the comments:
function (element) {
// If trigger is true, that means the previously-evaluated element was the match.
// Therefore, we must be currently evaluating one AFTER it.
// We should match on this one.
if (trigger === true) return true;
// Then we update the value of trigger to the result of comparing the element with the search value
trigger = (element === value)
// Now we `and` it together with its negation in order to make sure
// it always returns false even when the element matches the search value
return trigger && !trigger
}
And there you have it! It only takes two lines of code to get the element after one that matches your query.
Want to see it in action? Here you go:
function showNext() {
const query = document.getElementById('query');
if (query === null) return;
const result = document.getElementById('result');
if (result === null) return;
var arr = ["ball", "1f7g", "spoon", "2c8d", "pen", "9c3c"];
// This is the important part
// Create a boolean flag that will inform the find function when it passes
// the matching element in its search
let trigger = false;
const found = arr.find(element => trigger || (trigger = element === query.value) && !trigger);
// Now `found` is equal to the array element AFTER the one you searched for
// If it is undefined, that means the one you searched for was either the last
// element in the array or it was missing.
result.innerText = `Found ${found ?? 'nothing'}`;
}
<input id="query" />
<input type="submit" value="Display next" onclick="showNext()" />
<div id="result"></div>

Related

JavaScript - IF statement inside FOR loop is outputting both options

I'm writing a function that searches an array from a prompt using an IF statement. So for I get the output I want if it is successful, but it also outputs the failure.
// The array I'm searching through
var statesArray = new Array();
statesArray['WI'] = "Wisconsin";
statesArray['MN'] = "Minnesota";
statesArray['IL'] = "Illinois";
// Now I'm trying to let the user search for the full state name from the two-letter abbreviation.
var stateSearch = prompt("enter a two letter state abbreviation")
for(var key in statesArray){
var value = statesArray[key]
if(stateSearch == key){
alert(value);
}else{
alert("try again");
}
}
So if I type "WI" in the prompt, I get "Wisconsin" and "try again".
Loop is not appropriate for this. Loop will check all values of array and check found or not for all.
var statesArray = new Array();
statesArray['WI'] = "Wisconsin";
statesArray['MN'] = "Minnesota";
statesArray['IL'] = "Illinois";
// Now I'm trying to let the user search for the full state name from the two-letter abbreviation.
var stateSearch = prompt("enter a two letter state abbreviation");
let searchObj =statesArray[stateSearch];
if(searchObj == null){
alert("try again");
}else{
alert(searchObj);
}
Your for loop isn't exiting after it has found a match. Either put it in a function and return when you find a match or break the for loop
for(var key in statesArray){
var value = statesArray[key]
if(stateSearch == key){
alert(value);
break;
}else{
alert("try again");
}
}
function searchArray(arr){
for(var key in arr){
var value = arr[key]
if(stateSearch == key){
return value;
}
}
}
Note: Array was created to store a numbered list of elements. If you need to get values by keywords, it's better idea to use a common object instead:
var statesArray = {
WI: "Wisconsin",
MN: "Minnesota",
IL: "Illinois",
null: "Cancelled!"
};
var abbr = prompt("enter a two letter state abbreviation");
alert( statesArray[abbr] || "try again" );
// will return the value, if `abbr` key exists,
// and "try again" if statesArray[abbr] returned `undefined`
Instead of using a for loop, you could use:
var index = statesArray.indexOf(stateSearch);
which will set index to -1 if the value is not found, otherwise it will be set to the position in the array that the value was found.
Like some of the other comments and answers said, you're not breaking/exiting your loop early when a match is found, which is why you are continuing to iterate through even after you find the right value. Had you searched for MI, for example, you would see:
try again
Minnesota
try again
Firstly, it's generally considered a better practice, from a performance and ease of reading standpoint, to create Array literals than to use the JavaScript new keyword to create an Array instance.
Secondly, JavaScript does not technically allow for Associative Arrays (Hashes/HashMaps) (in other words, arrays with named indices). From the MDN Developer Documentation for Arrays:
Arrays cannot use strings as element indexes (as in an associative array) but must use integers. Setting or accessing via non-integers using bracket notation (or dot notation) will not set or retrieve an element from the array list itself, but will set or access a variable associated with that array's object property collection.
In your case, I would think a simple object or Map would suit you better. For Map:
// Define the Map.
const statesHashMap = new Map([
['WI', 'Wisconsin'],
['MN', 'Minnesota'],
['IL', 'Illinois']
]);
// You could also do:
statesHashMap.set('TX', 'Texas');
// Attain user input:
const stateSearchKey = prompt("Enter a two letter state abbreviation");
Then, to iterate over and find the right state, you'd have a few different options:
// Iterate with forEach.
statesHashMap.forEach((value, key) => {
if (stateSearchKey === key) {
console.log(value)
}
});
// Iterate with for..of
for (const key of statesHashMap.keys()) {
if (stateSearchKey === key) {
const state = statesHashMap.get(key);
console.log(state);
}
}
The problem with the forEach method above is that you can't break out without throwing an exception, which makes the for..of loop likely more favorable for you. If you want to show a message to the user when they don't get a state, you could use break statements:
// Iterate with for..of
for (const key of statesHashMap.keys()) {
if (stateSearchKey === key) {
const state = statesHashMap.get(key);
console.log(state);
break;
} else {
console.log('Try again');
break;
}
}
Or, to make it look nicer and as a better programming practice, encapsulate the operation in a function and return early when needed:
// Iterate with for..of
const findStateByAbbreviation = abbrev => {
for (const key of statesHashMap.keys()) {
if (stateSearchKey === key) {
const state = statesHashMap.get(key);
return console.log(state);
} else {
return console.log('Try again');
}
}
}
findStateByAbbreviation(stateSearchKey);
You should probably also use .toUpperCase() on the input from the user to ensure you match the key WI (for example) if the user provides wi.
Be wary of browser compatibility with the Map option, however.
Hope this helps.

How to detect duplicate characters in an Array, and create an argument accordingly?

Good evening, I attempting to detect duplicate characters in a string. More specifically, I am trying to find up to two different duplicates within an Array. If there is one duplicate, add a sub-string, and if there is another duplicate, add a different sub-string. Is there any way to do this?
Here is some example code I have so far:
var CodeFieldArray = ["Z80.0", "Z80.1", "Z80.0", "Z70.4"];
/* We have an array here used to create the final string at the end of the
code. It is a dummy array with similar variables in my actual code. For
reference sake, there may be only one object in the array, or 7 total,
depending on the user's input, which is where the duplicate detection should
come in, in case the user enters in multiples of the same code. */
var i, Index;
for (i = 0, L = 0; i < CodeFieldArray.length; i++) {
Index = CodeFieldArray[i].indexOf(CodeFieldArray[i]);
if(Index > -1) L += 1;
Extra0 = CodeFieldArray.indexOf("Z80.8");
Extra1 = CodeFieldArray.indexOf("Z80.9");
if(L >= 2 && Extra0 == -1) CodeFieldArray.push("Z80.8");
Extra0 = CodeFieldArray.indexOf("Z80.8");
if(L >= 4 && Extra0 != -1 && Extra1 == -1) CodeFieldArray.push("Z80.9");
console.println(Extra0);
}
/*^ we attempted to create arguments where if there are duplicates
'detected', it will push, "Z80.8" or, "Z80.9" to the end of the Array. They
get added, but only when there are enough objects in the Array... it is not
actually detecting for duplicates within the Array itself^*/
function UniqueCode(value, index, self) {
return self.indexOf(value) === index;
}
CodeFieldArray = CodeFieldArray.filter(UniqueCode);
FamilyCodes.value = CodeFieldArray.join(", ");
/* this is where we turn the Array into a string, separated by commas. The expected output would be "Z80.0, Z80.1, Z70.4, Z80.8"*/
I have it to where it will add "Z80.8" or "z80.9" if they are not present, but they are being added, only if there are enough objects in the Array. My for-loop isn't detecting specifically the duplicates themselves. If there was a way to detect specifically the duplicates, and create an argument based off of that, then we would be doing grand. The expected output would be "Z80.0, Z80.1, Z70.4, Z80.8"
You can use Set and forEach and includes
var CodeFieldArray = ["Z80.0", "Z80.1", "Z80.0", "Z70.4"];
let unique = [...new Set(CodeFieldArray)];
let match = ['Z80.8','Z80.9'];
let numOfDup = CodeFieldArray.length - unique.length;
if(numOfDup){
match.forEach(e=>{
if(!unique.includes(e) && numOfDup){
unique.push(e);
numOfDup--;
}
})
}
console.log(unique.join(','))
So the idea is
Use Set to get unique values.
Now see the difference between length of original array and Set to get number of duplicates.
Now will loop through match array and each time we push item from match array into unique we reduce numOfDup by so ( to handle case where we have only one duplicate or no duplicate ).
In the end join by ,
You could do something like this:
var uniqueArray = function(arrArg) {
return arrArg.filter(function(elem, pos,arr) {
return arr.indexOf(elem) == pos;
});
};
uniqueArray ( CodeFieldArray )

How to add/remove elements from array based on array contents

I've been struggling with this piece for a few days and I can't seem to find what's wrong. I have an array with a few objects:
myMainPostObj.categories = [Object, Object]
This is for add/removing categories to a post. Imagine I'm editing an existing post which is already associated with a couple of categories (as per code above).
I also have an array which has all categories in the db (including the ones which are associated with the post). On my js controller I have the following code:
$scope.addCategory = function (cat) {
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.slice(i, 1);
} else if (cat._id !== $scope.post.category_ids[i]) {
$scope.post.category_ids.push(cat._id);
}
}
}
The above function is called every time the user click on a category. The idea is for the above function to loop through the categories within the post (associated with the post) and compares it with the category passed as argument. If it matches the function should remove the category. If it doesn't it should add.
In theory this seems straight forward enough, but for whatever reason if I tick on category that is not associated with the post, it adds two (not one as expected) category to the array. The same happens when I try to remove as well.
This is part of a Angular controller and the whole code can be found here
The error in your code is that for each iteration of the loop you either remove or add a category. This isn't right... You should remove if the current id matches but add only if there was no match at all. Something like this:
$scope.addCategory = function (cat) {
var found = false;
for (var i in $scope.post.category_ids) {
if (cat._id === $scope.post.category_ids[i]) {
$scope.post.category_ids.splice(i, 1); // splice, not slice
found = true;
}
}
if (!found) // add only if it wasn't found
$scope.post.category_ids.push(cat._id);
}
I guess the problem could be that you're altering the category_ids array while you're iterating over it with the for loop. You might be better off trying something like this:
$scope.addCategory = function (cat) {
var catIndex = $scope.post.category_ids.indexOf(cat._id);
if (catIndex > -1)
$scope.post.category_ids.splice(catIndex, 1);
else
$scope.post.category_ids.push(cat._id);
}
Note that indexOf doesn't seem to be supported in IE7-8.
Let's simplify this a bit:
const CATEGORIES = [1, 2, 3];
let addCategory = (postCategories, categoryId) => {
CATEGORIES.forEach((cId, index) => {
if (postCategories[index] === cId) console.log("Removing", categoryId);
else console.log("Adding", categoryId);
});
return postCategories;
};
Please ignore the fact that we actually are not mutating the array being passed in.
A is either equal or not equal to B - there is no third option (FILE_NOT_FOUND aside). So you are looping over all of your categories and every time you don't find the category ID in the array at the current index you add it to the postCategories array.
The proper solution to your problem is just to use a Set (or if you need more than bleeding edge ES6 support, an object with no prototype):
// Nicer, ES6-or-pollyfill option
var postCategories = new Set();
postCategories.add(categoryId);
// Or, in the no-prototype option:
var postCategories = Object.create(null);
postCategories[categoryId] = true;
// Then serialization needs an extra step if you need an array:
parentObject.postCategories = Object.keys(parentObject.postCategories);

How do I add elements to a dynamic array and exclude exsisting elements

function addNumifnotThere(numer){
var numCent = [];
numCent.forEach(function(){
if(numer in numCent)
console.log("you logged that");
else
numCent.push(numer);
});
return numCent;
}
This is my current code, what its attempting to do is read an array and if there is already an element exits the loop and says "you already logged that", obviously if it cannot find a similar element then it pushes it to the array.
I want this to work dynamically so we cannot know the size of the array beforehand, so the first element passed as an argument should be put into the array, (addNum(1) should have the array print out [1], calling addNum(1) again should print "you already logged that")
However there are two problems with this
1) Trying to push to a new array without any entries means everything is undefined and therefore trying to traverse the array just causes the program to print [].
2) Adding some random elements to the array just to make it work, in this case numCent=[1,2,3] has other issues, mainly that adding a number above 3 causes the code to print incorrect information. In this case addNum(5) should print [1,2,3,5] but instead prints [1,2,3,5,5,5]
I know this has to be a simple mistake but I've been dragging myself too long to not ask for help.
EDIT: Thanks to the many outstanding answers here I have now leanred about the indexOf method, thank you guys so much.
For every non-match you are pushing the number. Use something like this
var numCent = [];
function addNumifnotThere(numer)
{
var index = numCent.indexOf(number);
if(index >=0)
{
console.log("you logged that");
}
else
{
numCent.push(number);
}
return numCent;
}
Use Array.prototype.indexOf
var numCent = [];
function addNum(numer){
if (numCent.indexOf(numer) > -1)
{
console.log("Number already in array");
}
else
{
numCent.push(numer);
}
}
//DEMO CODE, not part of solution
document.querySelector("button").addEventListener("click", function(){
if (document.querySelector("input").value.length > 0)
{
addNum(document.querySelector("input").value);
document.querySelector("div").innerHTML = numCent.join(", ");
}
}, false);
Output
<div id="output"></div>
<input />
<button>Add number</button>
indexOf tests if an element is inside the array and returns its index. If not found it will return -1. You can test for this. You can try it for your self in this snippet. It will only allow you to add a number (or any string, in this example) once.
I also was confused by the newCent array declaration inside the function. I think, based upon the content of your question, you meant this.
If you want the array held in the instance, you can do it like this.
function AddIf(arr){
if( arr || !this.arr ) {
this.arr = arr || [];
}
return function(number) {
if( this.arr.indexOf(number) >= 0 ) {
console.log("Already Present!");
} else {
this.arr.push(number);
}
return this.arr;
}.bind(this);
}
// Usage would be like this:
// var addIf = new AddIf([1, 2, 3]);
// addIf(10); // returns [1, 2, 3, 10]
// addIf(10); // logs "Already Present!", doesn't add 10 to array
This basically returns a function, with this bound to the function being called. If you pass in an initial array, it will use that array to compare to when adding it to the array.
You can catch the return function and call it as you would want to. If you don't call new when invoking however, it will share the same array instance (and have a funky way of being called, AddIf()(10)).
I used fn.bind() to ensure the function gets called in the correct context every time, if you were wondering why I called it like that.
Do do this cleanly, I'd consider prototyping the global Array object and adding a method to push values but only if they're unique to the array. Something like this:
Array.prototype.pushUnique = function (item) {
if (this.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
this.push(item);
}
}
If you're not comfortable prototypeing global types like Array, you can build the same thing in a procedural pattern:
function arrayPushUnique (arr, item) {
if (arr.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
arr.push(item);
}
}
Then to use it, simply create a new empty array and start pushing things to it.
var numCent = [];
// The Array.prototype method
numCent.pushUnique(number);
// The procedural method
arrayPushUnique(numCent, number);

How to remove an array element without splice and without undefined remaining?

Is it possible to remove an array element at a certain position, without rearranging indexes, and without that position changing to undefined?
I don't think that is possible with delete nor splice?
I need an accurate way to view the length of the array, without rearranging indexes.
I do not want to use splice because i have an object that has specific positions mapped to actual X,Y points of a tabel (Punkt).
UPDATE: actually, knowing if the array element exists out of ONLY undefined values might also help me, is there an easier way then looping through?
var keys = Object.keys(racks);
for (var i = 0; i < keys.length; i++)
{
for (var x = 0; x < racks[keys[i]].punkt.length; x++)
{
if(racks[keys[i]].punkt[x].y == fullName)
{
//delete racks[keys[i]].punkt[x];
racks[keys[i]].punkt.splice(x,1);
console.log(keys[i] + " : " + racks[keys[i]].punkt.length);
}
}
}
I don't think that is possible with delete nor splice?
I need an accurate way to view the length of the array, without rearranging indexes.
Then delete, a hasOwnProperty or in guard when retrieving from the array, and a loop counting the elements (or a separate variable keeping track) is the only way to do it. JavaScript's standard arrays are inherently sparse (because they're not really arrays at all), they can have gaps in them where they don't have entries. To create a gap, delete the array entry using delete.
Example:
// Setup
var a = ["a", "b", "c", "d"];
console.log(a.length); // 4
// Using delete
delete a[2]; // Delete the entry containing "c"
console.log(a.length); // Still 4
a.hasOwnProperty(2); // false
// Using the guard when getting an entry
if (a.hasOwnProperty(2)) { // Or `if (2 in a)`
// Get and use [2]
}
else {
// Do whatever it is you want to do when the array doesn't have the entry
}
// Finding out how many it *really* has:
var key;
var count = 0;
for (key in a) {
if (a.hasOwnProperty(key) && // See below
/^0$|^[1-9]\d*$/.test(key) &&
key <= 4294967294
) {
++count;
}
}
console.log(count); // 3
See this other answer for the details behind that if in the loop. If you never put non-element properties on the array, you can skip the second and third parts of that if.
This works perfectly.
var delrow = window.event.srcElement;
while ((delrow = delrow.parentElement) && delrow.tagName != "TR");
delrow.parentElement.removeChild(delrow);
var punten = racks[keys[i]].punkt.length;
if(racks[keys[i]].punkt[x].y == fullName)
{
delete racks[keys[i]].punkt[x];
punten--;
}
if(punten==0)
{
console.log("Could have removed device: " + keys[i]);
}

Categories