sorting data into a tree - javascript

I have the following data:
var data = [
{ index : 1, sort : 10, parent : 0 },
{ index : 2, sort : 7, parent : 0 },
{ index : 3, sort : 15, parent : 1 },
{ index : 4, sort : 4, parent : 0 },
{ index : 5, sort : 13, parent : 1 },
{ index : 6, sort : 20, parent : 5 },
{ index : 7, sort : 2, parent : 8 },
{ index : 8, sort : 6, parent : 5 },
];
How do I efficiently sort this by both parent ID and the sort value so that I end up with:
var data = [
{ index : 4, sort : 4, parent : 0 },
{ index : 2, sort : 7, parent : 0 },
{ index : 1, sort : 10, parent : 0 },
{ index : 5, sort : 13, parent : 1 },
{ index : 8, sort : 6, parent : 5 },
{ index : 7, sort : 2, parent : 8 },
{ index : 6, sort : 20, parent : 5 },
{ index : 3, sort : 15, parent : 1 },
];
This is a tree structure. Each element is immediately followed by any children and all elements on the same branch are sorted by the sort value.
The best I can come up with is to first sort by parent and then do a second sort on each branch. This seems inefficient.
Edit: The example sort order was wrong. I've corrected it.
Edit for clarification: Each nested branch needs to appear immediately below the parent value, not at the end of the branch.
Edit: further corrections to data.

This is not your original approach, but you could build an actual tree from your data, like this:
function TreeNode(data) {
this.data = data;
this.parent = null;
this.children = [];
}
TreeNode.comparer = function (a, b) {
return a.data.sort < b.data.sort ? 0 : 1;
};
TreeNode.prototype.sortRecursive = function () {
this.children.sort(TreeNode.comparer);
for (var i=0, l=this.children.length; i<l; i++) {
this.children[i].sortRecursive();
}
return this;
};
function toTree(data) {
var nodeById = {}, i = 0, l = data.length, node;
nodeById[0] = new TreeNode(); // that's the root node
for (i=0; i<l; i++) { // make TreeNode objects for each item
nodeById[ data[i].index ] = new TreeNode(data[i]);
}
for (i=0; i<l; i++) { // link all TreeNode objects
node = nodeById[ data[i].index ];
node.parent = nodeById[node.data.parent];
node.parent.children.push(node);
}
return nodeById[0].sortRecursive();
}
With this set-up, you will get your nodes neatly nested with a simple call:
var tree = toTree(data);
TreeNode:0
parent -> null
data -> undefined
childen -> Array[
TreeNode:1
parent -> TreeNode:0
data -> { index : 4, sort : 4, parent : 0 }
childen -> Array[]
TreeNode:2
parent -> TreeNode:0
data -> { index : 2, sort : 7, parent : 0 }
childen -> Array[]
TreeNode:3
parent -> TreeNode:0
data -> { index : 1, sort : 10, parent : 0 }
childen -> Array[
TreeNode:4
parent -> TreeNode:3
data -> { index : 5, sort : 13, parent : 1 }
childen -> Array[
]
TreeNode:5
parent -> TreeNode:3
data -> { index : 3, sort : 15, parent : 1 }
childen -> Array[
... and so on ...
]
]
]
Once you have that tree object, you can do a number of things with it, including traversing it recursively in the expected order.
To do this, you could add a helper function that does depth-first traversal and executes a payload function f for every node:
TreeNode.prototype.walk = function(f, recursive) {
for (var i=0, l=this.children.length; i<l; i++) {
var child = this.children[i];
f.apply(child, Array.prototype.slice.call(arguments, 2));
if (recursive) child.walk.apply(child, arguments);
}
}
and call it like this:
tree.walk(function () { console.log(this.data) }, true);
which would produce:
{ index: 4, sort: 4, parent: 0}
{ index: 2, sort: 7, parent: 0}
{ index: 1, sort: 10, parent: 0}
{ index: 5, sort: 13, parent: 1}
{ index: 8, sort: 6, parent: 5}
{ index: 7, sort: 2, parent: 8}
{ index: 6, sort: 20, parent: 5}
{ index: 3, sort: 15, parent: 1}
Use more complex payload functions for other effects, like adding table rows in a table with jQuery or items to a <select> box.

