In Javascript, I don't see any tutorials clearly explain how to create like
MyItems[Row][Index][categories]
so that
MyItems[0][0][0]=1
MyItems[1][0][0]='stock'
MyItems[5][1][0]='pending'
My use case is each Index will contain different value which is integer or string.
What is the best way to avoid error when accessing MyItems[0][1][0] that has no value?
Because JS doesn't have actual multidimensional arrays, but instead merely have nested arrays that don't necessarily form a rectangular structure, you'd need to check for each nested array first. A simple "truthy" test would be fine.
if (myItems[0] && myItems[0][0])
myItems[0][0].push(1);
If you wanted to create the arrays that aren't there, then you can do that like this:
if (!myItems[0])
myItems[0] = [];
if (!myItems[0][0])
myItems[0][0] = [];
myItems[0][0].push(1);
Of course this assumes that the first and second levels should always be arrays, and only the third level will hold the actual values. You'll need to adjust it if that's not the case.
Also, a function would be a good idea to get rid of the repetition.
function addNested(outer, idx1, idx2, idx3, value) {
if (!outer[idx1])
outer[idx1] = [];
if (!outer[idx1][idx2])
outer[idx1][idx2] = [];
outer[idx1][idx2][idx3] = value;
}
addNested(myItems, 1, 0, 0, 'stock');
This is how you'd make a 3D array, but I'd recommend against mixing data types in your array, that's not exactly a common or standard practice.
// just filler stuff, ignore the body of this function
function getStringOrNumber(row, col, cat) {
var thing = row * cols * cats + col * cats + cat;
return Math.random() < .5 ? thing : thing.toString();
}
// something to deal with each value
function doSomething(value) {
switch (typeof value) {
case 'string':
// logic for string type
break;
case 'number':
// logic for number type
break;
default:
// unexpected?
break;
}
}
// here's how you make your 3D array
var rows = 10,
cols = 10,
cats = 10,
array3d = new Array(rows),
i, j, k;
for (i = 0; i < rows; i++) {
array3d[i] = new Array(cols);
for (j = 0; j < cols; j++) {
array3d[i][j] = new Array(cats);
for (k = 0; k < cats; k++) {
array3d[i][j][k] = getStringOrNumber(i, j, k);
doSomething(array3d[i][j][k]);
}
}
}
If you want to check whether an index exists on the 3d array, try a function like this:
function setValue(array3d, row, col, cat, value) {
if (array3d[row] && array3d[row][col] && array3d[row][col][cat]) {
array3d[row][col][cat] = value;
} else {
throw new RangeError("Indices out of range");
}
}
If you were to allocate each array at each index in a breadth-first pattern before accessing any of it, then this would work without any special handling.
However, as you've correctly recognized, if you want to be able to access indexes that may not have been allocated yet, this won't work.
Actually, to be more specific, you are allowed to attempt to read an index outside the length of an array, in which case you'll get undefined. The problem is that if you get undefined for the first or second depth, then an attempt to index that undefined value will fail.
Thus, to prevent this error, you must guard against undefined first- or second-depth indexes.
The best way to do this is to write a class that provides a getter and setter that automatically take care of the special handling requirements. Here's an example of such a class, defined using the prototype pattern:
(function() {
var Array3D = function() {
this.data = [];
};
Array3D.prototype.get = function(r,c,z) {
if (this.data.length <= r) return undefined;
if (this.data[r].length <= c) return undefined;
return this.data[r][c][z];
};
Array3D.prototype.set = function(r,c,z,v) {
if (this.data.length <= r) this.data[r] = [];
if (this.data[r].length <= c) this.data[r][c] = [];
this.data[r][c][z] = v;
return this;
};
window.Array3D = Array3D;
})();
var a = new Array3D();
alert(a.get(0,0,0)); // undefined, no error
a.set(0,0,0,'x');
alert(a.get(0,0,0)); // 'x'
a.set(234,1234,342,'y');
alert(a.get(234,1234,342)); // 'y'
alert(a.get(0,1,0)); // undefined, no error
alert(a.get(12341234,243787,234234)); // undefined, no error
Since this completely differs from my other answer, I thought it would be helpful to suggest another approach using nested sparse arrays which could be implemented using associative arrays or objects. Try this:
// N-dimensional array
function ArrayND() {
// nothing to do here, seriously
}
ArrayND.prototype.setValue = function (value) {
var indices = arguments,
nest = this,
index, i;
// note the range of values since the last recursion is being set to a value
for (i = 1; i < indices.length - 2; i++) {
index = indices[i];
if (nest[index] instanceof ArrayND) {
nest = nest[index];
} else if (typeof nest[index] === "undefined") {
// recursive functionality!
nest = nest[index] = new ArrayND();
} else {
// we don't want to get rid of this value by accident!
return false;
}
}
// now "nest" is equal to the ArrayND you want to set the value inside of
index = indices[i];
nest[index] = value;
// we set the value successfully!
return true;
}
ArrayND.prototype.getValue = function () {
var indices = arguments,
nest = this,
index, i;
// note the range because we're getting the last value
for (i = 0; i < indices.length; i++) {
index = indices[i];
// for last recursion, just has to exist, not be ArrayND
if (nest[index]) {
nest = nest[index];
} else {
// nothing is defined where you're trying to access
return undefined;
}
}
return nest;
}
var arrayND = new ArrayND();
arrayND.setValue(1, 0, 0, 0);
arrayND.setValue("stock", 1, 0, 0);
arrayND.setValue("pending", 5, 1, 0);
// you can treat it like a normal 3D array if you want
console.log(arrayND[0][0][0]); // 1
console.log(arrayND[1][0][0]); // "stock"
console.log(arrayND[5][1][0]); // "pending"
// or use a nicer way to get the values
console.log(arrayND.getValue(1, 0, 0)); // "stock"
// phew, no errors!
console.log(arrayND.getValue(3, 1, 0)); // undefined
// some awesome recursive functionality!
console.log(arrayND.getValue(5).getValue(1).getValue(0)); // "pending"
Related
Iterator should print every value within range but its only printing alternate nos.
function iterator(rangeStart, rangeEnd) {
if (rangeStart == 0 && rangeEnd == 0) {
return null;
}
var iterate = function*(start = 0, end = 5, step = 1) {
let iterationcount = 0;
for (let i = start; i <= end; i += step) {
yield i;
iterationCount = i;
}
return iterationCount;
}
var values = iterate(rangeStart, rangeEnd);
var tmp = [];
while (values.next().value != undefined) {
tmp.push(values.next().value);
}
return tmp.join(",");
}
console.log(iterator(0, 10))
expected
[0,1,2,3,4,5,6,7,8,9,10]
Result
[1,3,5,7,9,10]
Every call to next will consume a value from the iterator, so the while condition is consuming a value that will therefore not get into tmp.
But... JavaScript allows you to consume values in much easier ways. For instance with Array.from or spread syntax you can collect all values from the iterator into an array.
Not your question, but:
iterationCount serves no purpose in your code, so just drop that part.
Why would the function behave differently when both range start and end are 0 than when start and end are both 10? I would remove that case. When the range end would be less than the start, it would make sense to exit, but that will happen anyway without any if statement.
The name iterator for your function is somewhat misleading, as the return value is not an iterator, but a comma separated string. I would therefore call it rangeToCsv
function rangeToCsv(rangeStart, rangeEnd) {
var iterate = function*(start = 0, end = 5, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
var values = iterate(rangeStart, rangeEnd);
return Array.from(values).join(",");
}
console.log(rangeToCsv(0, 10))
What is the shortest way to create a 2d array of a certain size?
I figured that following code is pretty short, but can you do it with even less code?
var x = 5;
var y = 7;
var my2dArray = (new Array(y)).fill(0).map(
function(){
return new Array(x);
}
);
Array.apply(0, Array(x)).map(function() { return Array.apply(0, Array(y)); })
Simpler if you write a little convenience routine
function make_array(x) { return Array.apply(0, Array(x)); }
then
make_array(x).map(function() { return make_array(y);} )
or if you prefer
make_array(x).map(make_array.bind(0, y))
If you're writing ES6:
Array(...Array(x)).map(() => Array(y));
If you'd prefer to use fill, since you seem to have it available:
Array(x).fill().map(function() { return Array(y); });
or somewhat more concisely in ES6, using an arrow function:
Array(x).fill().map(() => Array(y));
It turns out Array#fill requires no argument; without it, it replaces absent elements with undefineds.
What about building your own Class ?
function Array2D(xSize, ySize, initialValue) {
initialValue=initialValue || 0;
// create an flat empty array filled with the initial value
var length = xSize*ySize;
var innerArray = new Array(length);
for (var i=0; i<length; i++) innerArray[i] = initialValue;
// accessors
this.getAt = function(x, y) { return innerArray[x+xSize*y]};
this.setAt = function(x, y, val) { innerArray[x+xSize*y]=val};
}
Use with :
var myArray2d = new Array2D(5,7);
myArray2d.setAt(1,1,3);
var value = myArray2d.getAt(1,1); // == 3
var anotherValue = myArray2d.getAt(2,2); // == 0
I'm trying to pick a random film from an object containing film objects. I need to be able to call the function repeatedly getting distinct results until every film has been used.
I have this function, but it doesn't work because the outer function returns with nothing even if the inner function calls itself because the result is not unique.
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var moviesLength = Object.keys(movies).length;
function doPick() {
var pick = pickRandomProperty(movies);
var distinct = true;
for (var i = 0;i < watchedFilms.length; i += 1) {
if (watchedFilms[i]===pick.title) {
distinct = false;
if (watchedFilms.length === moviesLength) {
watchedFilms = [];
}
}
}
if (distinct === true) {
watchedFilms.push(pick.title);
return pick;
}
if (distinct === false) {
console.log(pick.title+' has already been picked');
doPick();
}
};
return doPick();
}
T.J. Crowder already gave a great answer, however I wanted to show an alternative way of solving the problem using OO.
You could create an object that wraps over an array and makes sure that a random unused item is returned everytime. The version I created is cyclic, which means that it infinitely loops over the collection, but if you want to stop the cycle, you can just track how many movies were chosen and stop once you reached the total number of movies.
function CyclicRandomIterator(list) {
this.list = list;
this.usedIndexes = {};
this.displayedCount = 0;
}
CyclicRandomIterator.prototype.next = function () {
var len = this.list.length,
usedIndexes = this.usedIndexes,
lastBatchIndex = this.lastBatchIndex,
denyLastBatchIndex = this.displayedCount !== len - 1,
index;
if (this.displayedCount === len) {
lastBatchIndex = this.lastBatchIndex = this.lastIndex;
usedIndexes = this.usedIndexes = {};
this.displayedCount = 0;
}
do index = Math.floor(Math.random() * len);
while (usedIndexes[index] || (lastBatchIndex === index && denyLastBatchIndex));
this.displayedCount++;
usedIndexes[this.lastIndex = index] = true;
return this.list[index];
};
Then you can simply do something like:
var randomMovies = new CyclicRandomIterator(Object.keys(movies));
var randomMovie = movies[randomMovies.next()];
Note that the advantage of my implementation if you are cycling through items is that the same item will never be returned twice in a row, even at the beginning of a new cycle.
Update: You've said you can modify the film objects, so that simplifies things:
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
var counter = keyCount * 2;
// Try a random pick
while (--counter) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
// We've done two full count loops and not found one, find the
// *first* one we haven't watched, or of course return null if
// they've all been watched
for (counter = 0; counter < keyCount; ++counter) {
candidate = movies[keys[counter]];
if (!candidate.watched) {
candidate.watched = true;
return candidate;
}
}
return null;
}
This has the advantage that it doesn't matter if you call it with the same movies object or not.
Note the safety valve. Basically, as the number of watched films approaches the total number of films, our odds of picking a candidate at random get smaller. So if we've failed to do that after looping for twice as many iterations as there are films, we give up and just pick the first, if any.
Original (which doesn't modify film objects)
If you can't modify the film objects, you do still need the watchedFilms array, but it's fairly simple:
var watchedFilms = [];
$scope.watchedFilms = watchedFilms;
var getRandomFilm = function(movies) {
var keys = Object.keys(movies);
var keyCount = keys.length;
var candidate;
if (watchedFilms.length >= keyCount) {
return null;
}
while (true) {
candidate = movies[keys[Math.floor(Math.random() * keyCount)]];
if (watchedFilms.indexOf(candidate) === -1) {
watchedFilms.push(candidate);
return candidate;
}
}
}
Note that like your code, this assumes getRandomFilm is called with the same movies object each time.
Background
I'm attempting to check the existence of a value in array A in a second array, B. Each value is an observable number. Each observable number is contained in an observable array. The comparison always returns -1, which is known to be incorrect (insofar as values in A and B overlap). Therefore, there's something wrong with my logic or syntax, but I have not been able to figure out where.
JSBin (full project): http://jsbin.com/fehoq/190/edit
JS
//set up my two arrays that will be compared
this.scores = ko.observableArray();
//lowest is given values from another method that splices from scores
this.lowest = ko.observableArray();
//computes and returns mean of array less values in lowest
this.mean = (function(scores,i) {
var m = 0;
var count = 0;
ko.utils.arrayForEach(_this.scores(), function(score) {
if (!isNaN(parseFloat(score()))) {
//check values
console.log(score());
// always returns -1
console.log(_this.lowest.indexOf(score()));
//this returns an error, 'not a function'
console.log(_this.lowest()[i]());
//this returns undefined
console.log(_this.lowest()[i]);
//only do math if score() isn't in lowest
// again, always returns -1, so not a good check
if (_this.lowest.indexOf(score())<0) {
m += parseFloat(score());
count += 1;
}
}
});
// rest of the math
if (count) {
m = m / count;
return m.toFixed(2);
} else {
return 'N/A';
}
});
Update
#Major Byte noted that mean() is calculated before anything gets pushed to lowest, hence why I get undefined. If this is true, then what might be the best way to ensure that mean() will update based on changes to lowest?
You really just could use a computed for the mean
this.mean = ko.computed(
function() {
var sum = 0;
var count = 0;
var n = 0;
for(n;n < _this.scores().length;n++)
{
var score = _this.scores()[n];
if (_this.lowest.indexOf(score)<0) {
sum += parseFloat(score());
count++;
}
}
if (count > 0) {
sum = sum / count;
return sum.toFixed(2);
} else {
return 'N/A';
}
});
this will trigger when you add to lower(), scores() and change scores().
obligatory jsfiddle.
Update:
Forgot to mention that I change something crucial as well. From you original code:
this.dropLowestScores = function() {
ko.utils.arrayForEach(_this.students(), function(student){
var comparator = function(a,b){
if(a()<b()){
return 1;
} else if(a() > b()){
return -1;
} else {
return 0;
}
};
var tmp = student.scores().sort(comparator).slice(0);
student.lowest = ko.observableArray(tmp.splice((tmp.length-2),tmp.length-1));
});
};
apart from moving the comparator outside of dropLowestScores function, I changed the line:
student.lowest = ko.observableArray(tmp.splice((tmp.length-2),tmp.length-1));
to
student.lowest(tmp.splice((tmp.length-2),tmp.length-1));
student.lowest is an observable array, no need to define it as an observableArray again, in fact that actually breaks the computed mean. (The correction for Drop Lowest Scores as per my previous comment is left out here).
I have this problem about arrays that I can't seem to solve. What I'm trying to do is to return numbers that are not inside the array. Return can only be done if the new value is not inside the array, else it's going to increment the value (to make sure that there is no space).
My code goes like:
function create_number(number) {
var array = [1,2,3,6,7,8,9];
for (var i=0;i<array.length;i++) {
if (array[i] == number) {
return number;
} else {
// create a new number that is not inside the array, and return it.
}
// If not just do the loop again.
// If the loop is over, then just create a valid number
// that is not found inside the array.
}
}
var array = [1,2,3,6,7,8,9];
var number = 0;
while (true) {
if (array.indexOf(++number) == -1) {
array.push(number);
return number;
}
}
but you need to persist array somewhere
PS: Array.prototype.indexOf shim for ancient browsers (credits to #Lochemage)
PPS: the solution above is O(N^2), just for fun here is O(N) one (it requires the array to be sorted initially):
var array = [1,2,3,6,7,8,9];
var number = 1;
while (true) {
if (array[number - 1] != number) {
array.splice(number - 1, 0, number);
return number;
}
++number;
}