473,779 Members | 1,873 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

Making Array.sort()'s comparator parametric


I am relatively new to JavaScript, though not to programming, and
I'm having trouble finding the idiomatic JS solution to the following
problem.

I have a table with (say) fields f1, f2, f3. I do a database
query and what I wind up with is a structure Q of arrays:
Q.f1[n] is field f1 in row n,
Q.f1.length == Q.f2.length == Q.f3.length is the record count of the query.
(This representation is of course backwards, but that's what ColdFusion's
CFWDDX facility produces, so I'm stuck with it.)

Now I have an array of row-numbers, which is supposed to represent a
subset of the query I just made:
A = [3, 6, 7] would represent rows 3, 6, 7 of query Q.

What I want to do is to sort A on a _specific_field _ of the query.
That is, produce a function compare(m, n) so that A.sort(compare)
sorts A in order corresponding to a specific field of Q.

Right now, I have separate handwritten comparators for each field:
function compare_f1(m, n) {
return Q.f1[m] < Q.f1[n] ? -1 :
Q.f1[m] > Q.f1[n] ? 1 : 0;
}
Obviously this is stupid.

(1)
I tried the approach that would be obvious in Lisp:
function comparator(fiel dname, invert) {
function compare(m, n) {
var comparison = Q[fieldname][m] < Q[fieldname][n] ? -1 :
Q[fieldname][m] > Q[fieldname][n] ? 1 : 0;
return invert ? -comparison : comparison;
}
return compare;
}

I expected this _not_ to work since I had thought JavaScript was
dynamically scoped. That is, I had thought something like

var c2 = comparator('f2' , false);
var fieldname = 'f3';
A.sort(c2);

would sort A using the wrong order, according to f3 (since 'compare'
isn't closed over the lexical environment containing 'fieldname').
But it seems to work correctly. Why does it work -- or does it work
only superficially?

(2)
Another approach I thought of, but which I was unable to get to work,
is to try making a 'functor' (function object) as in C++:

function compare(m, n) {
var fieldname = this.fieldname;
var invert = this.invert;
var comparison = Q[fieldname][m] < Q[fieldname][n] ? -1 :
Q[fieldname][m] > Q[fieldname][n] ? 1 : 0;
return invert ? -comparison : comparison;
}
function Comparator(fiel dname, invert) {
this.fieldname = fieldname;
this.invert = invert;
this.compare = compare;
}

Unfortunately if you then do

var c2 = new Comparator('f2' , false);
A.sort(c2.compa re);

the result is an error because the reference to 'this.fieldname '
inside the body of 'compare' does not treat 'this' as 'c2' as I
intended it to; 'this.fieldname ' is undefined.

An alternative strategy would be to try to make the Comparator
itself applicable, i.e., instead of
this.compare = compare;
put
Comparator.prot otype = compare;
and then try
A.sort(c2);

This doesn't work; c2 can't be applied.

So, does (1) work, and why or why not?
And is there a way to make something like (2) work?

Thanks for your time.
--
Chris Jeris cj****@oinvzer. net
Apply (1 6 2 4)(3 7) to domainname to reply.
Jul 20 '05 #1
7 4018
Christopher Jeris <cj****@fake.ad dress.us> writes:
I have a table with (say) fields f1, f2, f3. I do a database
query and what I wind up with is a structure Q of arrays:
Q.f1[n] is field f1 in row n,
Q.f1.length == Q.f2.length == Q.f3.length is the record count of the query.
(This representation is of course backwards,
Hear, hear!
but that's what ColdFusion's CFWDDX facility produces, so I'm stuck
with it.)
You have my sympathy :)
Now I have an array of row-numbers, which is supposed to represent a
subset of the query I just made:
A = [3, 6, 7] would represent rows 3, 6, 7 of query Q. What I want to do is to sort A on a _specific_field _ of the query.
I tried the approach that would be obvious in Lisp:
function comparator(fiel dname, invert) {
function compare(m, n) {
var comparison = Q[fieldname][m] < Q[fieldname][n] ? -1 :
Q[fieldname][m] > Q[fieldname][n] ? 1 : 0;
return invert ? -comparison : comparison;
}
return compare;
}
Perfect. If anything, I would assigne Q[fieldname][m/n] to local
variables so that you don't look it up twice. But that's just
performance, the concept is fine.
I expected this _not_ to work since I had thought JavaScript was
dynamically scoped.
But it isn't :). Javascript is statically/textually scoped, and
function expressions create closures.

Dynamic scope is one of the things that make me dislike Lisp. I'll
take Scheme over it any day.
That is, I had thought something like

var c2 = comparator('f2' , false);
var fieldname = 'f3';
A.sort(c2);

would sort A using the wrong order, according to f3 (since 'compare'
isn't closed over the lexical environment containing 'fieldname').
But it seems to work correctly. Why does it work -- or does it work
only superficially?
It works because Javascript is textually scoped.

What sometimes confuzes is the combination of scope and assignment:

var x = 42;
function foo() {return x;}
x = 37;
alert(foo());

This alerts 37. The foo function declaration does create a closure,
but it is the variable's *location* that is captured, not its current
value. Another example that often gives headaches is:

for (var i=0;i<10;i++) {
setTimeout(func tion(){alert(i) ;},1000);
}

This creates ten functions (or one function ten times, depending on
how the browser optimizes) and schedules them for later
execution. After approximatly one second, all the functions are called.
People often expect this to alert the numbers between 0 and 9, but
in fact it alerts 10 times 10. That is because the closure for the
functions are identical, they all refer to the same *variable*, which
holds the value 10 after the loop ends.

(2)
Another approach I thought of, but which I was unable to get to work,
is to try making a 'functor' (function object) as in C++:

function compare(m, n) { .... }
function Comparator(fiel dname, invert) {
this.fieldname = fieldname;
this.invert = invert;
this.compare = compare;
}

Unfortunately if you then do

var c2 = new Comparator('f2' , false);
A.sort(c2.compa re);

the result is an error because the reference to 'this.fieldname '
inside the body of 'compare' does not treat 'this' as 'c2' as I
intended it to; 'this.fieldname ' is undefined.
Yes. The argument you send to sort is the pure function. The function
itself doesn't know which object it belongs to as a method (because
really, it doesn't - the object just has a reference to the function,
and the *same* function can be a method of several different objects
at the same time ... as your one compare function is).
An alternative strategy would be to try to make the Comparator
itself applicable, i.e., instead of
this.compare = compare;
put
Comparator.prot otype = compare;
and then try
A.sort(c2);

This doesn't work; c2 can't be applied.
Nope. You are putting the function object as the prototype of your
constructor. However, you then create a new (non-function) object.
So, c2 is not a function, it just has one as a prototype.
So, does (1) work, and why or why not?
Yes, for all the right reasons.
And is there a way to make something like (2) work?
Try either:
---
function Comparator(fiel dname,invert) {
this.fieldname = fieldname;
this.invert = invert;
}
Comparator.prot otype.compare = function (m,n) {
var a = Q[this.fieldname][m];
var b = Q[this.fieldname][n];
var compare = (a<b?-1:b<a?1:0);
return this.invert?-compare:compare ;
}
function makeComparator( fieldname,inver t) {
var comparator = new Comparator(fiel dname,invert)
return function(m,n){r eturn comparator.comp are(m,n);}
}
---
.... i.e., make an object with a method, but store that
object in a closure, or
---
function makeComparator( fieldname,inver t) {
var comparator = function(m,n) {
var fieldname = arguments.calle e.fieldname;
var invert = arguments.calle e.invert;
var a = Q[fieldname][m];
var b = Q[fieldname][n];
return (!invert-invert)*((b<a)-(a<b)); // very short-hand :)
}
comparator.fiel dname = fieldname;
comparator.inve rt = invert;
return comparator;
}
---
.... i.e., store the values directly on the function object (functions are
objects) and access it as arguments.calle e (modern browsers only).

None of these are better than the pure closure based way.

Apply (1 6 2 4)(3 7) to domainname to reply.

You just don't want non-mathmaticians to reply, eh :)
/L
--
Lasse Reichstein Nielsen - lr*@hotpop.com
DHTML Death Colors: <URL:http://www.infimum.dk/HTML/rasterTriangleD OM.html>
'Faith without judgement merely degrades the spirit divine.'
Jul 20 '05 #2
"Christophe r Jeris" <cj****@fake.ad dress.us> wrote in message
news:ua******** ***@fake.addres s.us...
<snip>
(1)
I tried the approach that would be obvious in Lisp:
function comparator(fiel dname, invert) {
function compare(m, n) {
var comparison = Q[fieldname][m] < Q[fieldname][n] ? -1 :
Q[fieldname][m] > Q[fieldname][n] ? 1 : 0;
return invert ? -comparison : comparison;
}
return compare;
}
When the comparator function is called an "execution context" is entered
and an internal object referred to (in ECMA 262 Section 10.1.3) as the
"variable" object is created and initialised. With comparator
initialisation of the variable object involves creating a property with
the name of each of the formal parameters of the function, assigning the
parameter values to then and then creating a function object with the
inner function definition and assigning a reference to that function to
a property of the "variable" object called "compare". The effect of
returning a reference to the (unique to each invocation of comparator)
inner function from the function call is to preserve the variable object
from the execution context of the call to comparator. This is required
because inner functions can access the parameters and local variables
(effectively any property of the "variable" object) from their outer
function(s) whenever they are subsequently executed.
I expected this _not_ to work since I had thought JavaScript was
dynamically scoped. That is, I had thought something like

var c2 = comparator('f2' , false);
var fieldname = 'f3';
A.sort(c2);
When the comparator function is called an "execution context" is entered
and an internal object referred to (in ECMA 262 Section 10.1.3) as the
"variable" object is created and initialised. With comparator
initialisation of the variable object involves creating a property with
Identifier resolution in the execution context of a call to the inner
function returned by a call to comparator starts with a search of the
"variable" object in its execution context then, because it is an inner
function, the "variable" object from the execution context in which it
was created is searched. So the identifier "fieldname" is resolved as a
property of the "variable" object from the execution context in which
the inner function was created, which has whatever value was passed to
the comparator function as its "fieldname" parameter when it was called
in order to return the inner function.

The scope chin does not come into play at all in the resolution of that
identifier (that would have come next if no corresponding properties of
either variable object had been found) and whether - var fieldname =
'f3' - is an assignment to a local variable in another function or to a
global variable its value cannot be accessed by the inner function at
all.
would sort A using the wrong order, according to f3 (since 'compare'
isn't closed over the lexical environment containing 'fieldname').
But it seems to work correctly. Why does it work -- or does it work
only superficially?
It works, though it could be more efficiently implemented by passing a
reference to an object, say - Q.f2 - as a formal parameter -
fieldObject - and then the inner function could do the comparison on the
members of that object - fieldObject[m] < fieldObject[n] - without the
need to resolve which object member of - Q - to refer to on each (and
every) reference.
(2)
Another approach I thought of, but which I was unable to get to
work, is to try making a 'functor' (function object) as in C++:

function compare(m, n) {
var fieldname = this.fieldname;
var invert = this.invert;
var comparison = Q[fieldname][m] < Q[fieldname][n] ? -1 :
Q[fieldname][m] > Q[fieldname][n] ? 1 : 0;
return invert ? -comparison : comparison;
}
function Comparator(fiel dname, invert) {
this.fieldname = fieldname;
this.invert = invert;
this.compare = compare;
}

Unfortunatel y if you then do

var c2 = new Comparator('f2' , false);
A.sort(c2.compa re);
Although - c2.compare - is a property of an object it is a property that
holds a reference to a function object, and that is all that is passed
to the array.prototype .sort method. That function object has no
knowledge of whether, or which, objects hold references to it and when
the function is invoked by the Array.prototype .sort method the function
is just executed as a function (rather than as a method of c2). As a
result the - this - keyword is a reference to the global object and -
this.fieldname - etc do not refer to properties of c2 (It refers to an
undefined global variable).

<snip>So, does (1) work, and why or why not?
Yes, use it.
And is there a way to make something like (2) work?


Probably (you can emulate pretty much anything that can be done in any
other language with JavaScript) but (1) is the better option.

Though you might consider the one-time task of turning the strange
object structure you have into something more conducive to the way in
which you want to use it, rather than assuming that because that is the
form you get it in it is the form you must use.

Richard.
Jul 20 '05 #3
"Lasse Reichstein Nielsen" <lr*@hotpop.com > wrote in message
news:8y******** **@hotpop.com.. .
<snip>
---
function makeComparator( fieldname,inver t) {
var comparator = function(m,n) {
var fieldname = arguments.calle e.fieldname;
var invert = arguments.calle e.invert;
var a = Q[fieldname][m];
var b = Q[fieldname][n];
return (!invert-invert)*((b<a)-(a<b));// very short-hand :)
}
comparator.fiel dname = fieldname;
comparator.inve rt = invert;
return comparator;
}
---
... i.e., store the values directly on the function object
(functions are objects) and access it as arguments.calle e
(modern browsers only).

<snip>

In the above it should be possible to replace -
arguments.calle e.fieldname - with - comparator.fiel dname - as the outer
function local variable is a reference to the function object that is
also arguments.calle e, and that seems to work OK with, for example,
Netscape 4.

Richard.
Jul 20 '05 #4
"Richard Cornford" <Ri*****@litote s.demon.co.uk> wrote in message
news:br******** ***********@new s.demon.co.uk.. .
<snip>
A.sort(c2);
When the comparator function is called an "execution context" is
entered and an internal object referred to (in ECMA 262 Section
10.1.3) as the "variable" object is created and initialised. With
comparator initialisation of the variable object involves creating
a property with


Hmm. This preceding block of text is a repeat of the start of the
opening paragraph. I don't know how it found its way down here but it
should not be here. This paragraph was intended to start with:-
Identifier resolution in the execution context ...

<snip>

Richard.
Jul 20 '05 #5
Lasse Reichstein Nielsen <lr*@hotpop.com > writes:
But it isn't :). Javascript is statically/textually scoped, and
function expressions create closures.
Thanks very much for your informative response. I can't now
remember what behavior I had observed that made me think Javascript
was dynamically scoped! That's what I get for using "pure thought"
instead of actually reading the manual :)
Dynamic scope is one of the things that make me dislike Lisp. I'll
take Scheme over it any day.
You must be thinking of Emacs Lisp or some much older Lisps.
Modern Common Lisp has had lexical scope for two decades (although
unlike many languages, it provides dynamically scoped special
variables for the contexts where they're useful).
What sometimes confuzes is the combination of scope and assignment:

var x = 42;
function foo() {return x;}
x = 37;
alert(foo());
Well, this is the same in Scheme:
(let ((x 42))
(let ((foo (lambda () x)))
(set! x 37)
(foo)))
returns 37 as well.
for (var i=0;i<10;i++) {
setTimeout(func tion(){alert(i) ;},1000);
}
This took me a little longer to figure out, but I think it would
be the same in Scheme as well, _if_ you used a looping construct
which used the equivalent of set! to mutate the loop index variables
(which 'do' and the conventional Scheme tail-recursion loop do not).

[regarding function-object technique] None of these are better than the pure closure based way.


Indeed, if I really can have closures, there's no reason not to
use them.

--
Chris Jeris cj****@oinvzer. net
Apply (1 6 2 4)(3 7) to domainname to reply.
Jul 20 '05 #6
"Richard Cornford" <Ri*****@litote s.demon.co.uk> writes:
[very informative description of scope resolution]
Indeed, this looks pretty close to equivalent to Lisp closures, although
perhaps a little less uniform. I'll have to read that part of the
standard (I was going to say 'reread', but from my misconceptions about
the scope rules it's obvious I never read it in the first place :))
Though you might consider the one-time task of turning the strange
object structure you have into something more conducive to the way in
which you want to use it, rather than assuming that because that is the
form you get it in it is the form you must use.


Well, I hadn't gotten that far yet. The backwardness is not a disaster,
certainly much less bad than not having the closures I thought I didn't
have.

Thank you very much for your informative and helpful reply!

--
Chris Jeris cj****@oinvzer. net
Apply (1 6 2 4)(3 7) to domainname to reply.
Jul 20 '05 #7
> This took me a little longer to figure out, but I think it would
be the same in Scheme as well, _if_ you used a looping construct
which used the equivalent of set! to mutate the loop index variables
(which 'do' and the conventional Scheme tail-recursion loop do not).


The correspondence between JavaScript and Scheme is surprisingly close. See
http://www.crockford.com/javascript/little.html. One thing to watch for:
JavaScript implementations do not optimize tail recursion.

Jul 20 '05 #8

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
2015
by: Steven T. Hatton | last post by:
While thunbing through _C++ Templates, The Complete Guide_ (reckon I aught to read it?) I came across a discussion of using templates to "unroll" loops. I thought that looked like a good idea, so I decided to try it on some code I'm writing. The idea is to use template objects to perform what happens in traditional nested for() loops. for(int row = 0; row < num_rows; row++) { for(int col = 0; col < num_cols; col++) {
4
2546
by: John Bullock | last post by:
Hello, I am at wit's end with an array sorting problem. I have a simple table-sorting function which must, at times, sort on columns that include entries with nothing but a space (@nbsp;). I want all of the spaces to be put in the first slots of the array. IE 6 does this. But Firefox 0.9.1 doesn't, and I don't know why. I have not been able to reproduce it in very simple form (which is itself a puzzle). But example code is...
2
3615
by: Tobias Froehlich | last post by:
How can I sort an array so that the highest value has the index of 0 ? I mean if i have a double array like this: { 0.1, 0.5, 0.32, 0.9 } it should be transformed into a new array that looks like the following:: { 0.9, 0.5, 0.32, 0.1 }
21
3224
by: yeti349 | last post by:
Hi, I'm using the following code to retrieve data from an xml file and populate a javascript array. The data is then displayed in html table form. I would like to then be able to sort by each column. Once the array elements are split, what is the best way to sort them? Thank you. //populate data object with data from xml file. //Data is a comma delimited list of values var jsData = new Array(); jsData = {lib: "#field...
22
3186
by: sandy | last post by:
I am trying to make a simulated directory structure application for a course. I am using a Vector to store pointers to my Directory objects (as subdirectories of the current object). In my header it looks like this (private section) vector<Directory*Directories;
5
3206
by: tienlx | last post by:
Hi all, I want to sort an array of struct _line: .... typedef struct _line { int x1; int x2; } line;
11
656
by: Jeff Schwab | last post by:
Would std::sort ever compare an object with itself? I'm not talking about two distinct, equal-valued objects, but rather this == &that. The container being sorted is a std::vector. I've never seen this, but a coworker says he is. NB: I can't post sample code that reproduces the issue, nor do I claim any bug in the STL implementation (GCC 3.4.2). I'm just hoping a definitive answer resides in one of the brains who frequent this group.
1
2472
by: Markus Dehmann | last post by:
In the following code example, I define several Comparator classes which contain different compare functions to use with std::sort. I have a Sorter class that gets passed a Comparator and is supposed to sort using the specific Comparator that's passed to it. But it always uses the Comparator base class and not the derived class that it is supposed to use, although the functions are virtual. How can I make the code work? You might argue...
5
3187
by: Ganesh | last post by:
hi ! Can the predicate function used in std::sort take optional arguments ? For instance. I have a class Point. I create a vector of this and then want to compare the slopes of these points with respect to another point of the same class (Graham's convex hull algorithm anyone ?) ganesh
0
9632
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
9471
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it. First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
0
10302
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
1
10071
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
8958
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
7478
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
0
6723
by: conductexam | last post by:
I have .net C# application in which I am extracting data from word file and save it in database particularly. To store word all data as it is I am converting the whole word file firstly in HTML and then checking html paragraph one by one. At the time of converting from word file to html my equations which are in the word document file was convert into image. Globals.ThisAddIn.Application.ActiveDocument.Select();...
1
4036
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
2
3631
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.