Complex array ordering - javascript

Suppose I have the following array:
var articles = [
{
id: 7989546,
tags: [
"98#6",
"107#6",
"558234#7"
]
},
//...
]
Each element of this array represents (partially) some kind of content in our website. It has an id and is tagged with people (#6) and/or topics (#7).
The user is going to be provided a cookie containing the suggested or recommended tags, like this:
var suggestions = [
"46#6",
"107#6",
"48793#7"
]
Consider these tags like suggestions that will be shown to the end user, like "Maybe you are interesed in reading..."
The suggestions array is already ordered by tag prioritiy. This means, that the first tag is more relevant to the user than the second tag.
Now, what I want to do is to order my articles array in the same way, that is, by tag priority.
No filters should be applied as the articles array is guaranteed to have elements that have at least one tag from the suggestions array.
If I have an article with tags: [ "98#6", "107#6", 558234#7" ] and another one with tags: [ "46#6", "36987#7" ], I want the latter to be first, because the tag 46#6 has more priority than 107#6 in the suggestions array.
How can I achieve this kind of ordering (using two arrays)?
Note: jQuery solutions are gladly accepted.

jsFiddle Demo
Just make your own sort function and then use .indexOf in order to check for tag existence. The issue that you are going to have to decide to handle on your own is what makes the most sense for collisions. If an article is tagged with a priority 1 tag, but another article is tagged with 3 lower priority tags, who gets precedence? There is some logic involved there and in my suggested solution I simply just take a total of the priority by using the length of suggestions and summing the priorities. This can be adapted to give a different type of collision detection if you wish, but the approach will be basically the same.
Step 1: Create the compare function
This is going to order the array descending base on the result from tagCount. Which is to say that if tagCount returns a value of 6 for right, and a value of 3 for left, then 6 is ordered first.
var compareFn = function(left,right){
return tagCount(right.tags) - tagCount(left.tags);
};
Step 2: Create the tagCount "algorithm" for determining priority
This simply gives precedence to the earliest occurring match, but will also give some weight to multiple later occurring matches. It does this by taking the matched index subtracted from the length of the match array (suggestions). So if there are 5 suggestions, and the first suggestion is matched, then that is going to end up being a value of 5 (length=5 - index=0).
var tagCount = function(tags){
var count = 0;
for(var i = 0; i < tags.length; i++){
var weight = suggestions.indexOf(tags[i]);
if(weight > -1)
count += tags.length - weight;
}
return count;
}
Stack Snippet
var articles = [
{
id: 7989546,
tags: [
"107#6",
"558234#7"
]
},
{
id: 756,
tags: [
"98#6",
"558234#7"
]
},
{
id: 79876,
tags: [
"98#6",
"107#6",
"558234#7"
]
},
{
id: 7984576,
tags: [
"98#6",
"107#6",
"46#6"
]
}
];
var suggestions = [
"46#6",
"107#6",
"48793#7"
];
var compareFn = function(left,right){
return tagCount(right.tags) - tagCount(left.tags);
};
var tagCount = function(tags){
var count = 0;
for(var i = 0; i < tags.length; i++){
var weight = suggestions.indexOf(tags[i]);
if(weight > -1)
count += tags.length - weight;
}
return count;
}
var a = articles.sort(compareFn);
console.log(a);
document.querySelector("#d").innerHTML = JSON.stringify(a);
<div id="d"></div>

My approach: Sort by sum of relevance score
Give you have:
var articles = [
{
id: 7989546,
tags: [
"98#6",
"107#6",
"558234#7"
]
},
{
id: 8000000,
tags: [
"107#6",
"107#10",
"558234#7",
"5555#1"
]
},
{
id: 8333000,
tags: [
"46#6",
"107#6",
"666234#7",
"107#6"
]
}
];
var suggestions = [
"46#6",
"107#6",
"48793#7"
];
And you want to sort articles by tags whereas tag ranks are defined in suggestions. One simple approach would be:
Step 1) For each article, get index of each tag exists in the suggestion. If it doesn't exist, discard.
Given suggestions ["a","b","c"]
Article tags ["a","b","zzzz","yyyy"]
Will be mapped to index [0,1] (last two tags are discarded because they do not exist in suggestion list)
Step 2) Calculate degree of relevance. Higher-ranked tag (smaller index) yields greater value (see function degreeOfRelevance() below).
Step 3) Sum the total degree of relevance and sort by this value. Thus, the article which contains higher ranked tags (based on suggestions) will yield higher total score.
Quick example:
article <100> with tags: [a,b,c]
article <200> with tags: [b,c]
article <300> with tags: [c,d,e,f]
Given suggestions: [a,b,c]
The articles will be mapped to scores:
article <100> index : [0,1] ===> sum score: 3+2 = 5
article <200> index : [1] ===> sum score: 2
article <300> index : [2] ===> sum score: 1
Therefore, the article <100> is ranked the most relevant document when sorted by score
And below is the working code for this approach:
function suggest(articles, suggestions){
function degreeOfRelavance(t){
return suggestions.length - suggestions.indexOf(t);
}
function weight(tags){
return (tags.map(degreeOfRelavance)).reduce(function(a,b){
return a+b
},0);
}
function relatedTags(a){
return a.tags.filter(function(t){
return suggestions.indexOf(t)>=0
});
}
articles.sort(function(a,b){
return weight(relatedTags(a)) < weight(relatedTags(b))
});
return articles;
}
// See the output
console.log(suggest(articles,suggestions));

