Indexed getter in Javascript - javascript

I'm using straight Javascript (no JQuery or anything like that, please). I've implemented a class which wraps an array, thus:
class Ctrls
{
_items = new Array();
constructor()
{
this._items = new Array();
}
Add(oCtrl)
{
this._items.push( { key:oCtrl.Name, value:oCtrl } );
}
Clear()
{
this._items = new Array();
}
get Count()
{
return this._items.length;
}
get Item(index)
{
// get the index'th item.
// If item is numeric, this is an index.
// If item is a string, this is a control name
if (Number.isInteger(index))
{
return this._items(index).value;
}
else
{
item = this._items.find(element => (element.value.Name == index));
return item;
}
}
get Items()
{
return this._items; // in case we desperately need to
}
}
I get an error on page load, at get Item(index), which is Uncaught SyntaxError: Getter must not have any formal parameters. I come from C# world and am looking for an equivalent of:
public Ctrl Item(iIndex)
{
get
{
return _items[iIndex];
}
}
How do I index a getter in Javascript?
Edit(1): I've had suggestions to turn get Item into a function, but if I change the definition to this:
function GetItem(index) // returns Ctrl
{
// get the index'th item.
// If item is numeric, this is an index.
// If item is a string, this is a control name
if (Number.isInteger(index))
{
return this._items(index).value;
}
else
{
item = this._items.find(element => (element.value.Name == index));
return item;
}
}
I get this error on pageload: Uncaught SyntaxError: Unexpected identifier at the line function GetItem...
Edit(2): Modified the above to read:
GetItem(index) // returns Ctrl
{
// get the index'th item.
// If item is numeric, this is an index.
// If item is a string, this is a control name
if (Number.isInteger(index))
{
return this._items(index).value;
}
else
{
item = this._items.find(element => (element.value.Name == index));
return item;
}
}
as functions within classes do not use the function keyword, oddly. This now works. Thank all.

"you can't pass parameters to getters in JS". Theoretically: yes, you cannot do that. But practically: functions are first-class citizens in JS, so they can be arguments or return values of a function. You can do it like this:
class GetterWithParameter {
constructor() {
this.array = ["index 0", "index 1", "index 2"]
}
get itemAtIndex() {
return (idx) => this.array[idx]
}
}
const getterWithParameter = new GetterWithParameter()
const idx0 = getterWithParameter.itemAtIndex(0)
const idx1 = getterWithParameter.itemAtIndex(1)
const idx2 = getterWithParameter.itemAtIndex(2)
console.log("item at index 0:", idx0)
console.log("item at index 1:", idx1)
console.log("item at index 2:", idx2)
So, while the getter cannot have arguments, you can return a function that can receive an argument - and use that.
Of course, the usage seems identical to defining a function on the class that requires the same argument - but still, you are using a getter.

Related

Removing element from array in class method

