I'm a sudent starting to learn Javascript. We're working with arrays. The question I have is when working with two arrays, how can I match indexes when one index is chosen. Example:
var nameArray = [ "Earl", "John", "Shelia", "Lenny" ];
var testScoreArray = [ 85, 92, 87, 99 ];
If you chose Lenny, I want to be able to match his score with his name and vice versa. The arrays aren't fixed and are part of a program that can add names and scores at any time. I understand you can program it to store the names and test scores as a multi-dimensional array but not for this example.
I would like to be able to look a score up based on it being the lowest, highest or another situation and match the score with the name and vise versa.
Given this:
var array1 = ['Earl', 'John', 'Shelia', 'Lenny']
… you can get the index of "Lenny" like this:
array1.indexOf('Lenny'); //3
So you simply need to plug that into the other array:
array2[array1.indexOf('Lenny')] //99
Snippet
var array1 = ['Earl', 'John', 'Shelia', 'Lenny'],
array2 = [85,92,87,99]
console.log(array2[array1.indexOf('Lenny')]); //99
The data structure that you are looking for is a bidirectional map. Description from Wikipedia:
In computer science, a bidirectional map, or hash bag, is an associative data structure in which the (key, value) pairs form a one-to-one correspondence. Thus the binary relation is functional in each direction: value can also act as a key to key. A pair (a, b) thus provides a unique coupling between a and b so that b can be found when a is used as a key and a can be found when b is used as a key.
Mathematically, a bidirectional map between keys and values is equivalent to a pair of functions:
A function f from keys to values (i.e. a -> b).
A function g from values to keys (i.e. b -> a).
In addition, there are two constraints imposed on bidirectional maps:
Given any key x the constraint g (f x) = x must be satisfied.
Given any value y the constraint f (g y) = y must be satisfied.
Therefore, Bimap(a, b) = (a -> b, b -> a).
Alternatively, a bidirectional map can be represented as a pair of maps or associative arrays:
Since Map(a, b) = a -> b, therefore Bimap(a, b) = (Map(a, b), Map(b, a)).
Internally, an associative array could be represented as either a pair of lists (i.e. ([a], [b])) or a list of pairs (i.e. [(a, b)]) or hash tables or any other suitable data structure.
Let's implement a bidirectional map data structure using hash tables internally for efficiency:
function Bimap(keys, vals) {
var length = Math.min(keys.length, vals.length);
var valMap = this.lookupVal = Object.create(null);
var keyMap = this.lookupKey = Object.create(null);
for (var i = 0; i < length; i++) {
var key = keys[i];
var val = vals[i];
valMap[key] = val;
keyMap[val] = key;
}
}
You can use it as follows:
var bimap = new Bimap(["Earl", "John", "Shelia", "Lenny"], [85, 92, 87, 99]);
bimap.lookupVal["Lenny"]; // 99
bimap.lookupKey[99]; // "Lenny"
You can also add new key value pairs to the bidirectional map as follows:
Bimap.prototype.insert = function (keys, vals) {
var length = Math.min(keys.length, vals.length);
var valMap = this.lookupVal;
var keyMap = this.lookupKey;
for (var i = 0; i < length; i++) {
var key = keys[i];
var val = vals[i];
valMap[key] = val;
keyMap[val] = key;
}
};
You would use it as follows:
bimap.insert(["Jane"], [96]);
bimap.lookupVal["Jane"]; // 96
bimap.lookupKey[96]; // "Jane"
You can summarize bidirectional maps (such as finding min/max values, etc.) using reduce:
Bimap.prototype.reduceVals = function (inductive, basis) {
var valMap = this.lookupVal, undefined;
return basis === undefined ?
Object.keys(valMap).reduce(callback) :
Object.keys(valMap).reduce(callback, basis);
function callback(proof, key) {
return inductive(proof, valMap[key]);
}
};
For example:
bimap.lookupKey[bimap.reduceVals(Math.min)]; // "Earl"
bimap.lookupKey[bimap.reduceVals(Math.max)]; // "Lenny"
Finally, most JavaScript engines now natively support the Map data structure. Hence, you can implement Bimap in terms of that instead.
You can create object of array in javascript like this
[{Name : "Test" , Score : 25},{Name : "Test Two" , Score : 35}]
The best way to do this is by using either associative arrays or multi-dimensional arrays. I would suggest taking a look at this tutorial: www.w3schools.com/js/js_arrays.asp
If I understand your question correctly, you want to be able to match a name with a score in two different arrays. To do this, let's say they give you Lenny, first by using a for loop, you can loop through the first array, and look for his name, while keeping count on which index you're on.
The code would look like
int index = 0;
for(; index < array1.size(); index++)
When you find his name, just use that index that you found like array2[index] and you'll have your answer.
Related
I have two arrays one with label date i.e [Date, Date, Date ...] and
the other with the actual date data i.e [2021-11-26, 2021-11-25, ...].
I want to combine these two arrays such that I get array of objects such as [ { Date: 2021-11-26}, {Date:2021-11-25}, {..}, ...].
I have tried these two methods
obj = {};
for (var i = 0, l = date_label.length; i < l; i += 1) {
obj[date_label[i]] = data_date[i]
}
console.log(obj);
and
_.zipObject(date_label, data_date);
However it only ends up giving me the last date of my data set, in an object data structure ie { Date: 1999-11-24}
The keys inside an object / associative array are unique. Your obj is such a thing. If you turn it into a regular array and push new objects into it, it will work.
const obj = [];
for (let i = 0, l = date_label.length; i < l; i++) {
obj.push({[date_label[i]]: data_date[i]})
}
console.log(obj);
You should probably assert that both your arrays have the same length.
The issues you are facing is that your date_label are the same and the loop are replacing the dates on the same label, again and again, you just need to change the label name and give unique to each one or you change them into the loop as well like this (obj[date_label[i] + str(i)] = data_date[i]).
date_label = ['date1', 'date2', 'date3', .....]
obj = {};
for (var i = 0, l = date_label.length; i < l; i += 1) {
obj[date_label[i]] = data_date[i]
}
console.log(obj);
obj is of type array not object.
data_date needs to be in string format.
for(var i= 0; i<data_date.length-1;i++) {
obj.push({"Date":date_date[i]}) }
with array reduce
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
var myFinalArray = data_date.reduce(
(previousValue, currentValue, currentIndex, array) => ({
currentValue: date_label[currentIndex]
}), {});
Hello AshleyCrasto,
Welcome to Stackoverflow.
Sol : Well, the other members have given solution on how to achieve the desired result. I will emphasize on why you are getting the single object.
it only ends up giving me the last date of my data set, in an object data structure ie { Date: 1999-11-24}
You need to understand how references work in JavaScript. Heres the catch,
As the values in date_label are all same
[Date, Date, Date ...]
When you use,
obj[date_label[i]] = data_date[i]
Everytime, it get executed the same key value will be keep updating instead of creating new key and new value. Because the same values holds same reference.
So, first time {"date" : "somevalue"} will be there, then
second time {"date" : "somevalue2"}, the value of key "date" will be updated
with new value. This is due to same key.
Therefore, you need to take of this thing. For your better understanding here is my code: (same as others but elaborately)
const date_label = ["date","date"]
const data_date = [2021-11-26, 2021-11-25]
function returnObj(label, value){
//this will return a new object with provided label and value.
const Obj = {};
Obj[label] = value
return Obj
}
let listOfObjects = []
for(let i=0 ; i< date_label.length ; i++){
//new object will be added to list.
const obj = returnObj(date_label[i],data_date[i])
listOfObjects.push(obj)
}
console.log(listOfObjects)
So, I have List a:
let a = Immutable.List([1])
and List b:
let b = Immutable.List([2, 3])
I want to get List union === List([1, 2, 3]) from them.
I try to merge them fist:
let union = a.merge(b); // List([2, 3])
It seems like merge method operates with indexes, not with values so overrides first item of List a with first item of List b. So, my question is what is the most simple way to get union of several lists (ideally without iterating over them and other extra operations).
You are correct about merge. Merge will update the index with the current value of the merging list. So in your case you had
[0] = 1
and merged it with
[0] = 2
[1] = 3
which ended up overwriting [0]=1 with [0]=2, and then set [1]=3 resulting in your observed [2,3] array after merging.
A very simple approach to solving this would be to use concat
var a = Immutable.List([1]);
var b = Immutable.List([2,3]);
var c = a.concat(b);
And it will work for this situation. However, if the situation is more complex, this may be incorrect. For example,
var a = Immutable.List([1,4]);
var b = Immutable.List([2,3,4]);
this would give you two 4's which is not technically a union anymore. Unfortunately there is no union included in Immutable. An easy way to implemented it would be to set each value in each list as the key to an object, and then take those keys as the resulting union.
jsFiddle Demo
function union(left,right){
//object to use for holding keys
var union = {};
//takes the first array and adds its values as keys to the union object
left.forEach(function(x){
union[x] = undefined;
});
//takes the second array and adds its values as keys to the union object
right.forEach(function(x){
union[x] = undefined;
});
//uses the keys of the union object in the constructor of List
//to return the same type we started with
//parseInt is used in map to ensure the value type is retained
//it would be string otherwise
return Immutable.List(Object.keys(union).map(function(i){
return parseInt(i,10);
}));
}
This process is O(2(n+m)). Any process which uses contains or indexOf is going to end up being O(n^2) so that is why the keys were used here.
late edit
Hyper-performant
function union(left,right){
var list = [], screen = {};
for(var i = 0; i < left.length; i++){
if(!screen[left[i]])list.push(i);
screen[left[i]] = 1;
}
for(var i = 0; i < right.length; i++){
if(!screen[right[i]])list.push(i);
screen[right[i]] = 1;
}
return Immutable.List(list);
}
Actually Immutable.js does have a union - it is for the Set data structure:
https://facebook.github.io/immutable-js/docs/#/Set/union
The great thing about Immutable.js is it helps introduce more functional programming constructs into JS - in this instance a common interface and the ability to abstract away data types. So in order to call union on your lists - convert them to sets, use union and then convert them back to lists:
var a = Immutable.List([1, 4]);
var b = Immutable.List([2, 3, 4]);
a.toSet().union(b.toSet()).toList(); //if you call toArray() or toJS() on this it will return [1, 4, 2, 3] which would be union and avoid the problem mentioned in Travis J's answer.
The implementation of List#merge has changed since this question was posted, and in the current version 4.0.0-rc-12 List#merge works as expected and solves the issue.
Javascript has arrays which use numeric indexes ["john", "Bob", "Joe"] and objects which can be used like associative arrays or "maps" that allow string keys for the object values {"john" : 28, "bob": 34, "joe" : 4}.
In PHP it is easy to both A) sort by values (while maintaining the key) and B) test for the existence of a value in an associative array.
$array = ["john" => 28, "bob" => 34, "joe" => 4];
asort($array); // ["joe" => 4, "john" => 28, "bob" => 34];
if(isset($array["will"])) { }
How would you acheive this functionality in Javascript?
This is a common need for things like weighted lists or sorted sets where you need to keep a single copy of a value in data structure (like a tag name) and also keep a weighted value.
This is the best I've come up with so far:
function getSortedKeys(obj) {
var keys = Object.keys(obj);
keys = keys.sort(function(a,b){return obj[a]-obj[b]});
var map = {};
for (var i = keys.length - 1; i >= 0; i--) {
map[keys[i]] = obj[keys[i]];
};
return map;
}
var list = {"john" : 28, "bob": 34, "joe" : 4};
list = getSortedKeys(list);
if(list["will"]) { }
Looking at this answer by Luke Schafer I think I might have found a better way to handle this by extending the Object.prototype:
// Sort by value while keeping index
Object.prototype.iterateSorted = function(worker, limit)
{
var keys = Object.keys(this), self = this;
keys.sort(function(a,b){return self[b] - self[a]});
if(limit) {
limit = Math.min(keys.length, limit);
}
limit = limit || keys.length;
for (var i = 0; i < limit; i++) {
worker(keys[i], this[keys[i]]);
}
};
var myObj = { e:5, c:3, a:1, b:2, d:4, z:1};
myObj.iterateSorted(function(key, value) {
console.log("key", key, "value", value)
}, 3);
http://jsfiddle.net/Xeoncross/kq3gbwgh/
With ES6 you could choose to extend the Map constructor/class with a sort method that takes an optional compare function (just like arrays have). That sort method would take two arguments, each of which are key/value pairs so that the sorting can happen on either the keys or the values (or both).
The sort method will rely on the documented behaviour of Maps that entries are iterated in insertion order. So this new method will visit the entries according to the sorted order, and then delete and immediately re-insert them.
Here is how that could look:
class SortableMap extends Map {
sort(cmp = (a, b) => a[0].localeCompare(b[0])) {
for (const [key, value] of [...this.entries()].sort(cmp)) {
this.delete(key);
this.set(key, value); // New keys are added at the end of the order
}
}
}
// Demo
const mp = new SortableMap([[3, "three"],[1, "one"],[2, "two"]]);
console.log("Before: ", JSON.stringify([...mp])); // Before
mp.sort( (a, b) => a[0] - b[0] ); // Custom compare function: sort numerical keys
console.log(" After: ", JSON.stringify([...mp])); // After
I'm not sure why none of these answers mentions the existence of a built-in JS class, Set. Seems to be an ES6 addition, perhaps that's why.
Ideally override either add or keys below... NB overriding keys doesn't even need access to the Set object's prototype. Of course you could override these methods for the entire Set class. Or make a subclass, SortedSet.
const mySet = new Set();
const mySetProto = Object.getPrototypeOf(mySet);
const addOverride = function(newObj){
const arr = Array.from(this);
arr.add(newObj);
arr.sort(); // or arr.sort(function(a, b)...)
this.clear();
for(let item of arr){
mySetProto.add.call(this, item);
}
}
mySet.add = addOverride;
const keysOverride = function(){
const arr = Array.from(this);
arr.sort(); // or arr.sort(function(a, b)...)
return arr[Symbol.iterator]();
}
mySet.keys = keysOverride;
Usage:
mySet.add(3); mySet.add(2); mySet.add(1); mySet.add(2);
for(let item of mySet.keys()){console.log(item)};
Prints out:
1 ... 2 ... 3
NB Set.keys() returns not the items in the Set, but an iterator. You could choose to return the sorted array instead, but you'd obviously be breaking the class's "contract".
Which one to override? Depends on your usage and the size of your Set. If you override both you will be duplicating the sort activity, but in most cases it probably won't matter.
NB The add function I suggest is of course naive, a "first draft": rebuilding the entire set each time you add could be pretty costly. There are clearly much cleverer ways of doing this based on examining the existing elements in the Set and using a compare function, a binary tree structure*, or some other method to determine where in it to add the candidate for adding (I say "candidate" because it would be rejected if an "identical" element, namely itself, were already found to be present).
The question also asks about similar arrangements for a sorted map... in fact it turns out that ES6 has a new Map class which lends itself to similar treatment ... and also that Set is just a specialised Map, as you might expect.
* e.g. https://github.com/Crizstian/data-structure-and-algorithms-with-ES6/tree/master/10-chapter-Binary-Tree
You usually don't sort an object. But if you do: Sorting JavaScript Object by property value
If you want to sort an array, let's say the following
var arraylist = [{"john" : 28},{ "bob": 34},{ "joe" : 4}];
You can always use Array.prototype.sort function.
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Maybe this code look like what you want:
Object.prototype.asort = function(){
var retVal = {};
var self = this;
var keys = Object.keys(this);
keys = keys.sort(function(a,b){return self[a] - self[b]});
for (var i = 0; i < keys.length; i++) {
retVal[keys[i]] = this[keys[i]];
}
return retVal;
}
var map = {"john" : 28, "bob": 34, "joe" : 4}
var sortedMap = map.asort();//sortedMap["will"]: undefined
If you use the open source project jinqJs its easy.
See Fiddler
var result = jinqJs()
.from([{"john" : 28},{ "bob": 34},{ "joe" : 4}])
.orderBy([{field: 0}])
.select();
Here's an implementation of OrderedMap.
Use the functions get() and set() to extract or push key value pairs to the OrderedMap.
It is internally using an array to maintain the order.
class OrderedMap {
constructor() {
this.arr = [];
return this;
}
get(key) {
for(let i=0;i<this.arr.length;i++) {
if(this.arr[i].key === key) {
return this.arr[i].value;
}
}
return undefined;
}
set(key, value) {
for(let i=0;i<this.arr.length;i++) {
if(this.arr[i].key === key) {
this.arr[i].value = value;
return;
}
}
this.arr.push({key, value})
}
values() {
return this.arr;
}
}
let m = new OrderedMap();
m.set('b', 60)
m.set('a', 10)
m.set('c', 20)
m.set('d', 89)
console.log(m.get('a'));
console.log(m.values());
https://github.com/js-sdsl/js-sdsl
The OrderedMap in Js-sdsl maybe helpful.
This is a sorted-map which implement refer to C++ STL Map.
/*
* key value
* 1 1
* 2 2
* 3 3
* Sorted by key.
*/
const mp = new OrderedMap(
[1, 2, 3].map((element, index) => [index, element])
);
mp.setElement(1, 2); // O(logn)
mp.eraseElementByKey(1) // O(logn)
// custom comparison function
mp = new OrderedMap(
[1, 2, 3].map((element, index) => [index, element]),
(x, y) => x - y
);
// enable tree iterator index (enableIndex = true)
console.log(new OrderedMap([[0, 1], [1, 1]], undefined, true).begin(),next().index); // 1
This is what I would like to become:
This is my javascript:
var retrievedObject = localStorage.getItem('exhibitor');
// CALL FUNCTION
parsePerObject(JSON.parse(retrievedObject));
function parsePerObject(data){
}
This is my object in my localStorage :
{"41873":{"id":"41873","external_id":"","eventid":"5588","venueid":"0","exhibitorcategoryid":"0","name":"Niels
Vroman","shortname":"","booth":"","imageurl":"","mapid":"0","y1":"0","x1":"0","x2":"0","y2":"0","description":"Niels
uit Zulte.","tel":"0497841121","address":"Drogenboomstraat
54","email":"vroman.niels#hotmail.com","web":"http://nielsvroman.be","code":"","username":"","password":"","image1":"","imagedescription1":"","image2":"","imagedescription2":"","image3":"","imagedescription3":"","image4":"","imagedescription4":"","image5":"","imagedescription5":"","image6":"","imagedescription6":"","image7":"","imagedescription7":"","image8":"","imagedescription8":"","image9":"","imagedescription9":"","image10":"","imagedescription10":"","image11":"","imagedescription11":"","image12":"","imagedescription12":"","image13":"","imagedescription13":"","image14":"","imagedescription14":"","image15":"","imagedescription15":"","image16":"","imagedescription16":"","image17":"","imagedescription17":"","image18":"","imagedescription18":"","image19":"","imagedescription19":"","image20":"","imagedescription20":"","order":"0","brands":[],"categories":[],"linktodetails":true,"imagethumb":""},"41877":{"id":"41877","external_id":"","eventid":"5588","venueid":"0","exhibitorcategoryid":"0","name":"Ferdau
Daems","shortname":"","booth":"","imageurl":"","mapid":"0","y1":"0","x1":"0","x2":"0","y2":"0","description":"Ferdau
Daems","tel":"0497683697","address":"Waregem","email":"fer.dau#gmail.com","web":"http://ferdau.be","code":"","username":"","password":"","image1":"","imagedescription1":"","image2":"","imagedescription2":"","image3":"","imagedescription3":"","image4":"","imagedescription4":"","image5":"","imagedescription5":"","image6":"","imagedescription6":"","image7":"","imagedescription7":"","image8":"","imagedescription8":"","image9":"","imagedescription9":"","image10":"","imagedescription10":"","image11":"","imagedescription11":"","image12":"","imagedescription12":"","image13":"","imagedescription13":"","image14":"","imagedescription14":"","image15":"","imagedescription15":"","image16":"","imagedescription16":"","image17":"","imagedescription17":"","image18":"","imagedescription18":"","image19":"","imagedescription19":"","image20":"","imagedescription20":"","order":"0","brands":[],"categories":[],"linktodetails":true}}
Does anyone know how I can sort alphabetically on the name and make a header from the first letter?
Say you have an Array of objects, not an Object of objects, to enable indexing and sorting. Objects don't have order.
You retrieve it from localStorage. You parse it.
var people = JSON.parse(localStoarge.getItem("exhibitor");
// now you have an array of objects, each object representing a person.
// regardless of what structure you have now, change it to achieve this.
var comparePersons = function(a, b) {
// this function will compare two people objects.
return a.name.localeCompare(b.name);
// it's using String.prototype.localeCompare which returns 1 if a.name > b.name,
// 0 for equal and -1 for smaller. localeCompare is lexicographic comparison.
};
people.sort(comparePersons);
// now you have the people sorted alphabetically.
You can run through the people array, get the unique start letters, make an array of out them, and then display data as you want. It should be fairly simple.
var letters = '', groups = {};
for (var i = 0, len = people.length; i < len; i++) {
var letterKey = people[i].name.charAt(0).toLowerCase();// get the first letter
if (letters.indexOf(letterKey)) == -1) {
letters += letterKey;
groups[letterKey] = [people[i]];// index the people by unique letters.
} else {
groups[letterKey].push([people[i]]);// add to the existing list. Another Syntax fix
};
};
At this point you have an object like this:
a: [person1, person2, person5, etc..]//the people at A.
b: [person 3, person 4, etc..]// the people at B.
Just use the above data to create the display. Anything more and I would have to invoice you:).
The tricks here are Array.prototype.sort(here is more on it) and String.prototype.localeCompare(read more here).
I'm no Javascript expert and I'm having problems trying to glue together the various nuggets I find here and elsewhere regarding multi-dimensional arrays and sorting and wondered if someone could help me with a complete example?
I have managed to get to the point that I can populate a localStorage with data read in via Ajax.
The format of the rows is ...
(msgXXX) (Key1:Value1|Key2:Value2|Key3:Value3|...etc)
where
(msgXXX) is the localStorage key; and
(Key1:Value1|Key2:Value2|Key3:Value3|...etc) is the single concatenated localStorage data string
What I want to be able to do is convert all this to a multi-dimensional array to which I can apply various sorts. For example, one of the Keys is called "Timestamp" and the value is an integer representing seconds since the Unix epoch. I would like to sort all rows based on this Timestamp value (in descending order - ie latest first). Right now the dataset is just over 600 rows.
I'm comfortable I can do the extraction and slicing and dicing to get the data out of the localStorage, but I'm not even sure what I'm aiming for with regards to populating an array and then setting up the sort.
Can someone point me in the right direction?
You can go with something like this:
function create(line) {
var tokens = line.split("|");
var obj = {};
for (var i = 0; i < tokens.length; i++) {
tokens[i] = tokens[i].split(":");
obj[tokens[i][0]] = tokens[i][1];
}
return obj;
}
var arr = [];
for (....) { // iterate over the input that each line is of key/value format
arr.push(create(line));
}
function timestampSort(a, b) {
if (a == b)
return 0;
return a.timestamp < b.timestamp ? -1 : 1;
}
// to sort by timestamp
arr.sort(timestampSort);
This code creates an object per key/value line, in the format you gave. The object will have the keys as attributes. All of those objects are being pushed into an array, which is then being sorted by passing a compare function to the native sort method of array.
You can of course make as many compare functions as you want, each comparing by a different attribute/criteria.
You can read more about the sort method here: http://www.w3schools.com/jsref/jsref_sort.asp
EDIT
The sort method both changes the array itself and returns the array, so doing something like:
console.log(arr.sort(timestampSort));
Will both change the actual array and return it, and so the console.log will print it.
If you don't want to change the original array and have a copy of it that will get sorted you can:
var arr2 = arr.slice();
arr2.sort(timestampSort);
As for the keys in the array, what I wrote was intended to work only with this part of the line: Key1:Value1|Key2:Value2|Key3:Value3|...etc
So, to add support for the entire format, here's the modification:
function create(line) {
var parts = line.match(/^\(msg(\d+)\) \((.+)\)$/);
var tokens = parts[2].split("|");
var obj = { msgID: parts[1] };
for (var i = 0; i < tokens.length; i++) {
tokens[i] = tokens[i].split(":");
obj[tokens[i][0]] = tokens[i][1];
}
return obj;
}
If you apply this to the example you gave you'll get this:
arr is: [{
msgID: XXX,
Key1: Value1,
Key2: Value2,
Key3: Value3
}]
Hope this clears things for you.