Implementing OneRule algorithmn in javascript [closed] - javascript

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
OneR, short for "One Rule", is a simple yet accurate classification algorithm that generates one rule for each predictor in the data, then selects the rule with the smallest total error as its "one rule".
I tried to find code samples on GitHub, but found only one, developed with R language. How could I implement this algorithm in Javascript?
What I have tried?
I am trying to implement following this sample article:
https://www.saedsayad.com/oner.htm
class OneR {
/**
* Pass dataset which will be an array of values.
* Last value is classifcator's value.
* All other values are predictors.
*
* Example
*
* The meaning of sequence values:
* |Outlook|Temp|Humidity|Windy|Play Golf|
*
* Representation of a sequence:
* ['rainy', 'hot', 'high', 0, 0]
*
* True and False are represented as zeros or ones
*/
constructor(data = []) {
this.data = data;
this.frequences = {};
}
predict() {
if (this.data && this.data.length > 0) {
const firstRow = this.data[0];
const predictorCount = firstRow.length - 1;
let classifcator;
// For each predictor,
for (let i = 0; i < predictorCount; i++) {
// For each value of that predictor, make a rule as follos;
for (let y = 0; y < this.data.length; y++) {
// Count how often each value of target (class) appears
classifcator = this.data[y][predictorCount];
console.log(classifcator);
// Find the most frequent class
// Make the rule assign that class to this value of the predictor
}
// Calculate the total error of the rules of each predictor
}
// Choose the predictor with the smallest total error
} else {
console.log("Cannot predict!");
}
}
}
module.exports = {
OneR
};
I have loaded data from csv
rainy,hot,high,0,0
rainy,hot,high,1,0
overcast,hot,high,0,1
sunny,mild,high,0,1
sunny,cool,normal,0,1
sunny,cool,normal,1,0
overcast,cool,normal,1,1
rainy,mild,high,0,0
rainy,cool,normal,0,1
sunny,mild,normal,0,1
rainy,mild,normal,1,1
overcast,mild,high,1,1
overcast,hot,normal,0,1
sunny,mild,high,1,0

If I understand correctly how the frequency tables must be compared (lowest error rate, highest accuracy), you could use Maps so to cope with non-string types if ever necessary.
Although your example has target values that are booleans (0 or 1), in general they could be from a larger domain, like for example "call", "fold", "raise", "check".
Your template code creates a class, but I honestly do not see the benefit of that, since you can practically only do one action on it. Of course, if you have other actions in mind, other than one-rule prediction, then a class could make sense. Here I will just provide a function that takes the data, and returns the number of the selected predictor and the rule table that goes with it:
function oneR(data) {
if (!data && !data.length) return console.log("Cannot predict!");
const predictorCount = data[0].length - 1;
// get unique list of classes (target values):
let classes = [...new Set(data.map(row => row[predictorCount]))];
let bestAccuracy = -1;
let bestFreq, bestPredictor;
// For each predictor,
for (let i = 0; i < predictorCount; i++) {
// create frequency table for this predictor: Map of Map of counts
let freq = new Map(data.map(row => [row[i], new Map(classes.map(targetValue => [targetValue, 0]))]));
// For each value of that predictor, collect the frequencies
for (let row of data) {
// Count how often each value of target (class) appears
let targetValue = row[predictorCount];
let predictorValueFreq = freq.get(row[i]);
let count = predictorValueFreq.get(targetValue);
predictorValueFreq.set(targetValue, count+1);
}
// Find the most frequent class for each predictor value
let accuracy = 0;
for (let [predictorValue, predictorValueFreq] of freq) {
let maxCount = 0;
let chosenTargetValue;
for (let [targetValue, count] of predictorValueFreq) {
if (count > maxCount) {
// Make the rule assign that class to this value of the predictor
maxCount = count;
chosenTargetValue = targetValue;
}
}
freq.set(predictorValue, chosenTargetValue);
accuracy += maxCount;
}
// If this accuracy is best, then retain this frequency table
if (accuracy > bestAccuracy) {
bestAccuracy = accuracy;
bestPredictor = i;
bestFreq = freq;
}
}
// Return the best frequency table and the predictor for which it applies
return {
predictor: bestPredictor, // zero-based column number
rule: [...bestFreq.entries()]
}
}
let data = [
["rainy","hot","high",0,0],
["rainy","hot","high",1,0],
["overcast","hot","high",0,1],
["sunny","mild","high",0,1],
["sunny","cool","normal",0,1],
["sunny","cool","normal",1,0],
["overcast","cool","normal",1,1],
["rainy","mild","high",0,0],
["rainy","cool","normal",0,1],
["sunny","mild","normal",0,1],
["rainy","mild","normal",1,1],
["overcast","mild","high",1,1],
["overcast","hot","normal",0,1],
["sunny","mild","high",1,0]
];
let result = oneR(data);
console.log(result);