Tomalak request above that I post my singleton version of their answer. Here it is:
/**
* Represents sorted results in a tree structure.
*/
Tree = (function() {
/**
*
* #type {Object} nodes Holds all the nodes in a flat format.
* #type {Object} nodes.data The data that is held in this node.
* #type {Object} nodes.parent Points to the parent object of this node.
* #type {Array} nodes.children An array of the child nodes of this node.
*/
var nodes = {};
/**
* #type {Object} root_node A Reference to the root node in nodes.
*/
var root_node;
/**
* A sort function to sort the nodes by the data.sort value in each node.
* #param {Number} a The first node to compare
* #param {Number} b The second node to compare
* #return {Boolean} Swap these nodes or not.
*/
var comparer = function (a, b) {
return a.data.sort < b.data.sort ? 0 : 1;
};
/**
* Sorts all the nodes so that they are in the correct order according to each nodes data.sort value.
* #param {Object} node A reference to the node in the nodes object.
*/
var sortRecursive = function (node) {
node.children.sort(comparer);
var len = node.children.length;
for (var i = 0 ; i < len ; i++) {
sortRecursive(node.children[i]);
}
};
/**
* Create a new node with the passed in data.
* #param {Object} data The data that is associated with this node.
*/
var create_node = function(data){
var node = {
data : data,
parent : null,
children : []
};
return node;
};
return {
/**
* Create a new tree of data
* #param {Array} data An array of data objects to transorm into a tree.
* #param {Array} data[].index The id of this node
* #param {Array} data[].parent The parent id of this node.
* #param {Number} root_id Id of the root node.
*/
create : function(data, root_id){
// Clear any previous data
nodes = {};
var i;
var len = data.length;
// Create an empty root node
nodes[root_id] = create_node({});
root_node = nodes[root_id];
// Make node objects for each data item
for (i=0; i<len; i++) {
if(typeof data[i].sort !== "undefined")
nodes[ data[i].index ] = create_node(data[i]);
}
// Link all TreeNode objects
for (i=0; i<len; i++) {
var node = nodes[data[i].index];
node.parent = nodes[node.data.parent];
node.parent.children.push(node);
}
sortRecursive(nodes[root_id]);
},
/**
* Walk through the nodes in nested and then sorted order, calling the passed in callback for each node.
* #param {Function} callback A callback function to call for each node.
* #param {Boolean} recursive Should the walkback be recursive, or just fetch the top level results.
* #param {Object|Undefined} node The node that is currently being walked.
* Ommit this value and the root node will be used.
*/
walk : function(callback, recursive, node) {
if(typeof node == "undefined")
node = root_node;
for (var i = 0, len = node.children.length; i < len; i++) {
var child = node.children[i];
callback.apply(child, Array.prototype.slice.call(arguments, 2));
if (recursive)
arguments.callee(callback, recursive, child);
}
}
};
})();
Populate the tree with:
Tree.create(unsorted_data, parent_id);
Fetch a sorted array with:
var sorted = [];
Tree.walk(function(){
sorted.push(this.data);
}, true);