I was working on a hackerrank problem and test cases showed that something was wrong with my 'remove' method. I always got undefined instead of true/false.
I know splice returns array of deleted elements from an array. When I console.log inside map, it looked like everything was fine when I was deleting first element (I was getting what I expected except true/false). But when 'name' I am deleting is not first element, I didn't get what I expected to get. Could you help me fix this? And of course, I never get true or false...
class StaffList {
constructor() {
this.members = [];
}
add(name, age) {
if (age > 20) {
this.members.push(name)
} else {
throw new Error("Staff member age must be greater than 20")
}
}
remove(name) {
this.members.map((item, index) => {
if(this.members.includes(name)) {
console.log(name)
let removed = this.members.splice(item, 1);
console.log(removed)
return true;
} else {
return false;
}
})
}
getSize() {
return this.members.length;
}
}
let i = new StaffList;
i.add('michelle', 25)
i.add('john', 30);
i.add('michael', 30);
i.add('jane', 26);
i.remove('john');
console.log(i);
Your return statements are wrapped within .map() (which you misuse in this particular case, so you, essentially, build the array of true/false), but your remove method does not return anything.
Instead, I would suggest something, like that:
remove(name){
const matchIdx = this.members.indexOf(name)
if(matchIdx === -1){
return false
} else {
this.members.splice(matchIdx, 1)
return true
}
}
In the remove method, you're using map with the array, which runs the function you give as argument for each array element. But I believe you don't want to do that.
Using the example you have bellow, basically what you do there is check if the array contains the name 'john', and if so, you delete the first item that appears in the array (which would be 'michelle'). This happens because the map function will run for every element, starting on the first one, and then you use that item to be removed from the array. After that, it returns the function, and no other elements get removed.
So my suggestion is just getting rid of the map function and running its callback code directly in the remove method (you would need to get the name's index in the array to use the splice method).
It is not clear why you need to use iterative logic to remove an item. You can simply use findIndex() to get the position of the member in the array. If the index is not -1, then you can use Array.prototype.slice(index, 1) to remove it. See proof-of-concept example below:
class StaffList {
constructor() {
this.members = [];
}
add(name, age) {
if (age > 20) {
this.members.push(name)
} else {
throw new Error("Staff member age must be greater than 20")
}
}
remove(name) {
const index = this.members.findIndex(x => x === name);
if (index !== -1) {
this.members.splice(index, 1);
}
}
getSize() {
return this.members.length;
}
}
let i = new StaffList;
i.add('michelle', 25)
i.add('john', 30);
i.add('michael', 30);
i.add('jane', 26);
i.remove('john');
console.log(i);
Use a filter method instead of map it's more elegant and you can return the rest of the array as well instead of true or false unless the problem you're working on requires true of false specifically.
You could write something like this:
remove(name) {
if (!this.members.some(el => el === name)) return false;
this.members = this.members.filter(item => item !== name);
return true;
}

How to pass an instance of an array to a function

I have a checkbox group that I want to get all checked items. I am trying to pass an Array to a function so I can get all checked items but it's not working.
checkedCategory: Array<number>;
contains(checkedArr: Array<number>, id: number): boolean {
if (checkedArr instanceof Array) {
return checkedArr.indexOf(id) > -1;
} else if (!!checkedArr) {
return checkedArr === id;
}
return false;
}
private add(checkedArr: Array<number>, id: number) {
if (!this.contains(checkedArr, id)) {
console.log('add: ' + checkedArr);
if (checkedArr instanceof Array) {
checkedArr.push(id);
} else {
checkedArr = [id];
}
}
}
private remove(checkedArr: Array<number>, id: number) {
const index = checkedArr.indexOf(id);
if (!checkedArr || index < 0) {
return;
}
checkedArr.splice(index, 1);
}
toggleCategory(id: number) {
if (this.contains(this.checkedCategory, id)) {
this.remove(this.checkedCategory, id);
} else {
this.add(this.checkedCategory, id);
}
}
I have a (click) event in my checkbox that will call togglecategory
(click)="toggleCategory(category.id)"
Then, when I try to console.log the 'checkedCategory' it's undefined.
I have 3 checkboxes group and I want to reuse the 'contains/add/remove' function that's why I want to pass an array.
Thank you
When you call toggleCategory(20) see what happens, in your case you will see that your function will print add: undefined. so the first thing you must debug is your add function. I think the issue is that your array is not defined. Try to initalize your empty array like this let checkedCategory: Array<number> = Array();
But either way, You need to debug your add function. Good Luck :)
If you have any questions about why this is the solution, let me know, I dont mind sharing the Theory aspect to why this occurs if you are interested.

Javascript Binary Search Tree methods not working

I am trying to build this simple Javascript Binary search tree. I have simply created the addItem method for the tree, but no item seems to get added to the tree. I have divided the addItem method into several other methods to ensure that the tree reference is passed properly without any errors. I think the problem is occurring in the addNode recursive calls.
Below the is the given code:
class Node{
constructor(value){
this.value=value;
this.left=null;
this.right=null;
}
show(){
console.log(this.value);
}
}
class BST{
constructor(){
this.root=null;
}
addNode(node, item){
if(node==null){
node=new Node(item);
}
else if(item<=node.value){
this.addNode(node.left, item);
}
else {
this.addNode(node.right, item);
}
}
addFunc(tree, item){
this.addNode(tree.root, item);
}
addItem(item){
this.addFunc(this, item);
}
}
let bst = new BST();
bst.addItem(5);
bst.addItem(43);
bst.addNode(12);
console.log(bst); // shows BST{root:null}
One of the problem is in function addNode() at if(node==null){node=new Node(item);}
node is passed as a parameter, which means when this.addNode(tree.root, item); is called
node.a = 5 changes value of tree.root.a to 5
node = 5 just changes the value of this node parameter to 5 but not the actual argument that is tree.root value is not assigned to 5.
More info
First, you've not assigned anything as the root of your tree. Your intent was there with this.root. Next, your logic has you recursing a method rather than recursing your tree.
While you call the addNode() method for each value, you always test the root of your tree for > and < values. So this code will only ever overwrite a single .left or .right value. So recursing your function internally doesn't really do what you expect.
The solution is to recurse the tree looking for the correct slot for the value that is passed into the function.
I've re-written your code a bit and taken some liberties with the API. For instance, creating a new BST will require the starting value to be passed into the constructor (this just helps simplify the code a bit). Next, you'll notice no more recursive functions. The recursive calls are replaced with a while loop that finds the appropriate node to add the new node.
This code actually builds the tree where as your attempt only builds a single level.
Edit refactored into search function
class Node {
constructor(value) {
this.value = value;
this.left = {};
this.right = {};
}
show() {
console.log(this.value);
}
}
class BST {
constructor(value = 0) {
this.root = new Node(value);
}
addNode(item) {
Object.assign(this.search(item), new Node(item));
}
findLeftChild(item) {
return this.search(item).left.value;
}
search(item) {
let found = false;
let foundNode = this.root;
while (!found) {
if (foundNode.value === item) {
break;
} else if (foundNode.value > item) {
foundNode = foundNode.left;
found = foundNode.value === undefined;
} else if (foundNode.value < item) {
foundNode = foundNode.right;
found = foundNode.value === undefined;
}
}
return foundNode;
}
addItem(item) {
this.addNode(item);
}
}
let bst = new BST(5);
bst.addItem(43);
bst.addItem(12);
bst.addItem(6);
bst.addItem(66);
bst.addItem(22);
bst.addItem(4);
bst.addItem(3);
bst.addItem(2);
bst.addItem(1);
console.log("Found: 17 ", bst.search(17).value !== undefined);
console.log("find value less than 12: " + bst.findLeftChild(12));
console.log(JSON.stringify(bst, null, 2)); // shows BST{root:null}