Related

What is the fastest format for a key in a JS object [Improved]

Backround Info
For a program I'm working on I need to track the path taken through a web. In this case, a web is defined a series of nodes, each node having zero or more child nodes. It's a web and not a tree because any node can point to any other and can be circular.
My program will start at one "entry point" node, and traverse through the web until it has taken a path that is considered "valid". All valid paths are stored in a series of nested maps, each map containing the keys of all possible next steps.
For example:
{ 0: {1: "success"} }
This nested map defines the path:
entryNode.children[0].children[1]
I have a minimal example of the traversal algorithm for benchmarking purposes:
// you can ignore this, it just helps me get some more info on the results
function getStandardDeviation (array) {
const n = array.length
const mean = array.reduce((a, b) => a + b) / n
return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n)
}
//// values that can be converted to a 1-digit base-36 number
// let list = [30, 31, 32]
//// without base-36: 411ms
//// with base-36: 2009ms
//// values that can be converted to a 2-digit base-36 number
// let list = [36, 37, 38]
//// without base-36: 391ms
//// with base-36: 1211ms
//// arbitrary large numbers
let list = [10000, 10001, 10002]
//// without base-36: 4764ms
//// with base-36: 1954ms
//// I tried encoding to base 36 to reduce the key length, hence the keys like '1o' and '1p'
//// This seems to hurt the performance of short numbers, but help the performance of large ones
// list = list.map(n => n.toString(36))
let maps = {}
let currentMap = maps
list.forEach((n, i) => {
if (i === list.length - 1) {
currentMap[n] = "res1"
} else {
const tempMap = {}
currentMap[n] = tempMap
currentMap = tempMap
}
})
console.log(maps)
// store samples for stdev
let times = []
const samples = 1000
const operations = 100000
// collect samples for stdev calculation
for (let k = 0; k < samples; k++) {
const begin = process.hrtime()
// dummy variable to simulate doing something with the result
let c = ""
let current = maps
for (let i = 0; i < operations; i++) {
// simulate what the final algorithm does
for (let j = 0; j < list.length; j++) {
current = current[list[j]]
if (typeof current === 'string') {
c = current
}
}
current = maps
}
const end = process.hrtime()
// get the ms difference between start and end
times.push((end[0] * 1000 + end[1] / 1000000) - (begin[0] * 1000 + begin[1] / 1000000));
}
const stdev = getStandardDeviation(times)
let total = 0;
times.forEach(t => total += t)
console.log("Time in millisecond is: ", total.toFixed(2), `+-${stdev.toFixed(2)}ms (${(total - stdev).toFixed(2)}, ${(total + stdev).toFixed(2)})`)
The Question
While testing, I wondered if using shorter keys would be faster, since I'm guessing JS hashes them somehow before doing the lookup. And I found that different object keys resulted in drastically different performance, varying by about an order of magnitude, with the only difference being the size/characters used in map's keys. There's not an obvious pattern that I can see, though.
I laid out the different input lists and their results in the top of the benchmark source, but here's the actual maps used and their respective times:
// raw numbers
{ '30': { '31': { '32': 'res1' } } }
Time in millisecond is: 411.00 +-0.13ms (410.86, 411.13)
// converted to base-36
{ u: { v: { w: 'res1' } } }
Time in millisecond is: 2009.91 +-0.18ms (2009.72, 2010.09)
// raw numbers
{ '36': { '37': { '38': 'res1' } } }
Time in millisecond is: 391.52 +-0.16ms (391.36, 391.69)
// converted to base-36
{ '10': { '11': { '12': 'res1' } } }
Time in millisecond is: 1211.46 +-0.19ms (1211.27, 1211.65)
// raw numbers
{ '10000': { '10001': { '10002': 'res1' } } }
Time in millisecond is: 4764.09 +-0.17ms (4763.93, 4764.26)
// converted to base-36
{ '7ps': { '7pt': { '7pu': 'res1' } } }
Time in millisecond is: 1954.07 +-0.17ms (1953.90, 1954.25)
Why do these differeny keys result in such wildly different timings? I've tested it a lot and they are quite consistent
Note:
I'm using Node V16.15.0 for benchmarking