After many attempts I've come up with this. It works, but is not very elegant. Could also do with abstracting into its own class.
// Initial sort places items in the correct sort order.
data.sort(function(a, b) {
return a.sort - b.sort;
});
vat top_parent_id = 1; // The value of an items parent if it is a top level item.
var sorted = []; // Empty array to copy sorted elements into.
var skipped = true; // flag to indicate if any elements have been skipped.
var skip_count = 0; // Counter to prevent infinite loops.
// Loop until no elements have been skipped.
//This loops through each level of the tree until all nested branches have been added
while(skipped){
skipped = false;
skip_count++;
if(skip_count === 50){ // Maximum tree depth.
throw "Error in sorted data or tree depth too great.";
break;
}
// Loop through the data in reverse order; this preserves the branch sort order.
for (var i = data.length - 1; i >= 0; i--) {
var item = data[i];
// Skip if this element has already been processed.
if(item.done)
continue;
// If this is this a top level item, then insert and continue.
if(item.parent == top_parent_id){
sorted.splice(0, 0, item); // Insert at the start; last item is the top sort.
item.done = true;
continue;
}
// Loop the new array to try and find this items parent.
var found = false;
for (var j = 0; j < sorted.length; j++) {
if(item.parent === sorted[j].index){
sorted.splice(j + 1, 0, item);
found = true;
item.done = true;
break;
}
}
// If a place for an item has not been found then skip it for now so it can be tested again on the next iteration.
if(!found){
skipped = true;
}
}
}
data = sorted;

Edit: ditch this, it doesn't work.
This is the best I've managed. Which should bubble sort it. I've not tested it yet. I'll leave the best answer open to see if anyone can improve on it.
data.sort(function(a, b) {
return a.parent - b.parent;
});
var changes = true;
while (changes){
changes = false;
for (var i = 1; i < data.length; i++) {
if(data[i].parent === data[i-1].parent && data[i].sort < data[i-1].sort){
var tmp = data[i-1];
data[i-1] = data[i];
data[i] = tmp;
changes = true;
}
}
}

Related

JavaScript - find unique values in array of objects, with count and create a new array of objects

I have an array of objects as below:
var arr =[
{
price:20,
rule:a
},
{
price:10,
rule:b
},
{
price:5,
rule:a
},
{
price:50,
rule:b
},
{
price:70,
rule:b
}
]
I want to extract an array of objects out of this as below:
var filteredArr = [
{
rule:a,
countOfRule:2,
minPriceForThisRule:5
},
{
rule:b,
countOfRule:3,
minPriceForThisRule:10
}
]
This means:
1) I want to create new array with no. of objects as unique no. of rules in first array "arr"
2) Need to count the unique rule repetition and add as property in new array objects - given as "countOfRule"
3) Find the minimum price for in a category of unique rule - given as "minPriceForThisRule"
I have read similar answers on SO and was able to get first 2 conditions only, and that too were not in the format as i need.
What I tried, referring to links on SO:
var ff = {},e;
for (var i = 0,l=arr.length; i < l; i++) {
e = arr[i];
ff[e.rule] = (ff[e.rule] || 0) + 1;
}
But this gives only a single object as
{
a : 2,
b: 3
}
You can do this with forEach and thisArg optional parameter.
var arr = [{"price":20,"rule":"a"},{"price":10,"rule":"b"},{"price":5,"rule":"a"},{"price":50,"rule":"b"},{"price":70,"rule":"b"}],
r = [];
arr.forEach(function(e) {
if(!this[e.rule]) {
this[e.rule] = {rule: e.rule, countOfRule: 0, minPriceForThisRule: e.price}
r.push(this[e.rule]);
}
this[e.rule].countOfRule++;
if(this[e.rule].minPriceForThisRule > e.price) this[e.rule].minPriceForThisRule = e.price;
}, {});
console.log(r)
I would use reduce. Something like:
var reduced = arr.reduce(function(memo, obj){
var rule = memo[obj.rule];
rule.countOfRule++;
if(!rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
} else{
if(obj.price < rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
}
}
return memo;
}, map);
where the initial map looks like:
var map = {
1: {
rule: 1,
countOfRule: 0,
minPriceForThisRule: null
},
2: {
rule: 2,
countOfRule: 0,
minPriceForThisRule: null
}
}
Of course you could dynamically create the map if needed.
https://plnkr.co/edit/wLw3tEx2SMXmYE7yOEHg?p=preview
This is how i would do this job,
var arr =[
{
price:20,
rule:"a"
},
{
price:10,
rule:"b"
},
{
price:5,
rule:"a"
},
{
price:50,
rule:"b"
},
{
price:70,
rule:"b"
}
],
reduced = arr.reduce((p,c) => { var fo = p.find(f => f.rule == c.rule);
fo ? (fo.countOfRule++,
fo.minPriceForThisRule > c.price && (fo.minPriceForThisRule = c.price))
: p.push({rule:c.rule, countOfRule:1, minPriceForThisRule: c.price});
return p;
},[]);
console.log(reduced);
Arrows might not work at IE or Safari. If that would be a problem please replace them with their conventional counterparts.

