diff --git a/Foundation/CPArray/CPMutableArray.j b/Foundation/CPArray/CPMutableArray.j index 420bf0873..308390429 100644 --- a/Foundation/CPArray/CPMutableArray.j +++ b/Foundation/CPArray/CPMutableArray.j @@ -334,7 +334,16 @@ - (void)sortUsingDescriptors:(CPArray)descriptors { - [self sortUsingFunction:compareObjectsUsingDescriptors context:descriptors]; + var i = [descriptors count], + jsDescriptors = []; + + // Revert the order of the descriptors + while (i--) + { + var d = [descriptors objectAtIndex:i]; + [jsDescriptors addObject:{ "k": [d key], "a": [d ascending], "s": [d selector]}]; + } + sortArrayUsingJSDescriptors(self, jsDescriptors); } /*! @@ -344,46 +353,7 @@ */ - (void)sortUsingFunction:(Function)aFunction context:(id)aContext { - var h, - i, - j, - k, - l, - m, - n = [self count], - o; - - var A, - B = []; - - for (h = 1; h < n; h += h) - { - for (m = n - 1 - h; m >= 0; m -= h + h) - { - l = m - h + 1; - if (l < 0) - l = 0; - - for (i = 0, j = l; j <= m; i++, j++) - B[i] = self[j]; - - for (i = 0, k = l; k < j && j <= m + h; k++) - { - A = self[j]; - o = aFunction(A, B[i], aContext); - if (o >= 0) - self[k] = B[i++]; - else - { - self[k] = A; - j++; - } - } - - while (k < j) - self[k++] = B[i++]; - } - } + sortArrayUsingFunction(self, aFunction, aContext); } /*! @@ -392,7 +362,7 @@ */ - (void)sortUsingSelector:(SEL)aSelector { - [self sortUsingFunction:selectorCompare context:aSelector]; + sortArrayUsingFunction(self, selectorCompare, aSelector); } @end @@ -408,20 +378,163 @@ @end -var selectorCompare = function selectorCompare(object1, object2, selector) +var selectorCompare = function(object1, object2, selector) { return [object1 performSelector:selector withObject:object2]; }; -// sort using sort descriptors -var compareObjectsUsingDescriptors= function compareObjectsUsingDescriptors(lhs, rhs, descriptors) +var sortArrayUsingFunction = function(array, aFunction, aContext) { - var result = CPOrderedSame, - i = 0, - n = [descriptors count]; + var h, + i, + j, + k, + l, + m, + n = array.length, + o; - while (i < n && result === CPOrderedSame) - result = [descriptors[i++] compareObject:lhs withObject:rhs]; + var A, + B = []; - return result; -}; + for (h = 1; h < n; h += h) + { + for (m = n - 1 - h; m >= 0; m -= h + h) + { + l = m - h + 1; + if (l < 0) + l = 0; + + for (i = 0, j = l; j <= m; i++, j++) + B[i] = array[j]; + + for (i = 0, k = l; k < j && j <= m + h; k++) + { + A = array[j]; + o = aFunction(A, B[i], aContext); + + if (o >= 0) + array[k] = B[i++]; + else + { + array[k] = A; + j++; + } + } + + while (k < j) + array[k++] = B[i++]; + } + } +} + +// Observe that the sort descriptors has the reversed order by the caller +var sortArrayUsingJSDescriptors = function(a, d) +{ + var h, + i, + j, + k, + l, + m, + n = a.length, + dl = d.length - 1, + o, + c = {}; + + var A, + B = [], + C1, + C2, + cn, + aUID, + bUID, + key, + dd; + + if (dl < 0) + return; + + for (h = 1; h < n; h += h) + { + for (m = n - 1 - h; m >= 0; m -= h + h) + { + l = m - h + 1; + + if (l < 0) + l = 0; + + for (i = 0, j = l; j <= m; i++, j++) + B[i] = a[j]; + + for (i = 0, k = l; k < j && j <= m + h; k++) + { + A = a[j]; + aUID = A._UID; + + if (!aUID) + aUID = [A UID]; + + C1 = c[aUID]; + + if (!C1) + { + C1 = {}; + cn = dl; + + do + { + key = d[cn].k; + C1[key] = [A valueForKey:key]; + } while (cn--) + + c[aUID] = C1; + } + + bUID = B[i]._UID; + + if (!bUID) + bUID = [B[i] UID]; + + C2 = c[bUID]; + + if (!C2) + { + C2 = {}; + cn = dl; + + do + { + key = d[cn].k; + C2[key] = [B[i] valueForKey:key]; + } while (cn--) + + c[bUID] = C2; + } + + cn = dl; + + do + { + dd = d[cn]; + key = dd.k; + o = objj_msgSend(C1[key], dd.s, C2[key]); + + if (o && !dd.a) + o = -o; + } while (cn-- && o == CPOrderedSame) + + if (o >= 0) + a[k] = B[i++]; + else + { + a[k] = A; + j++; + } + } + + while (k < j) + a[k++] = B[i++]; + } + } +} diff --git a/Tests/Foundation/CPArrayPerformanceTest.j b/Tests/Foundation/CPArrayPerformanceTest.j index 573e54dab..5c1f4235a 100644 --- a/Tests/Foundation/CPArrayPerformanceTest.j +++ b/Tests/Foundation/CPArrayPerformanceTest.j @@ -1,72 +1,136 @@ +var FILE = require("file"); + @import @import @import @import - var ELEMENTS = 100, REPEATS = 10; @implementation CPArrayPerformanceTest : OJTestCase - -- (void)testSortUsingDescriptorsSpeed { - var array = [self makeUnsorted]; - - var descriptors = [ - [CPSortDescriptor sortDescriptorWithKey:"a" ascending:NO], - [CPSortDescriptor sortDescriptorWithKey:"b" ascending:YES] - ]; - - var start = (new Date).getTime(); - - for (var i = 0; i < REPEATS; ++i) - { - var sorted = [array sortedArrayUsingDescriptors:descriptors]; - - [self checkSorted:sorted]; - } - - var end = (new Date).getTime(); - - CPLog.warn(_cmd + ": " + (end - start) + "ms"); + CPArray descriptors; } -- (void)testSortUsingNativeSort +- (void)setUp { - var array = [self makeUnsorted]; - var descriptors = [ - [CPSortDescriptor sortDescriptorWithKey:"a" ascending:NO], - [CPSortDescriptor sortDescriptorWithKey:"b" ascending:YES] - ]; + [CPSortDescriptor sortDescriptorWithKey:"a" ascending:NO], + [CPSortDescriptor sortDescriptorWithKey:"b" ascending:YES] + ]; +} - function sortFunction(lhs, rhs) - { - return ([lhs a] === [rhs a]) ? ([lhs b] - [rhs b]) : ([rhs a] - [lhs a]); - } +- (void)testAlmostSortedNumericUsingMergeSort +{ + CPLog.warn("\nNUMERIC ALMOST SORTED"); + var a = [self makeUnsorted], + sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors]; + [self checkAlmostSorted:sorted]; +} - var start = (new Date).getTime(); +- (void)testAlmostSortedNumericUsingNativeSort +{ + var a = [self makeUnsorted], + sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors]; + [self checkAlmostSorted:sorted]; +} + +- (void)testRandomNumericUsingMergeSort +{ + CPLog.warn("\nNUMERIC RANDOM"); + var a = [self makeRandomNumeric], + sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors]; + [self checkRandomSorted:sorted]; +} + +- (void)testRandomNumericUsingNativeSort +{ + var a = [self makeRandomNumeric], + sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors]; + [self checkRandomSorted:sorted]; +} + +- (void)testRandomTextUsingMergeSort +{ + CPLog.warn("\nTEXT RANDOM"); + var a = [self makeRandomText], + sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingDescriptors:) withObject:descriptors]; + [self checkRandomSorted:sorted]; +} + +- (void)testRandomTextUsingNativeSort +{ + var a = [self makeRandomText], + sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingDescriptors:) withObject:descriptors]; + [self checkRandomSorted:sorted]; +} + +- (void)testAlmostSortedNumericUsingMergeSelectorSort +{ + CPLog.warn("\nNUMERIC ALMOST SORTED (SELECTOR)"); + var a = [self makeUnsorted], + sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)]; + [self checkAlmostSorted:sorted]; +} + +- (void)testAlmostSortedNumericUsingNativeSelectorSort +{ + var a = [self makeUnsorted], + sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)]; + [self checkAlmostSorted:sorted]; +} + +- (void)testRandomNumericUsingMergeSelectorSort +{ + CPLog.warn("\nNUMERIC RANDOM (SELECTOR)"); + var a = [self makeRandomNumeric], + sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)]; + [self checkRandomSorted:sorted]; +} + +- (void)testRandomNumericUsingNativeSelectorSort +{ + var a = [self makeRandomNumeric], + sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)]; + [self checkRandomSorted:sorted]; +} + +- (void)testRandomTextUsingMergeSelectorSort +{ + CPLog.warn("\nTEXT RANDOM (SELECTOR)"); + var a = [self makeRandomText], + sorted = [self sort:a usingSortSelector:@selector(sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)]; + [self checkRandomSorted:sorted]; +} + +- (void)testRandomTextUsingNativeSelectorSort +{ + var a = [self makeRandomText], + sorted = [self sort:a usingSortSelector:@selector(_native_sortedArrayUsingSelector:) withObject:@selector(compareAAscendingThenBDescending:)]; + [self checkRandomSorted:sorted]; +} + +- (CPArray)sort:(CPArray)anArray usingSortSelector:(SEL)aSelector withObject:(id)anObject +{ + var sorted, + start = (new Date).getTime(); for (var i = 0; i < REPEATS; ++i) - { - var sorted = [array copy]; - - sorted.sort(sortFunction) - - [self checkSorted:sorted]; - } + sorted = [anArray performSelector:aSelector withObject:anObject]; var end = (new Date).getTime(); - CPLog.warn(_cmd+": " + (end - start) + "ms"); + CPLog.warn(aSelector + ": " + (end - start) + "ms"); + + return sorted; } - (CPArray)makeUnsorted { var array = []; - for (var i=0; i < ELEMENTS; ++i) + for (var i = 0; i < ELEMENTS; ++i) { var s = [Sortable new]; @@ -79,12 +143,49 @@ var ELEMENTS = 100, return array; } -- (void)checkSorted:(CPArray)sorted +- (CPArray)makeRandomNumeric +{ + var array = []; + + for (var i = 0; i < ELEMENTS; i++) + { + var s = [Sortable new], + n1 = ROUND(RAND() * ELEMENTS), + n2 = ROUND(RAND() * ELEMENTS); + + [s setA:n1]; + [s setB:n2]; + array.push(s); + } + + return array; +} + +- (CPArray)makeRandomText +{ + var the_big_sort = FILE.read(FILE.join(FILE.dirname(module.path), "the_big_sort.txt"), { charset:"UTF-8" }), + words = the_big_sort.split(" ", ELEMENTS), + wordcount = words.length, + array = []; + + for (var i = 0; i < wordcount - 1; i++) + { + var s = [Sortable new]; + + [s setA:words[i]]; + [s setB:words[i + 1]]; + array.push(s); + } + + return array; +} + +- (void)checkAlmostSorted:(CPArray)sorted { // Verify it really got sorted. - for (var j=0; j < ELEMENTS; ++j) + for (var j = 0; j < sorted.length; ++j) { - var expectedA = 4-FLOOR(j * 5 / ELEMENTS); + var expectedA = 4 - FLOOR(j * 5 / ELEMENTS); if (sorted[j].a != expectedA) [self fail:"a out of order: " + expectedA + " != " + sorted[j].a]; @@ -96,6 +197,66 @@ var ELEMENTS = 100, } } +- (void)checkRandomSorted:(CPArray)sorted +{ + // Verify that it really got sorted. + for (var j = 0; j < sorted.length - 1; ++j) + { + if ([sorted[j].a compare:sorted[j + 1].a] == CPOrderedAscending) + [self fail:"a out of order: " + sorted[j].a + " > " + sorted[j + 1].a]; + + if ([sorted[j].a compare:sorted[j + 1].a] == CPOrderedSame && [sorted[j].b compare: sorted[j + 1].b] == CPOrderedDescending) + [self fail:"b out of order: " + sorted[j].b + " < " + sorted[j + 1].b]; + } +} + +@end + +@implementation CPArray (NativeSort) + +- (CPArray)_native_sortedArrayUsingDescriptors:(CPArray)descriptors +{ + var sorted = [self copy]; + + [sorted _native_sortUsingDescriptors:descriptors]; + + return sorted; +} + +- (CPArray)_native_sortUsingDescriptors:(CPArray)descriptors +{ + var count = [descriptors count]; + + sort(function(lhs, rhs) + { + var i = 0, + result = CPOrderedSame; + + while (i < count) + if ((result = [descriptors[i++] compareObject:lhs withObject:rhs]) !== CPOrderedSame) + return result; + + return result; + }); +} + +- (CPArray)_native_sortedArrayUsingSelector:(SEL)aSelector +{ + var sorted = [self copy]; + + [sorted _native_sortUsingSelector:aSelector]; + + return sorted; +} + +- (CPArray)_native_sortUsingSelector:(SEL)aSelector +{ + sort(function(lhs, rhs) + { + return [lhs performSelector:aSelector withObject:rhs]; + }); +} + @end @implementation Sortable : CPObject @@ -104,4 +265,11 @@ var ELEMENTS = 100, int b @accessors; } +- (int)compareAAscendingThenBDescending:(Sortable)other +{ + var aResult = [a compare:other.a]; + + return aResult === CPOrderedSame ? [b compare:other.b] : -aResult; +} + @end diff --git a/Tests/Foundation/CPArrayTest.j b/Tests/Foundation/CPArrayTest.j index 75951c471..a99dfc931 100644 --- a/Tests/Foundation/CPArrayTest.j +++ b/Tests/Foundation/CPArrayTest.j @@ -539,6 +539,20 @@ [self assertTrue:d.indexOf("y: 6") !== -1 message:"Can't find 'y: 6' in description of array " + d]; } +- (void)testSortUsingDescriptorsWithDifferentSelectors +{ + var a = [CPDictionary dictionaryWithJSObject:{"a": "AB", "b": "ba"}], + b = [CPDictionary dictionaryWithJSObject:{"a": "aa", "b": "BB"}], + array = [a, b], + d1 = [[CPSortDescriptor sortDescriptorWithKey:@"a" ascending:YES selector:@selector(compare:)]], + d2 = [[CPSortDescriptor sortDescriptorWithKey:@"a" ascending:YES selector:@selector(caseInsensitiveCompare:)]], + s1 = [array sortedArrayUsingDescriptors:d1], + s2 = [array sortedArrayUsingDescriptors:d2]; + + [self assertTrue:s1[0] === a message:s1[0] + " is larger then " + a + " when sorting case sensitive"]; + [self assertTrue:s2[1] === a message:s2[1] + " is larger then " + a + " when sorting case insensitive"]; +} + @end @implementation AlwaysEqual : CPObject diff --git a/Tests/Foundation/the_big_sort.txt b/Tests/Foundation/the_big_sort.txt new file mode 100644 index 000000000..871ec5b6a --- /dev/null +++ b/Tests/Foundation/the_big_sort.txt @@ -0,0 +1 @@ +Introduction We have a neighborhood Internet listserve in South Austin that is often a source of good information about painters, plumbers, and lost animals. The e-discussion can also become a parody of liberal preciousness. One participant wrote to say that he planned to live-trap rats that had invaded his garage. The vermin-friendly homeowner wanted to know where it might be safe to relocate the rodents. The one Republican on the newsgroup, Stephen Mason, dared to say what most of us probably thought, volunteering that the man could release the varmints near Masons rat terrier, Hotard, who would happily relocate them to rat heaven. Our lone conservative correspondent, however, knew better than to make his comments overtly political. Mason, an intellectual property attorney and Texas A&M graduate, had tried to start a genuine political discussion on the list the year before. He wasnt about to try again. It was the spring of 2004, so things were already tense when Mason called the newsgroups attention to the election for the board of the local community college. Mason gave the names of both candidates, listed their websites, and then, after a warning that what followed was possibly inappropriate electioneering, recommended one of the candidates. The man Mason backed was deeply conservative, a member of the Federalist Society, a former officer of the Young Conservatives of Texas, and an opponent of gay marriage and adoption. Within the day, the newsgroup reacted in a way that wasnt as much ideological as biological. Mason wasnt just someone to be argued against. For the protection of the group, he needed to be isolated, sealed off, and expelled. First response: Okay, as a member of this list, Id really like to see this political discussion disappear. As a lesbian, obviously Im not going to vote for anybody who doesnt believe I shouldnt be allowed to adopt kids . . . As a resident of Travis Heights and a member of this neighborhood list, Im not interested in having this kind of discussion here. I have to defend myself against my government pretty much daily these days, and one place I dont want to have to do it is on this list. And then: A-men. Stephen, youre in the minority politically on this list and in this neighborhood, and while your opinions are your own to have, this list isnt the place for them . . . T-Hts is my home, and this list an extension of that . . . I hope we can all agree to prevent it from becoming a battleground. Mason responded: [The] ideological balkanization of America is dead-on true . . . Living here, especially as an out Republican, is a great deal of fun, and I learn a great deal from it. The most valuable thing that I learn daily is the capacity to respect people with whom I have disagreements. I hope not to be exiled to some place where the vast majority agrees with me. Then, alas, enough with the calls for reasoned discussion: Im really not interested [in] being surprised by right-wing e-mail in my in box, no matter what its guise. It makes me feel bad, and I dont like it. There were dissenting views. I really dont want political or other uncomfortable subjects segregated into some kind of opinion ghetto where only proselytizing activists and ideologues would venture, wrote one South Austinite. Another: Im going to stick my neck out here. I DO want to have political discussion . . . It is a tragedy that this country is becoming more and more polarized. Neighborhoods are becoming more and more homogeneous, either Democrat or Republican. Talk about segregation! But Mason got the picture: he was a Republican Crusoe on this Democratic island, and so he withdrew, promising never to talk politics again with his neighbors. Look, I know a lot about homogeneous political communities, Mason said one afternoon at a South Austin coffeehouse. Hed grown up in the Republican suburbs of Houston and attended the conservative hive of Texas A&M University. He was pugnacious about his politics, ending every e-mail message with a quote from Teddy Roosevelt: Aggressive fighting for the right is the greatest sport in the world. Mason was also engaged to marry a feminist filmmaker, an experience that taught him that nearly everything is political. But nothing had prepared him for the unsettling experience of being a political minority in the community where he lived — for being a minority in the age of political segregation. I ran off that listserve when the rhetoric became so shrill that I didnt have the taste for saying about anyone the things you would need to say to win that argument, Mason continued. Im not going to use that listserve for politics again, and there is some shame in that. The experience had changed the way he saw himself in the neighborhood. Knowing he was a minority, he wondered what people thought of him as he walked Hotard. In some way after that exchange, I think Im viewed with suspicion by my neighbors because of an act of political expression, which is a little on the bizarre side, he said. Im just a guy who has a dog and works a job. Chapter 3: The Psychology of the Tribe Washington was, from its beginning, a politically segregated city. In his forty-year-old study, The Washington Community, historian James Sterling Young mapped three Washingtons, one created for each of the three branches of government. The nine members of the U.S. Supreme Court lived in the same house until 1845. Executive branch workers gathered in one section of the city, near the White House, while congressmen were bunched together nearer the Capitol. Men whom the Constitution merely separated into different work groups separated themselves into different societies, wrote Young. Congressmen lived in boarding houses. They formed eating clubs around common tables, and they slept together, two to a room. Young tracked the membership of these new boarding house communities and found that the residential \ No newline at end of file