5

Background:

As needed in some task, I need a simple sort function. For simplicity, I wrote another function to wrap the built-in sort function as:

function sortBy(obj, extra, func){
    if(typeof func == 'function'){
        f = func;
    } else if(typeof extra != 'function'){
        eval('function f(a, b, ai, bi, e){return ' + func + '}');
    } else {
        var f = extra;
        extra = null;
    }

    var res = [];
    for(var i in obj){
        if(obj.hasOwnProperty(i)){
            obj[i]._k_ = i;
            res.push(obj[i]);
        }
    }

    res.sort(function(a, b){
        if(f(a, b, a._k_, b._k_, extra)){
            return 1;
        } else {
            return -1;
        }
    })

    return res;
}

My attempts are:

  1. Make it possible to sort a object directly
  2. Keep the original object as the hash table
  3. Allow some simple syntax

For instance,

var data ={
    12: {age:27, name:'pop', role: 'Programmer'},
    32: {age:25, name:'james', role: 'Accontant'},
    123:{age:19, name:'jerry', role:'Sales Representative'},
    15:{age:22, name:'jerry', role:'Coder'},
    17:{age:19, name:'jerry', role:'Tester'},
    43:{age:14, name:'anna', role: 'Manager'},
    55: {age:31, name:'luke', role:'Analyst'}
};

There are several usages:

var b = sortBy(data, '', 'a.age < b.age'); // a simple sort, order by age
var b = sortBy(data, 19, 'b.age == e');    // pick up all records of age 19, and put them in the beginning
var b = sortBy(data, function(a, b){return a.name > b.name});  // anonymous sort function is also allowed

QUESTION

Though it works as expected in our code, I would like to raise some question:

  1. Is there any potiential problem about using eval to create sort function from string?
  2. Is there any story about sort function returning -1(nagative), 0 and 1(positive)? Can we change the code as "return if(f(a, b, a.k, b.k, extra)", instead of returning 1 or -1? We found it works in our firefox and chrome, but not sure whether it is safe to do so.
3
  • 2
    Why aren't you using the sort function that already exists? Commented Jun 22, 2011 at 14:43
  • @dave he is: " I wrote another function to wrap the built-in sort function as..." Commented Jun 22, 2011 at 14:45
  • If I'm not remember wrong sort function is only for Array object, right? Commented Jun 22, 2011 at 14:47

4 Answers 4

4

1. Is there any potiential problem about using eval to create sort function from string?

Not per se, but it does suffer all the same deficiencies as calling other eval-style functions with strings, e.g. setTimeout() or the Function() constructor. As long as you trust the source of the string there's no real problem. However, I would consider using the Function constructor instead:

f = new Function(a, b, ai, bi, e, 'return ' + func);

It's more manageable and it's definitely more appropriate than evaluating a function declaration.

2. Is there any story about sort function returning -1(nagative), 0 and 1(positive)?

Not really understanding this part of your question, but your function doesn't appear to tackle what to do if two items are the same from the comparison. You should be returning less than 0, 0 or more than 0 depending on the result. The best approach for this is to use String.prototype.localeCompare():

return String.prototype.localeCompare.call(a, b);
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the first answer. That's a nice suggestion
0

Try making it so you only do eval on a small part:

(fiddle: http://jsfiddle.net/maniator/SpJbN/)

function sortBy(obj, extra, func){
    var f = null;
    if(typeof func == 'function'){
        f = func;
    } else if(typeof extra != 'function'){
        f = function(a, b, ai, bi, e){
            return eval(func); // <-- smaller part
        }
    } else {
        f = extra;
        extra = null;
    }

    var res = [];
    for(var i in obj){
        if(obj.hasOwnProperty(i)){
            obj[i]._k_ = i;
            res.push(obj[i]);
        }
    }

    res.sort(function(a, b){
        if(f(a, b, a._k_, b._k_, extra)){
            return 1;
        } else {
            return -1;
        }
    })

    return res;
}

1 Comment

Technically, that's slower. In the original, eval runs only once before the sort. In your version, you're calling the compiler every time the sort function is called. f = new Function(...) would be more appropriate in either case.
0
  1. Thanks to Andy, we change the code to

    var f = new Function('a, b, ai, bi, e', 'return ' + func);

Note that the arguments should be passed in as string, check out : https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function

  1. About the second question, I think it is because we were trying to make the sort function more explicitly. For instance,

    sort([1, 2, 3, 5, 1], '', 'a < b') ==> [1, 1, 2, 3, 5]

Literal meaning of 'a < b' to us is "the latter item is greater than the former item", so the array is sorted to [1, 1, 2, 3, 5].

Another example, 'a.age < b.age' will return the records in the order that younger person comes before olders.

That's the reason I am asking can we use true or false instead of -1, 0, 1.

We keep doing some more small tests, and figure out something, would like to share with everyone.

For example:

var b = [
    {age:27, name:'pop 2', role: 'Programmer'},
    {age:19, name:'james', role: 'Accontant'},
    {age:19, name:'jerry', role:'Sales Representative'},
    {age:22, name:'jerry', role:'Coder'},
    {age:19, name:'jerry', role:'Tester'},
    {age:14, name:'anna', role: 'Manager'},
    {age:19, name:'luke', role:'Analyst'},
    {age:27, name:'pop', role: 'Programmer'},
    {age:14, name:'anna 2', role: 'Manager'}
];

b.sort(function(a, b) {return a.age - b.age > 0? 1: -1}); // #1
b.sort(function(a, b) {return a.age > b.age});  // #2
b.sort(function(a, b) {return a.age - b.age});  // #3

Although the above sorts return the same result, try this one :

b.sort(function(a, b) {return a.age - b.age < 0? -1: 1});  // #4

In this statement, the records are still ordered by age, but the order is reversed within a same age group.

#1 and #2 is same as #3 by chance. If the browser use different algorithm to implement the sort function, it is possible that #1 and #2 will behave like #4. If you are strict with the order of the result, you need to explicitly return 0, when 'a' equals to item 'b'.

Besides, as Andy pointed out, in certain cases(e.g., #4), it is possible that unnecessary swaps are done if we don't return 0 explicitly, which could affect the performance.

The reason we didn't notice this before is because we didn't care the order within a group, providing the record is sorted on certain property.

2 Comments

The point I should have made in my answer is performance. If an item doesn't need moving, 0 should be returned because there's less to do. Take, for instance an array that is already half-sorted [1,1,1,5,3,4]. Even though the first three items are already in place you're telling the sort implementation to move them anyway, thus requiring more work to be done.
And I think that depends on how the browser implements the sort function. For instance, in the statement #1 and #2, I don't think the performance differ from #3(the standard one), as the sorted items remain in their original order, is it? Of course in #4, it takes extra to do.
-1

I think it would be appropriate to pass a function that does the comparing. so something like

sort(somedata, function(a, b) {
    return a < b;
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.