How can method getMetricByStatString (statString) be done on O(1)?

// CatalogAPI is a object that contains two methods for data retrieving
var CatalogAPI = {
// Returns a promise. When it resolves, it returns an array of arrays, containing category data.
getCategories: function () {
return new Promise(function (resolve) {
var categoriesData = [[1, 'device', 'http', 'HTTP'],
[2, 'device', 'dns', 'DNS'],
[3, 'application', 'ldap', 'LDAP'],
[4, 'application', 'mongo', 'MongoDB']];
resolve(categoriesData);
});
},
// Returns a promise. When it resolves, it returns an array of arrays, containing metric data.
getMetrics: function () {
return new Promise(function (resolve) {
var metricsData = [[1, 1, 'req', 'Requests'],
[2, 1, 'rsp', 'Responses'],
[3, 1, 'tprocess', 'Server Processing Time'],
[4, 2, 'req', 'Requests'],
[5, 2, 'rsp', 'Responses'],
[6, 2, 'trunc', 'Truncated Responses'],
[7, 3, 'plain', 'Plain Text Messages'],
[8, 3, 'sasl', 'SASL Messages'],
[9, 3, 'error', 'Errors'],
[10, 4, 'req', 'Requests'],
[11, 4, 'rsp', 'Responses'],
[12, 4, 'tprocess', 'Server Processing Time']];
resolve(metricsData);
});
}
};
/* custom constructor that creates a Category object
parameters: categoryData, an array containing category data in the following order:
[0] = ID, [1] = objectType, [2] = statName, [3] = displayName
*/
function Category(categoryData) {
this.ID = categoryData[0];
this.objectType = categoryData[1];
this.statName = categoryData[2];
this.displayName = categoryData[3];
};
/* custom constructor that creates a Metric object
parameters: metricData, an array containing metric data in the following order:
[0] = ID, [1] = categoryID, [2] = fieldName, [3] = displayName
*/
function Metric(metricData) {
this.ID = metricData[0];
this.categoryID = metricData[1];
this.fieldName = metricData[2];
this.displayName = metricData[3];
};
/* custom contructor that creates a Catalog object
attributes: initilized --> indicates if the object Catalog has been initialized already, set to false when first created
categories --> contains the categories data downloaded thru the CatalogAPI, set to empty when first created
metrics --> contains the metrics data downloaded thru the CatalogAPI, set to empty when first created
methods: getCategoryByID(ID)
getCategoryByStatString(statString)
getMetricByID(ID)
getMetricByStatString(statString)
initialize()
*/
function Catalog() {
this.initialized = false;
this.categories = [];
this.metrics = [];
/* searches the categories to check if a category matches a given ID,
if found, it returns a Category object with the found category's attributes
else, it returns a Category object with null attributes
*/
this.getCategoryByID = function (ID) {
var categoryObj = new Category([null, null, null, null]);
for (var i = 0; i < this.categories.length; i++) {
if (this.categories[i][0] === ID) {
categoryObj = new Category(this.categories[i]);
}
}
return categoryObj;
};
/* searches the categories to check if a category matches a given statString,
if found, it returns a Category object with the found category's attributes
else, it returns a Category object with null attributes
*/
this.getCategoryByStatString = function (statString) {
var categoryObj = new Category([null, null, null, null]);
var splittedStatString = statString.split(".");
for (var i = 0; i < this.categories.length; i++) {
if ((this.categories[i][1] === splittedStatString[1]) &&
(this.categories[i][2] === splittedStatString[2])) {
categoryObj = new Category(this.categories[i]);
}
}
return categoryObj;
};
/* searches the metrics to check if a metric matches a given ID,
if found, it returns a Metric object with the found metric's attributes
else, it returns a Metric object with null attributes
*/
this.getMetricByID = function (ID) {
var metricObj = new Metric([null, null, null, null]);
for (var i = 0; i < this.metrics.length; i++) {
if (this.metrics[i][0] === ID) {
metricObj = new Metric(this.metrics[i]);
}
}
return metricObj;
};
/* searches the metrics to check if a metric matches a given statString,
if found, it returns a Metric object with the found metric's attributes
else, it returns a Metric object with null attributes
*/
this.getMetricByStatString = function (statString) {
var metricObj = new Metric([null, null, null, null]);
// method getCategoryByStatString is called to identify the cateogry that the metric belongs to
var splittedStatString = statString.split(":");
var categoryObj = this.getCategoryByStatString(splittedStatString[0]);
// once the metric's cateogry is known, the metric is found by searching for categoryID and fieldName
for (var i = ((categoryObj.ID - 1) * 3) ; i < (categoryObj.ID * 3) ; i++) {
if (this.metrics[i][2] === splittedStatString[1]) {
metricObj = new Metric(this.metrics[i]);
}
}
return metricObj;
};
/* it returns a promise when the Catalog is ready to use,
if the catalog data was already downloaded, it does not downloads the data again
*/
this.initialize = function () {
// reference to catalog class, so that refering to catalog inside functions is easier
var catalogReference = this;
/* if initialized === true, then do not download catalog data,
just return promise acknowloging that the Catalog is ready to be use */
if (catalogReference.initialized) {
return new Promise(function (resolve) {
resolve();
});
}
/* if initialized === false, then download the catalog
data by calling the CatalogAPI methods */
else {
var categoriesData = CatalogAPI.getCategories();
var metricsData = CatalogAPI.getMetrics();
var dataReady = Promise.all([categoriesData, metricsData]).then(function (catalogData) {
catalogReference.categories = catalogData[0];
catalogReference.metrics = catalogData[1];
});
catalogReference.initialized = true;
return dataReady;
}
};
};
var c = new Catalog();
c.initialize().then(function () {
var category1, category2, metric1, metric2;
// Test category lookups
category1 = c.getCategoryByID(1);
category2 = c.getCategoryByStatString('extrahop.device.http');
if (category1.ID === category2.ID) {
console.log(category1.displayName + ' lookups match!');
}
// Test metric lookups
metric1 = c.getMetricByID(12);
metric2 = c.getMetricByStatString('extrahop.application.mongo:tprocess');
if (metric1.ID === metric2.ID) {
console.log(metric1.displayName + ' lookups match!');
}
});
The method getMetricBySatString(statString) has a parameter that is a string that contains 2 attributes from the categoriesData array and one from metricData, and it's supposed to be accomplished in the O(1), any ideas on how to accomplish that?
Thanks.

