I need an hash table in Javascript, i.e. to implement an associative array which maps keys (strings) to values (in my case, these are several integer arrays). I realized that this kind of approach is not commonly used, or at least I haven't found it on the web yet:
var hash = ['s0'];
for (var i = 5; i >= 1; i--) {
var r = Math.floor(Math.random() * 3);
hash['s'+i] = [r, r*2, r^2];
}
console.log(hash);
hash.forEach(function (v, i, a) {
document.getElementById('foreach').innerHTML += i + ' => ' + v + '<br>';
})
for (var i = 5; i >= 1; i--) {
var key = 's'+i;
document.getElementById('for').innerHTML += key + ' => [' + hash[key].toString() + ']<br>';
}
<p id="foreach">forEach (val,index):<br/></p>
<p id="for">for:<br/></p>
Perhaps due to the fact that the declared array seems to not be correctly mapped after I add the new values (open your console and click the + button, you can see the values are there even when it displays [s1]). The forEach keeps assuming the array only has one value, but if I access any of those keys directly, e.g. hash['s3'], the respective array is returned.
Therefore, am I doing something wrong? Should I even use this approach?
If objects in JSON are more appropriate for this case, what is the best way to implement something simple and similar to the example above?
Furthermore, if key_string is the string I want as key, hash.push(key_string: val_array) fails because it is not a "formal parameter". However by doing something like:
hash.push({'key':key_string,'value':val_array})
How can I access one of those arrays in the simplest way possible through its associated key?
Why can't you use a JavaScript Map()?
MDN JavaScript Reference: Map
I modified your code below to use a Map instead of an Array:
var map = new Map();
for (var i = 5; i >= 1; i--) {
var r = Math.floor(Math.random() * 3);
map.set('s'+i, [r, r*2, r^2]);
}
console.log(map);
map.forEach(function (v, i, m) {
document.getElementById('foreach').innerHTML += i + ' => ' + v + '<br>';
})
for (var i = 5; i >= 1; i--) {
var key = 's'+i;
document.getElementById('for').innerHTML += key + ' => [' + map.get(key).toString() + ']<br>';
}
<p id="foreach">forEach (val,index):<br/></p>
<p id="for">for:<br/></p>
Javascript's object type covers all the behavior you are looking for:
var obj = {};
for (var i = 5; i >= 1; i--) {
var r = Math.floor(Math.random() * 3);
obj['s'+i] = [r, r*2, r^2];
}
The cool thing about the object type in Javascript is you can access properties using the associate array-like syntax or using dot notation.
obj['key'] === obj.key
Please, check this example in the console.
var hash = {'s0':' '};
for (var i = 5; i >= 1; i--) {
var r = Math.floor(Math.random() * 3);
hash['s'+i] = [r, r*2, r^2];
}
see that hash object now contains key-value mapping
console.log(hash);
to access the object with forEach you can extract the keys of the object as an array and iterate over it:
Object.keys(hash).forEach(function (v, i, a) {
console.log( i , v, hash[v] );
})
You may also start using such libraries as https://lodash.com/ that implement a number of common operations over collections.
Related
I want to get a different slice of the array in the console.log depending on what button I press, however no matter what button I end up pressing I always get the last 20 elements of the array.
How can I make it behave as expected?
for (var i = 0; i < array.length; i++) {
var b;
var NewArr = [];
if (i % 20 == 0) {
NewArr = array.slice(i, i + 20);
b = createButton(i + "-" + (i + 20), NewArr);
b.position(x, y + i * 1.5);
b.mousePressed(function () {
console.log(NewArr);
});
}
}
Use let instead of var.
var declarations are hoisted (there are many other answers/wiki articles about JS hoisting already if you want to read more) to the top of the current function scope, even you declare it inside the for loop.
So I've created this JS code to get a map where every word in a string is the key and the amount of times that word appears is the value.
var txtArray = txtVar.split(" ");
var txtMap = {};
for (var i = 0; i<txtArray.length;i++){
if(txtArray[i] in txtMap)
txtMap[txtArray[i]] += 1;
else
txtMap[txtArray[i]] = 1;
}
Now i need to sort them so that I can somehow rank them by the 10 longest words and 10 most used words and so on and I guess I could find a way to do it by making a for loop where i stored them in arrays and then pushed the oldest "top" word up or down the list when a new one comes along, but I think there must be a less awkward way to do this task?
It's probably an adequate approach to do as you're currently doing, then sorting/slicing those results when you're done:
var allWords = Object.keys(txtMap);
var tenMostFrequent = allWords
.sort((a, b) => txtMap[b] - txtMap[a])
.slice(0, 10);
var longestWords = allWords
.sort((a, b) => b.length - a.length)
.slice(0, 10);
You can use Object.keys() to get an array of the words, then .sort() as you see fit and use .slice() to get the top 10:
var txtVar = "some sample input to have something to fill the array with - random words the in are the it the what with some random whatever what what ridiculous whatever hello goodbye words the to have";
var txtArray = txtVar.split(" ");
var txtMap = {};
for (var i = 0; i<txtArray.length;i++){
if(txtArray[i] in txtMap)
txtMap[txtArray[i]] += 1;
else
txtMap[txtArray[i]] = 1;
}
var tenLongest = Object.keys(txtMap).sort(function(a, b) {
return b.length - a.length;
}).slice(0, 10);
console.log("Longest: ", tenLongest.join(", "));
var tenMostUsed = Object.keys(txtMap).sort(function(a, b) {
return txtMap[b] - txtMap[a];
}).slice(0, 10)
.map(function(v) { return v + " (" + txtMap[v] + ")"; });
console.log("Most used: ", tenMostUsed.join(", ") );
Given this JavaScript array:
var list = [1,1,1,2,2,2,2]
I want to know how I can produce an HTML list below that has each unique item in the array and number of times that they appear in the array. I just want to know the JavaScript to produce the data, I can generate the HTML.
1 is x3
2 is x4
I'm confused about how to achieve this. Basically, similar to shopping cart quantity functionality, but using the array.
http://jsfiddle.net/37ab3k00/
Use .reduce to reduce your array to an object of quantities.
var list = [1, 1, 1, 2, 2, 2, 2];
var quantities = list.reduce(function(obj, n) {
if (obj[n]) obj[n]++;
else obj[n] = 1;
return obj;
}, {});
var ul = document.querySelector("ul");
for (var n in quantities) {
ul.appendChild(document.createElement("li")).textContent = n + " has a quantity x" + quantities[n];
}
<ul></ul>
The first argument to .reduce() is a function that gets invoked for each member in the Array.
The second argument is an object that we're going to pass along to each iteration. It gets passed as the first argument to the function we provided, and we always return it so that it always gets passed as that argument. This is called the accumulator.
The n argument to the function we provided is the value of the current member in the list. So what we do is first see if our obj has a truthy n member. If so, it must have been encountered already, so we increment it. If not, we assign it the initial value of 1 to represent the first n that was found for that value.
var list = [1,1,1,2,2,2,2]
var counts = {};
for (var i = 0; i < list.length; i++) {
counts[list[i]] = 1 + (counts[list[i]] || 0);
}
Demo: http://jsfiddle.net/famn4zcL/2/
Add to HTML
var li = '';
for (var el in counts) {
li += '<li>' + el + ' is x' + counts[el] + '</li>';
}
document.getElementById('list').innerHTML = li;
Demo: http://jsfiddle.net/famn4zcL/3/
Another way would be using array of objects (those can be easily upgraded with additional data that you probably would need building products), like so:
HTML:
<span id="display"></span>
JS (plain, no Framework):
var objects = [
{prod:0,quant:00},
{prod:1,quant:11},
{prod:2,quant:22},
{prod:3,quant:33},
{prod:4,quant:44},
{prod:5,quant:55}
];
var list_of_objects = "", display_id = document.getElementById("display");
for (var key in objects) {
if (objects.hasOwnProperty(key)) {
console.log(key);
list_of_objects += '<li>'+objects[key].prod + ' has a qtty x ' + objects[key].quant+'</li>';
}
}
console.log(list_of_objects);
display_id.innerHTML = list_of_objects;
So you could easily upgrade product data with new info, like:
var objects = [
{prod:0,quant:00,url:"http://url00"},
{prod:1,quant:11,url:"http://url11"},
{prod:2,quant:22,url:"http://url22"},
{prod:3,quant:33,url:"http://url33"},
{prod:4,quant:44,url:"http://url44"},
{prod:5,quant:55,url:"http://url55"}
];
JSfiddle to play with: http://jsfiddle.net/7hokfmdu/
The enumeration of JS objects seems to be inconsistent in Firefox.
Code:
var a = {"3":"a", "2":"b", "foo":"c", "1":"d"};
var str = "";
for(var n in a) { str += n + " = " + a[n] + "\n"; }
alert(str);
Result with FF22 on Windows:
1 = d
2 = b
3 = a
foo = c
Result expected (and what I get with FF20 on Linux):
3 = a
2 = b
foo = c
1 = d
How can I keep the Elements in the same order as inserted?
I know the ECMA specification doesn't say how the enumeration should be done, therefore it can't be called a bug. But I need the elements in the order inserted. (Reason: I get a JSON-encoded hash-table which is ordered server-side. Until recently the order was kept, now the whole list is a mess because it's ordered by the keys)
No, you can't get that. The order of keys in object is undetermined and can be anything JS implementation wants.
You can, however, sort the keys:
var a = {"3":"a", "2":"b", "foo":"c", "1":"d"};
var str = "";
for(var n in Object.keys(a).sort(your_sorting_function)) {
str += n + " = " + a[n] + "\n";
}
alert(str);
your_sorting_function should accept two arguments, which effectively will be names of the keys in a object. Then this function should return 0 for identical keys, 1 if the first key is considered "bigger" and -1 otherwise.
Object.keys() does not exist in all the browsers, but there's plenty of implementations that can be found.
For example:
Object.keys = Object.keys || function(o) {
var result = [];
for(var name in o) {
if (o.hasOwnProperty(name))
result.push(name);
}
return result;
};
Here is my code so far for my school project (using Murach's JavaScript and DOM Scripting by Ray Harris). The chapter is only about Arrays and does not cover Prototypes, but I wanted to try it out based on Internet tutorials and references:
/*
Operation
This application stores the last name, first name, and score for
one or more students and it calculates the average score for all of the scores
that have been entered. When the user clicks on the Clear button, this
application clears the score data from this application. When the user clicks
on the Sort button, this application sorts the data in alphabetical order by
last name.
Specifications
The program should use one or more arrays to store the data.
Assume that the user will enter valid data.
*/
var $ = function (id)
{
return document.getElementById(id);
}
/*
Array prototype object extension for averaging the contents
"Adding a method to the built-in Array object to extract the average
of any numerical values stored in the array is therefore a useful
addition to that object." http://javascript.about.com/library/blaravg.htm
*/
Array.prototype.average = function ()
{
var avg = 0;
var count = 0;
for (var i = 0; i<this.length; i++)
{
//never gets here:
alert(i + ": " + this[i]);
var e = +this[i];
if(!e && this[i] !== 0 && this[i] !== '0')
{
e--;
}
if (this[i] == e)
{
avg += e;
count++;
}
}
return avg / count;
}
var addScore = function ()
{
studentScores[$('last_name').value + ', ' + $('first_name').value] = $('score').value;
update();
}
var clearScore = function ()
{
for (var i in studentScores)
{
studentScores[i] = '';
}
update();
}
var sortScore = function ()
{
scores.sort();
update();
}
var update = function ()
{
var result = '';
for (var i in studentScores)
{
result += (i + ': ' + studentScores[i] + '\n');
}
$('scores').value = result;
$('average_score').value = studentScores.average().toFixed(1);
}
window.onload = function ()
{
//a variable is initialized inside a function without var, it will have a global scope:
studentScores = [];
$('add_button').onclick = addScore;
$('sort_button').onclick = sortScore;
$('clear_button').onclick = clearScore;
$('last_name').focus();
}
When the code enters the "update()" function (end of the "addScore()" function) and accesses the array,
it populates the "literal" code from the Prototype into the text area (and fails to find the average on the next line):
I don't have enough rep points to post the image, but here is my output (there are no errors in the Chrome JS Console):
lowe, doug: 82
average: function ()
{
var avg = 0;
var count = 0;
for (var i = 0; i<this.length; i++)
{
//never gets here:
alert(i + ": " + this[i]);
var e = +this[i];
if(!e && this[i] !== 0 && this[i] !== '0')
{
e--;
}
if (this[i] == e)
{
avg += e;
count++;
}
}
return avg / count;
}
Any help appreciated (best practice or algorithm suggestions welcome)
Change this:
studentScores = []
to this:
studentScores = {}
...so that you're using an Object instead of an Array.
Your for loop in average() is just iterating numeric indices instead of the non-numeric keys you created.
Create your average() method as a standalone function like the others, and pass studentScores to it to calculate the average, and then use for-in instead of for.
That's simple: Do not use for…in enumerations for looping Arrays! You do so in your clearScore and update functions.
for (var prop in obj) loops over all [enumerable] properties, including those that are inherited from Array.prototype (for Array objects at least). A for (var i=0; i<array.length; i++) loop will not have that problem.
You have to decide whether studentScores is intended to be an array (i.e., an integer is used to access the stored data) or an Object/Associative Array (a string is used to set/get an element).
If you want to use the student's name as the key, you should declare studentScores as an object, and your 'average' method would have to be added to the Object prototype (which I don't recommend).
With the current state of the code, you have stumbled on the fact that an Array is also an object, and can have arbitrary properties attached to it, like any other object. You have added properties by name, but in your average method, you are trying to access numerically based indices. But that's not where the data you're adding is stored.
> a = [];
[]
> a['foo'] = 'bar';
'bar'
> a.length
0
> a[3] = 0;
0
> a.length
4