Javascript - Merge Sort Visualizer using CSS Style to Sort, having issues

I am having an issue with my merge sort visualizer.
My program has no issues visualizing bubble sort or quick sort, as I can do the swapping operation of css property values in-place, but I am having major issues trying to get merge sort to work properly. The issue arises when I try to update a css property on the dom, it causes the sort to not function.
I have tried passing in copies of the data I wish to sort, and all sorts of weird things I could think of to make it work. I am currently trying to sort by the css property 'maxWidth'. I use that to display how large a div element is in the html file and then visualize the sort from there.
My latest thought has been to set all the div elements to have another css property equal to the maxWidth (I am using fontSize as it does not affect my program) and then sorting based on fontSize, allowing me in theory to change the maxWidth properties of the divs without affecting merge sorts algorithm.
I am including my entire js file as I hope reading my correctly working bubble sort or quick sort functions can help you see what I am trying to achieve. Thank you so much for taking the time to read this and offer any help!
Important Note: I am not trying to visualize the individual steps of merge sort yet because I am unable to update the final result to the html page without affecting the merge sort algorithm. According to console logs, my merge sort algorithm does indeed work, I just can't update the DOM without messing it up. Once I can do that, I will turn it into an asynchronous function using async and await like I previously did with bubble and quick sort.
/********* Generate and Store Divs to be Sorted *************/
const generateSortingDivs = (numOfDivs) => {
const divContainer = document.querySelector('.div-container');
let html = '';
for (let i = 0; i < numOfDivs; i++) {
let r = Math.floor(Math.random() * 100);
html += `<div class='sorting-div' id='id-${i}' style='max-width: ${r}%'>&nbsp</div>`;
}
divContainer.innerHTML = html;
for(let i = 0; i < numOfDivs; i++) {
let x = document.getElementById('id-' + i);
x.style.fontSize = x.style.maxWidth;
}
}
const storeSortingDivs = () => {
const divContainer = document.querySelector('.div-container');
let divCollection = [];
const numOfDivs = divContainer.childElementCount;
for(let i=0; i<numOfDivs; i++) {
let div = document.getElementById('id-' + i);
divCollection.push(div);
}
return divCollection;
}
/********** SLEEP FUNCTION ************/
//Used to allow asynchronous visualizations of synchronous tasks
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/******* SWAP FUNCTIONS *********/
//Used for Testing Algorithm before Animating Visualization
const syncSwap = (div1, div2) => {
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
async function asyncSwap(div1, div2) {
await sleep(50);
let tmp = div1.style.maxWidth;
div1.style.maxWidth = div2.style.maxWidth;
div2.style.maxWidth = tmp;
}
const swapDivs = (smallerDiv, biggerDiv) => {
return new Promise(resolve => {
setTimeout(() => {
let tmp = smallerDiv.style.maxWidth;
smallerDiv.style.maxWidth = biggerDiv.style.maxWidth;
biggerDiv.style.maxWidth = tmp;
resolve();
}, 50);
});
}
/****************************************/
/*********** SORTING ALGO'S *************/
/****************************************/
/******* BUBBLE SORT ***********/
async function bubbleSort(divCollection) {
displayBubbleSortInfo();
const len = divCollection.length;
for(let i=0; i<len; i++) {
for(let j=0; j<len-i-1; j++) {
divCollection[j].style.backgroundColor = "#FF4949";
divCollection[j+1].style.backgroundColor = "#FF4949";
let numDiv1 = parseInt(divCollection[j].style.maxWidth);
let numDiv2 = parseInt(divCollection[j+1].style.maxWidth);
let div1 = divCollection[j];
let div2 = divCollection[j+1];
if(numDiv1 > numDiv2) {
await swapDivs(div2, div1);
}
divCollection[j].style.backgroundColor = "darkcyan";
divCollection[j+1].style.backgroundColor = "darkcyan";
}
divCollection[len - i - 1].style.backgroundColor = 'black';
}
}
function displayBubbleSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Bubble Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n^2)</h2>`;
html += `<h3>Space Complexity: O(1)</h3>`;
html += `<p>This sorting algorithm loops through the array and continues to push the
largest found element into the last position, also pushing the last available
position down by one on each iteration. It is guaranteed to run in exactly
O(n^2) time because it is a nested loop that runs completely through.</p>`;
infoDiv.innerHTML = html;
}
/****** QUICK SORT ********/
async function quickSort(divCollection, start, end) {
if(start >= end) return;
let partitionIndex = await partition(divCollection, start, end);
await Promise.all([quickSort(divCollection, start, partitionIndex - 1), quickSort(divCollection, partitionIndex + 1, end)]);
}
/* This function takes last element as pivot, places
the pivot element at its correct position in sorted
array, and places all smaller (smaller than pivot)
to left of pivot and all greater elements to right
of pivot */
async function partition(divCollection, start, end) {
let pivotIndex = start;
let pivotValue = parseInt(divCollection[end].style.maxWidth);
for(let i = start; i < end; i++) {
if(parseInt(divCollection[i].style.maxWidth) < pivotValue) {
await asyncSwap(divCollection[i], divCollection[pivotIndex]);
pivotIndex++;
}
}
await asyncSwap(divCollection[pivotIndex], divCollection[end]);
return pivotIndex;
}
function displayQuickSortInfo(){
const infoDiv = document.querySelector('.algo-info');
let html = `<h1>Quick Sort Visualizer</h1>`;
html += `<h2>Time Complexity: O(n log n)</h2>`;
html += `<h3>Space Complexity: O(log n)</h3>`;
html += `<p>This sorting algorithm uses the idea of a partition to sort
each iteration recursively. You can implement quick sort
in a variety of manners based on the method in which you
pick your "pivot" value to partition the array. In this
visualization, I implemented the method that chooses the
last element of the array as the pivot value. You could
also choose the first value, the middle value, or the median
value based on the first, middle, and last values.</p>`;
infoDiv.innerHTML = html;
}
/* Merge Sort does not sort in place, and thus we have to be
* clever when implementing it and also editing the css style
* of our divs to show the visualization of how the algorithm
* works. My method is to store a copy of the divs, that way
* I can use one to be sorted by merge sort, and the other to
* change the css style property to show the visualization.
* Unlike Quick Sort and Bubble Sort, we are not swapping
* elements when sorting, instead we are merging entire
* arrays together as the name implies. */
function mergeSort(divCollection) {
if(divCollection.length < 2) return divCollection;
let middleIndex = Math.floor(divCollection.length / 2);
let left = divCollection.slice(0, middleIndex);
let right = divCollection.slice(middleIndex);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
let mergedCollection = [];
while(left.length && right.length) {
if(parseInt(left[0].style.fontSize) < parseInt(right[0].style.fontSize || right.length === 0)) {
let el = left.shift();
mergedCollection.push(el);
} else {
let el = right.shift();
mergedCollection.push(el);
}
}
let res = mergedCollection.concat(left.slice().concat(right.slice()));
return res;
}
/***** INITIALIZATION FUNCTION *******/
generateSortingDivs(10);
let divs = storeSortingDivs();
let copyDivs = [...divs];
console.log('Original State: ')
console.log(divs);
//bubbleSort(divs);
//displayQuickSortInfo();
//quickSort(divs, 0, divs.length-1);
let x = mergeSort(copyDivs);
console.log('Sorted: ');
console.log(x);

Compare arrays in loop - javascript

I'm doing a lottery system and I need to make sure that each Array is ​​different. This is my code:
var intNumberOfBets = 10;
let aLotteryTicket=[];
let aData = [];
for(intI = 0; intI <intNumberOfBets; intI++){
let oCasilla ={};
oCasilla.block=[];
for(intI = 0; intI <intNumberOfBets; intI++){
let oCasilla ={};
oCasilla.block=[];
Each "lottery ticket" has an array with 5 numbers. They can have the same numbers as others but in different positions.
for (let intB=1;intB<=5;intB++)
{
for(let intA=1;intA<=50; intA++){ aLotteryTicket.push(intA); }
oCasilla.block.push(aLotteryTicket.splice(parseInt(Math.random()*aLotteryTicket.length),1)); // ADD 5 NUMBERS RANDOMLY TO ARRAY
};
oCasilla.block.sort(function (a,b){ return (parseInt(a)-parseInt(b));});
aData.push(oCasilla);
alert(aData[intI].block); // show generated arrays
}//END FOR
How can I prevent each array from being the same as another, before adding it to my final Array aData[]?
Example:If i add the array 5,6,7,8,9 to oCasilla.block=[]; , i need to check that there is not another 5,6,7,8,9 in oCasilla.block=[];
Thanks in advance
You can use a set of string representations (numbers separated by comma built using join(',')) of your tickets to keep track of what was added, and only add if a ticket was not previously created.
function generateTicket() {
// generate an array with 5 unique random numbers
let a = new Set();
while (a.size !== 5) {
a.add(1 + Math.floor(Math.random() * 50));
}
return Array.from(a);
}
let oCasilla = {
block: []
};
let addedTickets = new Set(); // add stingified ticket arrays here
// add 10 unique tickets to oCasilla.block
while (oCasilla.block.length !== 10) {
const ticket = generateTicket();
if (!addedTickets.has(ticket.join(','))) {
oCasilla.block.push(ticket);
addedTickets.add(ticket.join(','));
}
}
console.log(oCasilla);

Can't figure out parseInt, even after research in JS and JQ [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I have an object with a property containing a large string. This property has a value with random numbers generated earlier in the script in the format x , x , x , x ... (isn't and can't be an array because of other needs for the variable within the program) and so on. I am trying to get the sum of these numbers and my first thought was to use parseInt() to do this by splitting them all up then adding them together, but when I do this it only returns the first number. Is this what I should do but I'm just doing it wrong? Or is there another function that would make this easier?
The program is a blackjack game I'm making to see how well i understand everything I am learning.
Here is the function i am trying to make to see if the user busts when taking a hit (not much so far because i can't figure out the parseInt thing)
'
function checkBust() {
var total = parseInt(user.hand, 10);
}
'
the user object
'
var user = {
hand: dealUser()
};
'
and the functions to set the object property
function randomCard() {
// random number between 0 and 10
var j = Math.random() * 10;
// round that number into a var called card
var card = Math.round(j);
// if card is 0, assign a J Q or K by making a random number again
if (card === 0) {
//another random number
var k = Math.random() * 10;
// checks random number and assign J Q or K
if (k <= 4) {
card = 'J';
} else if (k <= 7) {
card = 'Q';
}
else {
card = 'K';
}
}
// value of the function is a single card
return card;
}
function dealUser() {
// empty array to store cards
var x = [];
// var to start for loop
var i = 0;
// start for loop
for (i; i < 2; i++) {
// add a random card to the i^th index of x
x[i] = randomCard();
}
// value for function is array of two cards x[0] , x[1]
var cards = x[0] + " , " + x[1];
return cards;
}
parseInt will stop parsing when it reaches a non numeric character.
parseInt('1234,5678', 10); // => 1234
// since a comma (,) is not a numeric character, everything after is ignored.
You have to split the string into an array of strings using the comma as the delimiter:
'1234,5678'.split(','); // => ['1234', '5678'];
Then parse each element of the array to convert them to numbers and then you can sum them.
Here's how I'd do it:
var nums = "1,2,3,4,5";
var sum = nums.split(',').reduce(function(memo, num) {
return memo + parseInt(num, 10);
}, 0);
console.log(sum); // => 15
That should work. See jsbin example.
Note the split parameter needs to match the delimiters you use in your string. for this example ',' is appropriate. For your example you might need /\s*,\s*/.
Unrelated
Since you provided an example of code I can see that you're spending a lot of effort attempting to duck punch and transform the values to the types you need instead of exposing the types in an object. Might I suggest:
function Stack(cards) {
this.cards = cards || [];
}
Stack.prototype.toString = function() {
return this.cards.join(' , ');
};
Stack.prototype.sum = function() {
return this.cards.reduce(function(memo, card) {
return memo + parseInt(card, 10);
}, 0);
};
function randomCard() {
return Math.floor(Math.random() * 13) + 1;
}
Stack.dealHand = function() {
var card1 = randomCard(), card2;
do { card2 = randomCard(); } while (card1 === card2);
return new Stack([card1, card2]);
};
// Example
var hand = Stack.dealHand();
console.log(hand + ' = ' + hand.sum()); // => '3 , 11 = 14'

Javascript challenge - which basket contains the last apple?

I'm presented with the following challenge question:
There are a circle of 100 baskets in a room; the baskets are numbered
in sequence from 1 to 100 and each basket contains one apple.
Eventually, the apple in basket 1 will be removed but the apple in
basket 2 will be skipped. Then the apple in basket 3 will be removed.
This will continue (moving around the circle, removing an apple from a
basket, skipping the next) until only one apple in a basket remains.
Write some code to determine in which basket the remaining apple is
in.
I concluded that basket 100 will contain the last apple and here's my code:
var allApples = [];
var apples = [];
var j = 0;
var max = 100;
var o ='';
while (j < max) {
o += ++j;
allApples.push(j);
}
var apples = allApples.filter(function(val) {
return 0 == val % 2;
});
while (apples.length > 1) {
for (i = 0; i < apples.length; i += 2) {
apples.splice(i, 1);
}
}
console.log(apples);
My question is: did I do this correctly? What concerns me is the description of "a circle" of baskets. I'm not sure this is relevant at all to how I code my solution. And would the basket in which the remaining apple reside be one that would otherwise be skipped?
I hope someone can let me know if I answered this correctly, answered it partially correct or my answer is entirely wrong. Thanks for the help.
So, ... I got WAY too into this question :)
I broke out the input/output of my last answer and that revealed a pretty simple pattern.
Basically, if the total number of items is a power of 2, then it will be the last item. An additional item after that will make the second item the last item. Each additional item after that will increase the last item by 2, until you reach another item count that is again divisible by a power of 2. Rinse and repeat.
Still not a one-liner, but will be much faster than my previous answer. This will not work for 1 item.
var items = 100;
function greatestPowDivisor(n, p) {
var i = 1;
while(n - Math.pow(p, i) > 0) {
i++;
}
return Math.pow(p, (i - 1));
}
var d = greatestPowDivisor(items, 2)
var last_item = (items - d) * 2;
I believe Colin DeClue is right that there is a single statement that will solve this pattern. I would be really interested to know that answer.
Here is my brute force solution. Instead of moving items ("apples") from their original container ("basket") into a discard pile, I am simply changing the container values from true or false to indicate that an item is no longer present.
var items = 100;
var containers = [];
// Just building the array of containers
for(i=0; i<items; i++) {
containers.push(true);
}
// count all containers with value of true
function countItemsLeft(containers) {
total = 0;
for(i=0; i<containers.length; i++) {
if(containers[i]) {
total++;
}
}
return total;
}
// what is the index of the first container
// with a value of true - hopefully there's only one
function getLastItem(containers) {
for(i=0; i<containers.length; i++) {
if(containers[i]) {
return(i);
}
}
// shouldn't get here if the while loop did it's job
return false;
}
var skip = false;
// loop through the items,
// setting every other to false,
// until there is only 1 left
while(countItemsLeft(containers) > 1) {
for(i=0; i<containers.length; i++) {
if(containers[i]) {
if(skip) {
skip = false;
} else {
containers[i] = false;
skip = true;
}
}
}
}
// what's the last item? add one to account for 0 index
// to get a human readable answer
var last_item = getLastItem(containers) + 1;
Needs error checking, etc... but it should get the job done assuming items is an integer.

Categories

Resources