Are there such things as dynamic-dimensional arrays in JavaScript?

What I mean by dynamic-dimensional arrays is multidimensional arrays that can have various dimensions. I need to create a function that does something to elements of multidimensional arrays, regardless of their dimensions. I wrote a function that should loop through all elements of a multidimensional array, but I can't find a way to get them. Here's what I wrote:
function loopThrough (multiArray, dimensions) {
var i, indexes = new Array(dimensions.length);
// stores the current position in multiArray as index combination
for (i in indexes) indexes[i] = 0; // position is initialised with [0, 0, ... 0]
while (i >= 0) {
doStuff(multiArray[indexes[0], indexes[1], ... indexes[?]]); // this is where I got stuck
for (i = indexes.length - 1; i >= 0 && ++indexes[i] >= dimensions[i]; indexes[i--] = 0);
// creates the next index combination
}
}
I also need a way to create such arrays. Say in an object's constructor, like:
function MultiArray (dimensions) {
this.array = [];
// create multidimensional array
}
For example, if I want to create a 5x3x8 array I should be able to call MultiArray([5,3,8]); just the same as calling MultiArray([4,6]); for a 4x6 array, or MultiArray([7]); for a plain 7-lengthed array.
You can use something like this:
function MultiArray(dimensions) {
var a = [];
if (dimensions > 1) {
a.push(MultiArray(dimensions -1));
}
return a;
}
var m = MultiArray(4);
function MultiArray(dimensions) {
this.elements = [];
var leaf = dimensions.length == 1;
var dimension = dimensions.shift();
for (var i = 0; i < dimension; ++i) {
this.elements.push(leaf ? undefined : new MultiArray(dimensions));
}
}
MultiArray.prototype.get(indexes) {
var leaf = indexes.length == 1;
var index = indexes.shift();
return leaf ? this.elements[index] : this.elements[index].get(indexes);
}
MultiArray.prototype.set(indexes, value) {
var leaf = indexes.length == 1;
var index = indexes.shift();
if (leaf) {
this.elements[index] = value;
} else {
this.elements[index].set(indexes, value);
}
return this;
}
var m = new MultiArray([4, 3, 5]);
m.set([1, 2, 4], "i'm a value in a multi dimensional array");
m.get([1, 2, 4]); // should return "i'm a value in a multi dimensional array"
m.get([2, 0, 3]); // should return undefined
m.get([0, 1]); // should return an array of 5 elements