javascript - unused argument 'idtype' although called from a .map() function

I am trying to re-write a function that filters out a specific property of an object to a function that can be passed a property and filter it.
This is the initial function:
function filterCategory(xmlObject, id) {
let newData = [];
xmlObject
.Sports[0]
.Sport[0]
.Category
.map(function (category) {
if (category.$.CategoryID == id) {
newData.push(category);
}
});
xmlObject
.Sports[0]
.Sport[0]
.Category = newData;
return xmlObject;
}
This is my new function:
function filterProperty(xmlObject, property, idtype, id) {
let newData = [];
if(xmlObject.hasOwnProperty(property)) {
xmlObject.property.map(function(value) {
if(value.$.idtype == id) {
newData.push(value);
}
});
xmlObject.property = newData;
}
return xmlObject;
}
For the second function my linter returns Unused idtype. Will my function be able to access the argument, or will it fail because I am trying to call it from a map() function? If so, how can I avoid this?
If you want to use idtype as a dynamic object property, then you can't use it like my.object.idtype as that will look for the property on the object that is literally called "idtype", instead you can use bracket notation to access the property
value.$[idtype];
Further illustration:
var obj = { one: 1, two: 2, three: 'foobarbaz' };
function getThingFromObject(mything) {
return obj[mything];
}
console.log(getThingFromObject('one')); // 1
console.log(getThingFromObject('three')); // 'foobarbaz'

Returning String from Method Produces Undefined

I have an ES2015 module:
export default class ArrayCollection {
constructor(items = []) {
this.items = items;
}
search(query) {
this.items.forEach(function (item, index) {
if (query == item) {
return `Your query ${query} was found in the array on the ${index} index`;
};
});
return `Your array does not contain a ${query}`;
}
}
And in my main.js I have this:
import ArrayCollection from './ArrayCollection';
let Numbers = new ArrayCollection([2, 4, 6, 8, 10]);
let searchResult = Numbers.search(4);
console.log(searchResult);
Why does the console.log return undefined?
I am trying to search an array for an item and if it is there to return it. I know that there are special methods in ES6, but I just want to know what is wrong with the above code.
Thanks.
=== EDIT ===
If you run above ES6 code through webpack or rollup it produces the following vanilla code that runs through any browser:
var ArrayCollection = function ArrayCollection(items) {
if ( items === void 0 ) items = [];
this.items = items;
};
ArrayCollection.prototype.search = function search (query) {
this.items.forEach(function (item, index) {
if (query == item) {
return ("Your query " + query + " was found in the array on the " + index + " index");
}
});
// return `Your array does not contain a ${query}`;
};
var Numbers = new ArrayCollection([2, 4, 6, 8, 10]);
var searchResult = Numbers.search(4);
alert(searchResult);
Here is the JsFiddle that produces the same error. It would be nice if I could get a correction on the ES6 version instead of the compiled version.
There are a two issues with your code:
Returning inside the forEach callback will only exit the callback, not the search function. Meaning even if you return within the callback, the result will always be Your array does not contain a 4.
Be careful with the == operator. Unless you're very familiar with coercion, I would recommend use use the triple equal operator instead (===). See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators
You could write your code even simpler and avoid your issues using .indexOf:
export default class ArrayCollection {
constructor(items = []) {
this.items = items;
}
search(query) {
if (this.items.indexOf(query) > 0) {
return `Your query ${query} was found in the array on the ${index} index`;
};
return `Your array does not contain a ${query}`;
}
}

Categories

Resources