I am trying to sort through arrays of n-dimensional objects based on a certain index. For example if I have an array:
var array1 = [['c',24,"John"],['a',-13,"Cindy"],['b',98,"Alex"]];
I want to be able to sort by any dimension. Does anything exist in javascript that would give me outputs that look like the following?
array1.sort(sortFunction(0));
- [['a',-13,"Cindy"],['b',98,"Alex"],['c',24,"John"]]
array1.sort(sortFunction(1));
- [['a',-13,"Cindy"],['c',24,"John"],['b',98,"Alex"]]
array1.sort(sortFunction(2));
- [['b',98,"Alex"],['a',-13,"Cindy"],['c',24,"John"]]
Thanks!
To clarify: I am aware you must write a sort function, but can you write one that takes in an argument? Here would be an example:
function sortFunction(dim){
if (a[dim] === b[dim]) {
return 0;
}
else {
return (a[dim] < b[dim]) ? -1 : 1;
}
}
You can write a function generator that take the index of the parameters in your array :
var array = [['a',24,"John"],['a',-13,"Cindy"],['b',98,"Alex"]];
function sorting(index) {
return function(a, b) {
return a[index] < b[index]?-1:(a[index] > b[index])?1:0;
}
}
var res1 = array.sort(sorting(0));
var res2 = array.sort(sorting(1));
var res3 = array.sort(sorting(2));
Note: this is the basic approach of how the sort callback function works. For a more elegant solution see Paul Boute’s answer. His solution is especially useful when the function needs to be applied in more than one situation.
Your sort function needs to compare two properties of the elements (or the two elements) themselves. It needs to return a negative number if they are in the correct order, 0 if they are equivalent and a positive number if they are in the wrong order.
A JavaScript example of such a function:
function sortFunction(elementA,elementB,index){
if(elementA[index]<elementB[index]){
return -1;
}
else if(elementA[index]>elementB[index]){
return 1;
}
else{
return 0;
}
}
Invokable as such:
array1.sort(function(a,b){
return sortFunction(a,b,1);
});
Or the shorter all-in-one version:
array1.sort(function(a,b){
var index=1;
if(a[index]<b[index]){
return -1;
}
else if(a[index]>b[index]){
return 1;
}
else{
return 0;
}
});
You can also use a library like underscore.js. Then, simply
// which index to sort on
var s = 1;
// sort it - it will work for objects too
_.sortBy(array1, function(a){ return a[s];});
// or simply use "property name" to sort on
_.sortBy( array1, s);
Related
This is my assignment:
By now you should have worked with the length property of strings, e.g. "hello".length. Your task is to write a function called stringLength that accepts a string as a parameter and computes the length of that string; however, as you may have guessed, you are not allowed to use the length property of the string!
Instead, you'll need to make use of the string method called slice.
For our purposes, we can consider slice as taking one argument -- the index to begin slicing from, and returns a new string starting from that index onwards.
This is what I tried:
function stringLength(string){
var count = count++;
if(string.slice(0)){
return count}
return stringLength(string.slice(0,-1))
}
console.log(stringLength("game"))
I am trying to slice each character of the string back to start index, index 0, and then accumulate my count variable. I do not understand why my count variable is not accumulating.
An iterative proposal.
function stringLength(string) {
var count = 0;
while (string) {
string = string.slice(1);
count++;
}
return count;
}
console.log(stringLength("game"));
A recursive proposal.
function stringLength(string) {
return string ? 1 + stringLength(string.slice(1)) : 0;
}
console.log(stringLength("game"));
Hmm i tried to write code in the same format that you did.
function stringLength(str, count){
if(!str.slice(0)){
return count;
}
return stringLength(str.slice(0,-1), ++count)
}
console.log(stringLength("game", 0))
I'll point out the mistakes in your original code so that its easy to understand.
The recursion base case was incorrect. string.slice(0) will return
true if the string is non-empty, so use !string.slice(0)
The count value was not initialized and it wasn't being passed down
the recursion.
Your count variable is a separate variable for each function invocation, so it will always get the same value and not keep incrementing.
You could use this:
function stringLength(string){
return string ? 1 + stringLength(string.slice(0,-1)) : 0;
}
console.log(stringLength("game"))
A bit shorter would be to take out the first character instead of the last:
return string ? 1 + stringLength(string.slice(1)) : 0;
You really should try to figure it out yourself. Otherwise, are you really learning the subject?
function stringLength(string) {
if(!string) return 0;
var length = -1;
while(string.slice(length) !== string) --length;
return -length;
}
A variation taking into account your odd definition of slice():
function stringLength(string) {
var length = 0;
while(string.slice(length) !== "") ++length;
return length;
}
I guess you could try to use recursion like this:
function stringLength(string) {
if (string) {
return 1 + stringLength(string.slice(1))
} else return 0
}
function stringLength(string) {
var len = 0;
while (string) {
string = string.substring(1);
len++;
}
return len;
}
console.log(stringLength("boss"));
this works as well.
I'm following an online course about Javascript Functional Programming
at the Exercise 16 it show you how reduce is actually implemented, in order to help you understand how to use it, but into this implementation there is something i don't actually get, i'll show the code:
Array.prototype.reduce = function(combiner, initialValue) {
var counter, accumulatedValue;
// If the array is empty, do nothing
if (this.length === 0) {
return this;
}
else {
// If the user didn't pass an initial value, use the first item.
if (arguments.length === 1) {
counter = 1;
accumulatedValue = this[0];
}
else if (arguments.length >= 2) {
counter = 0;
accumulatedValue = initialValue;
}
else {
throw "Invalid arguments.";
}
// Loop through the array, feeding the current value and the result of
// the previous computation back into the combiner function until
// we've exhausted the entire array and are left with only one value.
while(counter < this.length) {
accumulatedValue = combiner(accumulatedValue, this[counter])
counter++;
}
return [accumulatedValue];
}
};
I don't understand the first if statement, when it check for this.length what this actually mean?
Take note this is different from the reduce in ES5, which returns an value instead of an Array, this is used just as a sample for the learning purpose.
Array.prototype.reduce = function(...
is saying, "create a function on the prototype of Array" - this means that the new reduce function will be callable on all arrays, eg:
[1, 2, 3].reduce(...
This means you can also call it on empty arrays, eg:
[].reduce(...
Building on the comment:
If the array is empty, do nothing
You're working on an array, and when the function is called, this is set to the array that reduce was called on. This implementation of reduce assumes that if that array is empty (ie this.length === 0), you can't logically reduce it any further - there's nothing to reduce, so you can return the same empty array.
As pointed out by #Alnitak in the comments, this implementation of reduce is flawed as compared to the specification. A different implementation is available on the MDN for polyfilling older browsers.
I am wondering, what is the best approach to write a recursive function with no direct base case (say: factorial), for instance, to count the number of elements in a nested array I have two approaches in mind, the first one below is preferred as it returns result directly:
the second one keeps the count in a variable attached to the function, works fine, but dealing with the result & resetting the variable is bizarre.
any pointers are appreciated.
You can simply return the value you are interested in:
function countElements(arr) {
var count = 0;
for (var i=0; i<arr.length; i++) {
if (arr[i] instanceof Array) {
count += countElements(arr[i]); // recursion here
} else {
count++; // normal element counts as 1
}
}
return count;
}
Demo: http://jsbin.com/ejEmOwEQ/1/edit
WARNING: The function might not end if the array contains self reference (var arr = []; arr.push(arr); countElements(arr);)
The correct way to write this is simply:
function countElements (obj) {
if (obj instanceof Array) {
var count = 0;
for (var i in obj)
count += countElements(obj[i]);
return count;
}
return 1
}
The terminating condition you're looking for is if not instanceof Array. Which in my code above is simply the fall through from the if instanceof Array block.
You do not need to keep a temp variable like count in recursive functions. You're still thinking iteratively (well, that for loop is iterative so you need a count variable there).
Recursive functions do everything by accepting arguments and returning results. No assignments are necessary. In fact, the code above can be written purely recursively without using a for loop and therefore without needing to use a count variable:
function countElements (obj) {
if (obj instanceof Array) {
if (obj.length) {
return countElements(obj.shift()) + countElements(obj);
}
return 0;
}
return 1;
}
There are 3 rules: if object is not an array we return 1, if object is an empty array we return 0 otherwise we count the first item in the array + the sum of the rest of the array.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Easiest way to find duplicate values in a JavaScript array
I am looking to find if two values are the same in an Array. I have written the following code:
function validatePassTimeFields(passtimes) {
var success = true;
var length = passtimes.length;
var hashMap = new Object();
for (var j=0; j<length; j++) {
if(hashMap[passtimes[j].value]==1) {
success = false;
alert("Duplicate Found");
break;
}
hashMap[passtimes[j].value]=1;
}
return success;
}
I am new to Javascript, so I tried using HashMap like to find if there is any duplicate. IS it the best way of finding a duplicate in JavaScript? or I can optimize it?
Your function is already very good, apart from the issue that it only works for arrays with strings or numbers. For a more difficile approach to care also about objects see this answer. I don't think that matters for you as you have an explicit and restricted use case (checking identity by the value property).
However, some points I'd do different:
Don't use the success variable and break from the loop, but just return from the whole function.
Instead of the constructor new Object usually the shortcut object literal {} is used
Instead of setting the values in the hashMap to 1 one might use true; you also could omit the equality operator == and just check for the truthiness of the property. I even would use the in operator.
function validatePassTimeFields(passtimes) {
var length = passtimes.length;
var hashMap = {};
for (var j=0; j<length; j++) {
if (passtimes[j].value in hashMap) {
alert("Duplicate Found");
return false;
}
hashMap[passtimes[j].value] = 1;
}
return true;
}
// You would only need to optimize it if you want to use it elsewhere-
function noduplicates(array){
var next, O= {},
L= array.length;
while(L){
next= array[--L];
if(O[next]) return false;
O[next]= 1;
}
return true;
}
function validatePassTimeFields(passtimes){
if (noduplicates(passtimes)) return true;
alert("Duplicate Found");
return false;
}
It might be worth checking out underscore's implementation of this functionality. If you are just looking to eliminate dupes, you can use _.uniq(), but if you are more interested in just knowing that there are dupes or the pure implementation details, you might enjoy checking out the source of this method, which is very nicely documented.
I know this isn't a direct code answer to the question - there are a few here already so it wouldn't be useful to repeat. But I thought it was worth mentioning as underscore is a great utility library and the source is a great place to learn more about well-written javascript.
It seems that you do not want to find the duplicates, only to see if there are any?
You're pretty close, here's a working function;
var hasDuplicates = function (arr) {
var _store = {};
for (var i = 0; i < arr.length; i++) {
if (typeof _store["_" + arr[i]] !== "undefined") {
return true;
}
_store["_" + arr[i]] = true;
}
return false;
};
The underscores in the associative array are necessary for storing numeric values. The hasDuplicates() function only works objects which have a toString() method.
To check for duplicates;
var yourArray = [1, 5, 7, 3, 5, 6];
if (hasDuplicates(yourArray)) {...
I'm sorting array:
myArray.sort(comparators.some_comparator);
and I have several comparator to choose from:
comparators = {
asc_firstname_comparator : function(o1, o2){
...
}
desc_firstname_comparator : function(o1, o2){
...
}
etc...
}
I want to write function which returns certain comparator depending on input data. It should figure out comparator from string inputs, something like this:
function chooseComparator(field, order){
return "comparators."+order+"_"+field+"_comparator";
}
So is it possible to pass only function name string to sort() method or I'll need to pass reference to correct comparator somehow?
use the subscript notation for indexing the javascript object (obj.prop is the same as obj["prop"], but the latter way you can create property names dynamically):
function chooseComparator(field, order){
return comparators[order+"_"+field+"_comparator"];
}
and yes, you have to pass a function object to the sort() function, just a name is not enough
Actually you can create a closure instead of writing dozens of functions. Assuming asc_firstname_comparator means "sort by x.firstname",
function compareByProperty(field, order) {
return function (o1, o2) {
var retval;
if (o1[field] > o2[field])
retval = 1;
else if (o1[field] < o2[field])
retval = -1;
else
retval = 0;
if (order === "desc")
retval = -retval;
return retval;
}
}
...
myArray.sort(compareByProperty("firstname", "asc"));
I'd do something like this.
var comparators = {
asc_firstname_comparator : function(o1, o2){ ... }
desc_firstname_comparator : function(o1, o2){ ... }
};
Array.prototype.customSort(comparatorName) {
this.sort(comparators[comparatorName]);
}
var myArray = [ ... ]; // define array
myArray.customSort("asc_firstname_comparator");