I have a function that tries to systematically add arrays to another multidimensional array. At each step of the way the arrays being added are calculated correctly, however, these calculations change the previously entered values. I've tried using slice but I'm clearly doing it wrong :(.
Please see code below - it is the return posMatrix that is being affected.
function allPossibilities(hand) {
var startingHandLength = hand.length;
var potHand = Array.prototype.slice.call(hand);
var scores = new Array();
var posMatrix = new Array();
var nextCard = 1;
var progressStage = true;
var finished = false;
var shallowArr = new Array();
do {
scores = calculateScores(potHand);
var maxScore = Math.max.apply(null, scores)
shallowArr = potHand.slice();
if (maxScore>16.5)
{posMatrix.push([shallowArr,maxScore])
console.log(posMatrix);
debugger;
if (potHand.length !== startingHandLength)
{
do{
if(potHand[potHand.length-1][1] < 10)
{
potHand[potHand.length-1][1]++;
progressStage = true;
}
else {potHand.pop();
potHand[potHand.length-1][1]++;}
}
while(progressStage === false)
}
}
else
{
potHand.push(["Imaginary",1,"Imaginary"]);
}
progressStage=false;
if(potHand.length === startingHandLength)
{finished = true;}
}
while(finished === false);
return posMatrix;
}
If the starting hand > 16.5, the function works as none of the other code gets to run. But otherwise it does not. The final return should be an array where each element is looks like this: [[array],number]. The number seems to come out fine, but since it is not an object it is not affected. I would expect the [array]s to be different from one another, currently they are all the same.
Slice returns a shallow copy of array, since you have multidimensional array so you need to deep clone of array
JSON.parse(JSON.stringify(array))
Or you can use loadash cloneDeep
You made a shallow copy of hand (which, BTW, you should've included). With statements like this
potHand[potHand.length-1][1]++;
you're accessing and modifying elements of hand, too.
Here, potHand[potHand.length-1] is an object, and it's en element of hand (not a copy - the same element).
What I did so far was the following:
function randomSelectObjects(randObjects, countShow){
var i = 0;
var countRandObjects = randObjects.length;
var preselectedObj = false;
randObjects.hide(); // hide all items
while (i < countShow) { // while until we found enough items we can show
preselectedObj = randObjects.eq(Math.floor(Math.random()*countRandObjects)); // random select an object
if(preselectedObj.is(':hidden')){ // make sure it is not already unhidden
preselectedObj.show(); // show the object
i++; // up the counter – done only in case it was not already visible
}
}
}
Usage:
var randObjects = $('.items');
randomSelectObjects(randObjects, 1);
The problem is that I will run into selecting an already revealed (show()) item inside while from time to time. I would love to remove that unnecessary overhead.
Unfortunately there seems to be no way to remove an object from a cached selection. remove() also removes the object from the DOM which is not (always) what I want.
Cloning the selection of objects first and then using remove() would work for the selection process but then there would be the overhead to match the selected items with the live DOM for actually show() them.
My suggestion is create a unique random array first. Hide all elements then loop through the array of random inidices and show matching elements
// wrap in simple jQuery plugin
$.fn.randomDisplay = function(max_items) {
max_items = max_items || 5;
//create array of unique random indices
var randArr = randArray(this.length, max_items);
// hide all then filter matches to show
this.hide().filter(function(i){
return randArr.indexOf(i) >-1
}).show();
// creates unique array
function randArray(max, len) {
var arr = [], rand;
for (var i = 0; i < len; i++) {
rand = getRand(max)
while (arr.indexOf(rand) > -1) {
rand = getRand(max)
}
arr.push(rand);
}
return arr;
}
// random number helper
function getRand(max) {
return Math.floor(Math.random() * max)
}
}
// use
$(function(){
$('.item').randomDisplay(7)
})
DEMO
Here's an interesting task that I faced today and I cannot think of any easy way to achieve the desired result.
Let's suppose we have a database with the following fields (columns): A,B,C,D,E,F,G but we don't know the names nor the count of the fields.
We receive a set of records from this database in the following format: {A:value1, B:value2, ...}.
If a value is not set for the current record the key will be missing too. This means I can receive {A:value} or {C:value1, D:value2} as valid records. The order of the keys will always stay the same. This means {D:value, C:value} is not a valid record.
I'm trying to recover the field names based on the returned records and keep the order of the keys.
For example I can receive records with the following keys:
A,C,D,E,F
D,F,G
A,B,F
From the example above I should be able to restore the original sequence which is A,B,C,D,E,F,G.
The first record gives us A,C,D,E,F.
The second one tells us that G is after F so now we have A,C,D,E,F,G
The third record gives us that B is after A so now we have A,B,C,D,E,F,G
If the order cannot be determined for sure we can use alphabetical order. Example for this is:
A,B
A,C
In the example above we cannot determine if the original order is A,B,C or A,C,B.
Any ideas how to implement this to work in the general case?
I will be implementing this algorithm using JavaScript but PHP, C++ or Java are also welcome.
EDIT: Do not think of the objects as standart JSON objects. In the real environment the structure is much more complex and the language is not pure JavaScript, but a modified version of ECMAScript. If it will be easier to understand - think only of the keys as an array of values ['A','B','C',...] and try to merge them, keeping the order.
EDIT 2: After struggling for some time and reading some ideas I came with the following solution:
Create an object that holds all relations - which column comes after which from each database record.
Create a relation between each a->b, b->c => a->c (inspired by Floyd–Warshall where each distance is considered as 1 if exists).
Create a sorting function (comparator) that will check if two elements can be compared. If not - alphabetical order will be used.
Get only the unique column names and sort them using the comparator function.
You can find the source-code attached below:
var allComparators = {};
var knownObjects = ['A,C,D,E,F','D,F,G','A,B,F'];
var allFields = knownObjects.join(',').split(',');
for (var i in knownObjects) {
var arr = knownObjects[i].split(',');
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
allComparators[arr[i]+'_'+arr[j]] = 1;
}
}
}
allFields = allFields.filter(function(value, index, self) {
return self.indexOf(value) === index;
});
for (var i in allFields) {
for (var j in allFields) {
for (var k in allFields) {
if (allComparators[allFields[i]+'_'+allFields[j]] && allComparators[allFields[j]+'_'+allFields[k]]) {
allComparators[allFields[i]+'_'+allFields[k]] = 1;
}
}
}
}
allFields.sort(function(a, b) {
if (typeof allComparators[a + '_' + b] != 'undefined') {
return -1;
}
if (typeof allComparators[b + '_' + a] != 'undefined') {
return 1;
}
return a > b;
});
console.log(allFields);
I give you the algorithm in a very direct and understandable way but the code! please try yourself and ask for help if required.
I express myself in two ways
In technical terms :
Generate a precedence graph (that is a directed graph)
Topological sort it
In more details :
Graph : Map(String, ArrayList< String >) = [Map(key,value)]
each key in the map corresponds to an element (A,B,C,...)
each value contains the elements that should place after the key,e.g for A it is {B,C,D,...}
How to fill the graph :
for each row:
for each element inside the row:
if the element is already as a key in the map
just add its immediate next item to the list*
else
add the element to the map and set the value to immediate next element of it**
* if the element is the last one in the row don't add anything to the map
** if the element is the last one in the row use {}, an empty list, as the value
Topological sort:
List sortedList;
for each key in the map:
if value.size() == 0 {
remove key from the map
add it the key to the sortedList
for each key' in the map:
if value'.contains(key)
value'.remove(key) (and update the map)
}
invert the sortedList
Test case :
the map for your first input will be:
{ A:{C,B} , C:{D} , D:{E,F} , E:{F} , F:{G} , G:{} , B:{F} }
Sort :
1 - G -> sortedList, map = { A:{C,B} , C:{D} , D:{E,F} , E:{F} , F:{} , B:{F} }
2 - F -> sortedList, map = { A:{C,B} , C:{D} , D:{E} , E:{} , B:{} }
3 - E -> sortedList, map = { A:{C,B} , C:{D} , D:{} }
4 - D -> sortedList, map = { A:{C,B} , C:{} }
5 - C -> sortedList, map = { A:{B} , B:{} }
6 - B -> sortedList, map = { A:{} }
6 - A -> sortedList, map = { }
sortedList = {G,F,E,D,C,B,A}
Invert - > {A,B,C,D,E,F,G}
do you think something like this would work?
var oMergedList = [];
function indexOfColumn(sColumnName)
{
for(var i = 0 ; i < oMergedList.length;i++)
if(oMergedList[i]==sColumnName)
return i;
return -1;
}
function getOrdinalIndex(sColumnName)
{
var i = 0;
for( ; i < oMergedList.length;i++)
if(oMergedList[i]>sColumnName)
break;
return i;
}
function merge(oPartial)
{
var nPreviousColumnPosition = -1;
for(var i = 0 ; i < oPartial.length;i++)
{
var sColumnName = oPartial[i] ;
var nColumnPosition = indexOfColumn(sColumnName);
if(nColumnPosition>=0)//already contained
{
if(nPreviousColumnPosition>=0 && nColumnPosition!=(nPreviousColumnPosition+1))//but inserted on wrong place
{
oMergedList.splice(nColumnPosition, 1);
nColumnPosition = nPreviousColumnPosition
oMergedList.splice(nColumnPosition, 0, sColumnName);
}
nPreviousColumnPosition = nColumnPosition;
}
else //new
{
if(nPreviousColumnPosition<0)//no reference column
{
nPreviousColumnPosition = getOrdinalIndex(sColumnName);
}
else// insert after previous column
nPreviousColumnPosition++;
oMergedList.splice(nPreviousColumnPosition, 0, sColumnName);
}
}
}
/* latest sample
merge(['A','C','E','G']);
merge(['A','D']);
merge(['C','D']);
*/
/* default sample
merge(['A','C','D','E','F']);
merge(['D','F','G']);
merge(['A','B','F']);
*/
/* fix order
merge(['A','B']);
merge(['A','C']);
merge(['A','B','C']);
*/
/* insert alphabetically
merge(['B']);
merge(['A']);
merge(['C']);
*/
document.body.innerHTML = oMergedList.join(',');
the only "undefined" parts are where to insert if you have no previous columns (I putted in firt position)
and second in the case A,B.. A,C the columns will be inserted when first seen
means A,B..A,C will give A,C,B .. and means A,C..A,B will give A,B,C
edited to use the current array position to fix
previous addition so if you add [A,C][A,B] you will get [A,C,B] but if you then pass [A,B,C]
the array will be fixed to reflect the new order
also when new columns appears and there is no reference column appends in alphabetical order
fixed the column correctioning par.. should now give you the correct result..
As described by JSON.org there is not such thing as a Json ordered keys:
An object is an unordered set of name/value pairs.
That being said, it becomes quite easy to merge objects as you don't need the order.
for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }
Source: How can I merge properties of two JavaScript objects dynamically?
the problem is I have a list with contacts and when someone change his/her status I try to move them to the top of the list. Everything worked till now, with IE9, and Firefox 4 is not working. I show you the code:
function sortByStatus()
{
var divs = getElementsByClassName(document,"status_sort");
divs.sort(compare);
for (var i = 0; i < divs.length; i++)
{
$("#contact_info").append(divs[i]);
}
}
function compare(div1, div2)
{
var id1 = div1.getAttribute("id");
var id2 = div2.getAttribute("id");
if (id1 > id2)
return 1;
else if (id1 < id2)
return -1;
else
return 0;
}
Any idea or possible fix? Thank you.
update
I have tried MrBuuBuu solution and it works patially, because now the sort by status works but the alphabetic sort is not working. I had to change part of MrBuuBuu solution, the compare function, because I compare the name of the contacts with a number just before the name that represent the status (ex. 2John , 2 means offline, and 1 online) so I have to compare with '<' and '>' and return 1, -1 or 0.
But what is worst, now it doesn't work with IE7 or IE8... the sort by status is not working.
Really weird, any idea?
document.getElementsByClassName returns a NodeList, not an array. So you have to convert it to an array first. I also cleaned up your compare() function.
function compare(div1, div2)
{
var id1 = div1.id;
var id2 = div2.id;
if (id1 < id2) {
return - 1;
}
if (id1 == id2) {
return 0;
}
return 1;
}
function sortByStatus()
{
var divs = document.getElementsByClassName("status_sort");
var divArray = $.map(divs, function(div) { return div; });
divArray.sort(compare);
$.each(divArray, function(i, div){
$("#contact_info").append(div);
});
}
If you're using the browser's native getElementsByClassName function, you may be ending up with a DOM node collection that is not a sortable Array.
When you say it's not working, are you getting any errors or is it just that the array doesn't get sorted? I'm assuming you're getting an error because sort in not defined.
One thing you could try is to clone the node collection to a plain JavaScript Array before sorting:
divs = [].slice.call(divs);
divs.sort(...
I don't have IE9 to test this, but with Chrome:
// undefined
document.getElementsByClassName("someclass").sort
But:
// the sort function
[].slice.call(document.getElementsByClassName("someclass")).sort
Are you sure it has been working? There's no such function as getElementsByClassName in the global scope.
Try using document.getElementsByClassName("status_sort") instead.
I am making a DOM builder which I have working succesfully but now I am trying to assign some shorthand functions so that div() -> e("div")
Here is my code:
//assign these objects to a namespace, defaults to window
(function(parent) {
/**
* Creates string of element
* #param tag The element to add
* #param options An object of attributes to add
* #param child ... n Child elements to nest
* #return HTML string to use with innerHTML
*/
var e = function(tag, options) {
var html = '<'+tag;
var children = Array.prototype.slice.apply(arguments, [(typeof options === "string") ? 1 : 2]);
if(options && typeof options !== "string") {
for(var option in options) {
html += ' '+option+'="'+options[option]+'"';
}
}
html += '>';
for(var child in children) {
html += children[child];
}
html += '</'+tag+'>';
return html;
}
//array of tags as shorthand for e(<tag>) THIS PART NOT WORKING
var tags = "div span strong cite em li ul ol table th tr td input form textarea".split(" "), i=0;
for(; i < tags.length; i++) {
(function(el) { //create closure to keep EL in scope
parent[el] = function() {
var args = Array.prototype.slice.call(arguments);
console.log(args);
args[0] = el; //make the first argument the shorthand tag
return e.apply(e,args);
};
})(tags[i]);
}
//assign e to parent
parent.e = e;
})(window);
What's currently happening is the args array is getting modified each time I call one of the shorthand functions and I assume what needs to happen is a closure somewhere so the args array I created does not get affected each time it is called. Here is the output of the unit tests:
div( div( span("Content")), span()) expected: <div><div><span>Content</span></div><span></span></div> result: <div><span></span></div>
div( div( span( e("b", e("b", e("b")))), span())) expected: <div><div><span><b><b><b></b></b></b></span><span></span></div></div> result: <div></div>
Though this doesn't directly answer your question,
for(var el in tags) {
is not entirely correct. tags is an array, not an object, so its properties cannot be enumerated using for (... in ...). Try
for(var el = 0; el < tags.length; el++) {
This can make a huge difference towards the interpreter's understanding of your code... and the correct execution of your algorithm.
Blonde moment, I was overwriting the first element when I meant to use args.unshift(el);
#MvanGeest - doing a for..in on an array is technically allowed. Arrays are still objects in javascript. The index of the array will be the key if iterated using a for..in loop. Obviously not the point of using an array in that case, but thought I would clarify.
#Anurag - the forEach method is not supported in IE8 (not sure about 9) so that might not be a reliable method to use until later on in the future.