Related

getting two towers updated at the same time every time I make a move Towers of Hanoi js

I am trying to write a very simple implementation of the Towers of Hanoi puzzle to practice what I have just learned about js constructors and prototypes. I am currently having problems with what I have written so far because every time I move a 'disc' from tower[0] to let's say tower[1], my tower[2] also gets updated with the same disc. Also, when I try to make an invalid move, I still get the disc I tried to move, taken away from its tower. I have checked out my logic and I can't see anything wrong with it (I could be just biased at this point too). I am wondering if it is a problem with my constructor function or any of my methods?
Here my code :
function TowersOfHanoi(numberOfTowers){
let towersQuant = numberOfTowers || 3 , towers;
towers = Array(towersQuant).fill([]);
towers[0] = Array(towersQuant).fill(towersQuant).map((discNumber, idx) => discNumber - idx);
this.towers = towers;
}
TowersOfHanoi.prototype.displayTowers = function(){
return this.towers;
}
TowersOfHanoi.prototype.moveDisc = function(fromTower,toTower){
let disc = this.towers[fromTower].pop();
if(this.isValidMove(disc,toTower)){
this.towers[toTower].push(disc);
return 'disc moved!'
} else {
return 'disc couldn\'t be moved.'
}
}
TowersOfHanoi.prototype.isValidMove = function(disc,toTower){
if(this.towers[toTower][toTower.length-1] > disc || this.towers[toTower].length === 0){
return true;
}else {
return false;
}
}
this is what I am testing :
let game2 = new TowersOfHanoi();
console.log(game2.displayTowers());
console.log(game2.moveDisc(0,1));
console.log(game2.displayTowers());
console.log(game2.moveDisc(0, 2));
console.log(game2.displayTowers());
and my output :
[ [ 3, 2, 1 ], [], [] ]
disc moved!
[ [ 3, 2 ], [ 1 ], [ 1 ] ]
disc couldn't be moved.
[ [ 3 ], [ 1 ],[ 1 ] ]
I appreciate any guidance. I am not necessarily looking for code. just to understand. Thanks
This quote from the Array fill() documentation tells you the problem:
When the fill method gets passed an object, it will copy the reference and fill the array with references to that object.
towers = Array(towersQuant).fill([]);
Arrays are objects. So what you've done is copy a reference of the first Array to each of the other arrays. The whole thing is compounded when you iterate these references to modify them.
Update
Something like this will work:
function TowersOfHanoi(numberOfTowers){
let towersQuant = numberOfTowers || 3 , towers = [];
for(let i=1; i < towersQuant; i++){
towers.push([]);
towers[0].push(i);
}
towers[0].reverse();
this.towers = towers;
}

Can Repeater Model execute JAVASCRIPT?