how to remove an item in a javascript array with json form

i have this json structure and I made it into an array. I was trying to remove the entry, but this code failed on me: Remove item from array if it exists in a 'disallowed words' array
var historyList = []; // assuming this already has the array filled
function add() {
var newHistory = {
ID: randomString(),
Name: $('#txtVaccineName').val(),
DoseDate: doseDate,
ResultDate: resultDate,
Notes: $('#txtResultNotes').val()
};
historyList.push(newHistory);
};
function removeEntry(value) {
historyList.remove('ID', value);
};
Array.prototype.remove = function(name, value) {
array = this;
var rest = $.grep(this, function(item) {
return (item[name] != value);
});
array.length = rest.length;
$.each(rest, function(n, obj) {
array[n] = obj;
});
};
You could use a property filter to match the item in your history list. Below is the sample quick code to do so, I've modified your history list a bit.
A quick test in FF, shows that it works!
var historyList = []; // assuming this already has the array filled
function addToHistory(name, notes) {
var newHistory = {
ID: new Date().getTime(),
Name: name,
DoseDate: "Somedate",
ResultDate: "SomeResultDate",
Notes: notes,
toString: function() {return this.Name;}
};
historyList.push(newHistory);
};
var Util = {};
/**
* Gets the index if first matching item in an array, whose
* property (specified by name) has the value specified by "value"
* #return index of first matching item or false otherwise
*/
Util.arrayIndexOf = function(arr, filter) {
for(var i = 0, len = arr.length; i < len; i++) {
if(filter(arr[i])) {
return i;
}
}
return -1;
};
/**
* Matches if the property specified by pName in the given item,
* has a value specified by pValue
* #return true if there is a match, false otherwise
*/
var propertyMatchFilter = function(pName, pValue) {
return function(item) {
if(item[pName] === pValue) {
return true;
}
return false;
}
}
/**
* Remove from history any item whose property pName has a value
* pValue
*/
var removeHistory = function(pName, pValue) {
var nameFilter = propertyMatchFilter(pName, pValue);
var idx = Util.arrayIndexOf(historyList, nameFilter);
if(idx != -1) {
historyList.splice(idx, 1);
}
};
// ---------------------- Tests -----------------------------------
addToHistory("history1", "Note of history1");
addToHistory("history2", "Note of history2");
addToHistory("history3", "Note of history3");
addToHistory("history4", "Note of history4");
alert(historyList); // history1,history2,history3,history4
removeHistory("Name", "history1");
alert(historyList); // history2,history3,history4
removeHistory("Notes", "Note of history3");
alert(historyList); // history2,history4
The entire Array.prototype.remove function can be inlined as follows:
function removeEntry(value) {
historyList = $.grep(historyList, function(item) {
return (item.ID !== value);
});
}
You can, of course, create a function to wrap $.grep or the predicate as you wish. (And if you do, you probably don't want to modify Array.prototype; prefer modifying historyList itself (using Array.prototype.splice) or creating a (likely static) function elsewhere.)
Also, this problem does not relate to the SO question you mention in your question, as far as I can tell.

Reordering arrays

Say, I have an array that looks like this:
var playlist = [
{artist:"Herbie Hancock", title:"Thrust"},
{artist:"Lalo Schifrin", title:"Shifting Gears"},
{artist:"Faze-O", title:"Riding High"}
];
How can I move an element to another position?
I want to move for example, {artist:"Lalo Schifrin", title:"Shifting Gears"} to the end.
I tried using splice, like this:
var tmp = playlist.splice(2,1);
playlist.splice(2,0,tmp);
But it doesn't work.
The syntax of Array.splice is:
yourArray.splice(index, howmany, element1, /*.....,*/ elementX);
Where:
index is the position in the array you want to start removing elements from
howmany is how many elements you want to remove from index
element1, ..., elementX are elements you want inserted from position index.
This means that splice() can be used to remove elements, add elements, or replace elements in an array, depending on the arguments you pass.
Note that it returns an array of the removed elements.
Something nice and generic would be:
Array.prototype.move = function (from, to) {
this.splice(to, 0, this.splice(from, 1)[0]);
};
Then just use:
var ar = [1,2,3,4,5];
ar.move(0,3);
alert(ar) // 2,3,4,1,5
Diagram:
If you know the indexes you could easily swap the elements, with a simple function like this:
function swapElement(array, indexA, indexB) {
var tmp = array[indexA];
array[indexA] = array[indexB];
array[indexB] = tmp;
}
swapElement(playlist, 1, 2);
// [{"artist":"Herbie Hancock","title":"Thrust"},
// {"artist":"Faze-O","title":"Riding High"},
// {"artist":"Lalo Schifrin","title":"Shifting Gears"}]
Array indexes are just properties of the array object, so you can swap its values.
With ES6 you can do something like this:
const swapPositions = (array, a ,b) => {
[array[a], array[b]] = [array[b], array[a]]
}
let array = [1,2,3,4,5];
swapPositions(array,0,1);
/// => [2, 1, 3, 4, 5]
Here is an immutable version for those who are interested:
function immutableMove(arr, from, to) {
return arr.reduce((prev, current, idx, self) => {
if (from === to) {
prev.push(current);
}
if (idx === from) {
return prev;
}
if (from < to) {
prev.push(current);
}
if (idx === to) {
prev.push(self[from]);
}
if (from > to) {
prev.push(current);
}
return prev;
}, []);
}
You could always use the sort method, if you don't know where the record is at present:
playlist.sort(function (a, b) {
return a.artist == "Lalo Schifrin"
? 1 // Move it down the list
: 0; // Keep it the same
});
Change 2 to 1 as the first parameter in the splice call when removing the element:
var tmp = playlist.splice(1, 1);
playlist.splice(2, 0, tmp[0]);
Immutable version, no side effects (doesn’t mutate original array):
const testArr = [1, 2, 3, 4, 5];
function move(from, to, arr) {
const newArr = [...arr];
const item = newArr.splice(from, 1)[0];
newArr.splice(to, 0, item);
return newArr;
}
console.log(move(3, 1, testArr));
// [1, 4, 2, 3, 5]
codepen: https://codepen.io/mliq/pen/KKNyJZr
EDIT: Please check out Andy's answer as his answer came first and this is solely an extension of his
I know this is an old question, but I think it's worth it to include Array.prototype.sort().
Here's an example from MDN along with the link
var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
return a - b;
});
console.log(numbers);
// [1, 2, 3, 4, 5]
Luckily it doesn't only work with numbers:
arr.sort([compareFunction])
compareFunction
Specifies a function that defines the sort order. If omitted, the array is sorted according to each character's Unicode code point value, according to the string conversion of each element.
I noticed that you're ordering them by first name:
let playlist = [
{artist:"Herbie Hancock", title:"Thrust"},
{artist:"Lalo Schifrin", title:"Shifting Gears"},
{artist:"Faze-O", title:"Riding High"}
];
// sort by name
playlist.sort((a, b) => {
if(a.artist < b.artist) { return -1; }
if(a.artist > b.artist) { return 1; }
// else names must be equal
return 0;
});
note that if you wanted to order them by last name you would have to either have a key for both first_name & last_name or do some regex magic, which I can't do XD
Hope that helps :)
Try this:
playlist = playlist.concat(playlist.splice(1, 1));
If you only ever want to move one item from an arbitrary position to the end of the array, this should work:
function toEnd(list, position) {
list.push(list.splice(position, 1));
return list;
}
If you want to move multiple items from some arbitrary position to the end, you can do:
function toEnd(list, from, count) {
list.push.apply(list, list.splice(from, count));
return list;
}
If you want to move multiple items from some arbitrary position to some arbitrary position, try:
function move(list, from, count, to) {
var args = [from > to ? to : to - count, 0];
args.push.apply(args, list.splice(from, count));
list.splice.apply(list, args);
return list;
}
Time complexity of all answers is O(n^2) because had used twice spice. But O(n/2) is possible.
Most Perfomance Solution:
Array with n elements,
x is to, y is from
should be n >x && n > y
time complexity should be |y - x|. So its is number of elements that is between from and to.
bestcase: O(1); //ex: from:4 to:5
average : O(n/2)
worthcase : O(n) //ex: from:0 to:n
function reOrder(from,to,arr) {
if(from == to || from < 0 || to < 0 ) { return arr};
var moveNumber = arr[from];
if(from < to) {
for(var i =from; i< to; i++){
arr[i] = arr[i+1]
}
}
else{
for(var i = from; i > to; i--){
arr[i] = arr[i-1];
}
}
arr[to] = moveNumber;
return arr;
}
var arr = [0,1,2,3,4,5,6,7,8,9,10,11,12,13];
console.log(reOrder(3,7,arr));
As a simple mutable solution you can call splice twice in a row:
playlist.splice(playlist.length - 1, 1, ...playlist.splice(INDEX_TO_MOVE, 1))
On the other hand, a simple inmutable solution could use slice since this method returns a copy of a section from the original array without changing it:
const copy = [...playlist.slice(0, INDEX_TO_MOVE - 1), ...playlist.slice(INDEX_TO_MOVE), ...playlist.slice(INDEX_TO_MOVE - 1, INDEX_TO_MOVE)]
I came here looking for a rearranging complete array, I want something like I did below, but found most of the answers for moving only one element from position A to position B.
Hope my answer will help someone here
function reArrangeArray(firstIndex=0,arr){
var a = [];
var b = []
for(let i = 0; i<= (arr.length-1); i++){
if(i<firstIndex){
a.push(arr[i])
}else{
b.push(arr[i])
}
}
return b.concat(a)
}
const arrayToRearrange = [{name: 'A'},{name: 'B'},{name: 'C'},{name: 'D'},{name: 'E'}];
reArrangeArray(2,arrayToRearrange)
// Output
// [
// { name: 'C' },
// { name: 'D' },
// { name: 'E' },
// { name: 'A' },
// { name: 'B' }
// ]
Reorder its work This Way
var tmpOrder = playlist[oldIndex];
playlist.splice(oldIndex, 1);
playlist.splice(newIndex, 0, tmpOrder);
I hope this will work

Categories

Resources