I have an XmlListModel in QML
XmlListModel {
id: model
source: "qrc:/Config/myConfig.xml"
query: "/levels/level"
XmlRole { name: "levName"; query: "#levName/string()" }
XmlRole { name: "from"; query: "from/number()" }
XmlRole { name: "to"; query: "to/number()" }
}
that reads this XML file
<levels parId = "3">
<level levelName = "level1">
<from>0</from>
<to>1</to
</level>
<level levelName = "level2">
<from>1</from>
<to>2</to>
</level>
</levels>
I also have a text element:
Text {
id: myText
x: 0; y:0
text: ""
}
I need to iterate through the XmlListModel in order to assign to myText.text the right level on the basis of what I found in myList.get(3).value, where myList is a ListModel.
Example:
if myList.get(3).value is between 0 (included) and 1 (excluded) I have to set myText.text = "level1", if it is between 1 (included) and 2 (excluded) I have to set myText.text = "level2", and so on...
Any suggestion?
Unfortunately you can't query your XmlListModel in O(1) like give me the value, where x is between role from and role to.
Good for you, you have an ordered list, so you can perform a binary search on your XmlListModel. The algorithm basically goes like this:
You first check whether your search value is by coincidence the one in the middle. If it is smaller, you search in the middle of the lower half, if it is larger, you search in the upper half... and so on.
With this you can find your value in O(log n) where n is the number of entries in your XmlListModel.
https://en.wikipedia.org/wiki/Binary_search_algorithm
If you have this implemented, to work on your model - either in JavaScript or in C++ or Python... you can have it like this:
Text {
text: binarySearch(model, myList.get(3).value).levName
}
When you implement this algorithm, make sure to deal with the gaps.

How to read this JSON with javascript & Get the value?

Here is my JSON code. I'm storing this json in an array.
total: {
limited: {
things: "451",
platforms: [
{
count: "358",
id: "Windows"
},
{
count: "44",
id: "X11"
},
{
count: "42",
id: "Macintosh"
},
{
count: "2",
id: "Linux"
},
{
count: "1",
id: "iPhone"
},
{
count: "1",
id: "iPod"
}
]
},
}
When i want to show the count of things in total > limited > things, I'm using the below code and it's working fine.
document.getElementById( "limited" ).value = arr.total.limited.things;
It's showing the 'things' value in 'limited' div area.
But I want to show the count of the particular id in platforms.
total > limited > platforms > id > windows.
How can i show the value of particular id from above json?
document.getElementById( "limited" ).value = arr.total.limited.platforms[0].count;
is showing the count but, the order of platforms always change, so we don't know where the windows is in the order exactly to use the above method.
How can we show the count of particular id from above json?
Also, how can we combine particular multiple id's count? for example, how to know all the count of Macintosh, iphone & ipod count combined?
Thanks.
You can filter the array to look for the Windows entry. Then, when you've got an array with only one element, access the count property of the the first element:
arr.total.limited.platforms.filter(
function(el) { return el.id == "Windows"; })[0].count
Getting the sum of counts for multiple platforms could be done like this by using the Array.map function:
// here, arr is the structure you describe in your question, and platf is an
// array of all desired platforms
function combinedCount(arr, platf) {
// for each element of the list of platforms in `arr`, we check
// if its id is inside the list of desired platforms,
// and return either its count or 0
var x = arr.total.limited.platforms.map(function(el) {
return (platf.indexOf(el.id) != -1) ? parseInt(el.count) : 0; });
// now x is an array of counts for the relevant platforms and
// 0 for all others, so we can just add its elements and return the sum
var count = 0;
for (var i = 0; i < x.length; i++) count += x[i];
return count;
}
You'd use it like this:
combinedCount(arr, ["Windows", "Linux"])
// returns 360
you can iterate over the elements of the platforms array, an look for the one you need.
for (var p in total.element.platform) {
if (total.element.platform[p].id == "iPhone") {
alert(total.element.platform[p].count)
}
}
If its possible you can structure your "platforms" as an object, that makes it possible to adress the platforms by key, this way you dont need to iterate over the entire array
A hacky solution to that could be to run arr.total.limited.things.platforms through a for loop and check the id tag to see if it's windows, if it is return value.
If you're using underscore.js ->
_.each(arr.total.limited.things.platforms, function(x) {
if (x.id === "Windows") {
document.getElementById( "limited" ).value = x.value;
}
});
or a good old fashioned for loop will work too! Using this method, you can change the if statement to check for the wanted iphone/etc. ids and then increment the value.
Another solution to this could be to change the way your JSON is stored. So instead of making platforms an array, you could make it an object:
platforms{
'windows: { id = '', value = 1 },
etc. and then just call by key!

Loop Through JSON, Insert Key/Value Between Objects?

UPDATE - Thanks for all the great answers and incredibly fast response. I've learned a great deal from the suggested solutions. I ultimately chose the answer I did because the outcome was exactly as I asked, and I was able to get it working in my application with minimal effort - including the search function. This site is an invaluable resource for developers.
Probably a simple task, but I can't seem to get this working nor find anything on Google. I am a Javascript novice and complex JSON confuses the hell out of me. What I am trying to do is make a PhoneGap Application (Phone Directory) for our company. I'll try to explain my reasoning and illustrate my attempts below.
I have JSON data of all of our employees in the following format:
[
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer"
},
{
"id":"2",
"firstname":"Mark",
"lastname":"Banana",
"jobtitle":"Artist"
},
... and so on
]
The mobile framework (Framework 7) that I am using offers a "Virtual List" solution which I need to take advantage of as our directory is fairly large. The virtual list requires you to know the exact height of each list item, however, you can use a function to set a dynamic height.
What I am trying to do is create "headers" for the alphabetical listing based on their last name. The JSON data would have to be restructured as such:
[
{
"title":"A"
},
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer"
},
{
"title":"B"
},
{
"id":"2",
"firstname":"Mark",
"lastname":"Banana",
"jobtitle":"Artist"
},
... and so on
]
I've been able to add key/value pairs to existing objects in the data using a for loop:
var letter, newLetter;
for(var i = 0; i < data.length; i++) {
newLetter = data[i].lastname.charAt(0);
if(letter != newLetter) {
letter = newLetter
data[i].title = letter;
}
}
This solution changes the JSON, thus outputting a title bar that is connected to the list item (the virtual list only accepts ONE <li></li> so the header bar is a div inside that bar):
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer",
"title":"A"
},
{
"id":"1",
"firstname":"Mike",
"lastname":"Apricot",
"jobtitle":"Engineer",
"title":""
}
This solution worked until I tried implementing a search function to the listing. When I search, it works as expected but looks broken as the header titles ("A", "B", etc...) are connected to the list items that start the particular alphabetical section. For this reason, I need to be able to separate the titles from the existing elements and use them for the dynamic height / exclude from search results.
The question: How can I do a for loop that inserts [prepends] a NEW object (title:letter) at the start of a new letter grouping? If there is a better way, please enlighten me. As I mentioned, I am a JS novice and I'd love to become more efficient programming web applications.
var items = [
{ "lastname":"Apple" },
{ "lastname":"Banana" },
{ "lastname":"Box" },
{ "lastname":"Bump" },
{ "lastname":"Can" },
{ "lastname":"Switch" }
];
var lastC = null; //holds current title
var updated = []; //where the updated array will live
for( var i=0;i<items.length;i++) {
var val = items[i]; //get current item
var firstLetter = val.lastname.substr(0,1); //grab first letter
if (firstLetter!==lastC) { //if current title does not match first letter than add new title
updated.push({title:firstLetter}); //push title
lastC = firstLetter; //update heading
}
updated.push(val); //push current index
}
console.log(updated);
Well right now you have an array of objects - prefixing the title as its own object may be a bit confusing - a better structure may be:
[
{
title: "A",
contacts: [
{
"id":"1",
"firstname":"John",
"lastname":"Apple",
"jobtitle":"Engineer",
"title":"A"
}
]
Given your current structure, you could loop and push:
var nameIndexMap = {};
var newContactStructure = [];
for (var i = 0; i < data.length; i++) {
var letter = data[i].lastname.charAt(0);
if (nameIndexMap.hasOwnProperty(letter)) {
//push to existing
newContactStructure[nameIndexMap[letter]].contacts.push(data[i])
} else {
//Create new
nameIndexMap[letter] = newContactStructure.length;
newContactStructure.push({
title: letter,
contacts: [
data[i]
]
});
}
}
newContactStructure will now contain your sorted data.
Demo: http://jsfiddle.net/7s50k104/
Simple for loop with Array.prototype.splice will do the trick:
for (var i = 0; i < data.length; i++) {
if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
data.splice(i, 0, {title: data[i].lastname[0]});
i++;
}
}
Demo. Check the demo below.
var data = [
{"lastname":"Apple"},
{"lastname":"Banana"},
{"lastname":"Bob"},
{"lastname":"Car"},
{"lastname":"Christ"},
{"lastname":"Dart"},
{"lastname":"Dog"}
];
for (var i = 0; i < data.length; i++) {
if (i == 0 || data[i-1].lastname[0] !== data[i].lastname[0]) {
data.splice(i, 0, {title: data[i].lastname[0]});
i++;
}
}
alert(JSON.stringify( data, null, 4 ));

Accessing an element in an array within an array in JavaScript

Pastebin of index.html: http://pastebin.com/g8WpX6Wn (this works but with some broken img links & no css).
Zip file if you want to see whole project:
I'm trying to dynamically change the contents of a div when I click an image. The image has it's respective id (the first index in the inner array) within the first inner array there's another array (index 3). I want to populate my div (id="articleLinks") with those links using JQuery when the image is clicked.
JavaScript & JQuery:
The tube array. *Note: the first index of each element in tubeArray is the ID & the news articles aren't linked to anything particular. Only interested in tubeArray[0] & tubeArray[4]
var tubeArray = [
['UQ', -27.495134, 153.013502, "http://www.youtube.com/embed/uZ2SWWDt8Wg",
[
["example.com", "Brisbane students protest university fee hikes"],
["example.com", "Angry protests over UQ student union election"],
]
],
['New York', 40.715520, -74.002036, "http://www.youtube.com/embed/JG0wmXyi-Mw",
[
["example.com" , "NY taxpayers’ risky Wall Street bet: Why the comptroller race matters"]
]
],
['To The Skies', 47.09399, 15.40548, "http://www.youtube.com/embed/tfEjTgUmeWw",
[
["example.com","Battle for Kobane intensifies as Islamic State uses car bombs, Syrian fighters execute captives"],
["example.com","Jihadists take heavy losses in battle for Syria's Kobane"]
]
],
['Fallujah', 33.101509, 44.047308, "http://www.youtube.com/embed/V2EOMzZsTrE",
[
["example.com","Video captures family cat saving California boy from dog attack"],
["example.com","Fines of £20,000 for dogs that chase the postman"]
]
]
];
A for loop which goes through each element in tubeArray then assigns id to the first index. Also an image that calls the function myFunctionId which takes the parameter this.id.
for (i = 0; i < tubeArray.length; i++) {
var id = tubeArray[i][0];
//other code
'<img src="img.png" onclick="myFunctionId(this.id);" id="' + id + '">' +
//other code
}
function myFunctionId (id) {
journal = id;
alert(journal) //just a test
//I want to search through tubeArray with the id and find the matching inner array.
//I then want to loop through the innerArray and append to my html a link using JQuery.
for (j = 0; i < innerArray.length; j++){
//supposed to get "www.linkX.com"
var $link = ;
//supposed to get "titleX"
var $title = ;
//change the content of <div id="articleLinks">
$('#articleLinks').append('<a href=$link>$title</a><br>');
}
}
HTML:
<div id="articleLinks">
Example Link<br>
</div>
Any help would be greatly appreciated. I've tried to simplify & cut out as much as I can so it's readable.
probably this might help: make yourself a map like
var tubeArray = [
[ // tubeArray[0]
'id', // tubeArray[0][0]
int, // tubeArray[0][1]
int, // tubeArray[0][2]
[ // tubeArray[0][3]
[ // tubeArray[0][3][0]
"www.link1.com", // tubeArray[0][3][0][0]
"title1" // tubeArray[0][3][0][1]
],
[ // tubeArray[0][3][1]
"www.link2.com", // tubeArray[0][3][1][0]
"title2" // tubeArray[0][3][1][1]
]
]
],
etc.
don't know whether this helps, but four dimensional arrays are brain-breaking ....
[edit]
... and thus go for a more OO like approach:
var tubeArray = [
'id' : { // tubeArray[id] or tubeArray.id
'a': int, // tubeArray.id.a
'b': int, // tubeArray.id.b
'entries': [ // tubeArray.id.entries
{ // tubeArray.id.entries[0]
'url': "www.link1.com", // tubeArray.id.entries[0].url
'title': "title1"
},
{ // tubeArray.id.entries[1]
'url': "www.link2.com", // tubeArray.id.entries[1].url
'title': "title2" ...
}
]
] ,
First you need to loop over tubeArray then go 4 deep and loop over the Array of Arrays at that level. Of course, you loop over those inner Arrays and get Elements 0 and 1.
$.each(tubeArray, function(z, o){
$.each(o[4], function(i, a){
$.each(a, function(n, v){
$('#articleLinks').append("<a href='"+v[0]+"'>"+v[1]+'</a>'); // use CSS to break lines
});
});
}

Categories

Resources