Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Update(orderBy): Guaranteed stable sort #15914

Conversation

BobChao87
Copy link
Contributor

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

Feature

What is the current behavior? (You can also link to an open issue here)

#14881

What is the new behavior (if this is a feature change)?

Falls back on built-in comparison function if user-provided comparison function fails to break a tie between elements being ordered.

Does this PR introduce a breaking change?

Yes

Please check if the PR fulfills these requirements

Other information:

BREAKING CHANGE: Using orderBy with a custom comparator function while
simultaneously setting reverse to true results in a potentially different
list order than previously.

Specifically, when the custom comparator fails to sort two objects (compares
them as being "equal"), the objects would previously have remained in their
original order in the array relative to each other. They will now switch places
as the reverse will be more accurately respected.

To migrate, make sure that your custom comparator properly differentiates
between all cases that your code base cares about and that "equal" cases are
not order dependent. We expect that due to the nature of this change, since the
entries are considered "equal", that very few projects will be affected by this
change in all but a minor visual manor that still respects the expected
operation being performed.

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of minor comments only.
I am not sure about the BC tbh. I don't really consider it to be one. It would only break usecases where all of the following conditions are true:

  • The app is using a custom comparator.
  • The custom comparator fails to differentiate between two values.
  • The app expects both ascending and descending sorting to yield the same results (which depend on the browser).

var reversed = ['B', 'a', 'c'];
var sorted = ['a', 'B', 'c'];

expect(orderBy(items)).toEqual(sorted);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem related.

@@ -115,7 +115,7 @@
*
* @param {boolean=} reverse - If `true`, reverse the sorting order.
* @param {(Function)=} comparator - The comparator function used to determine the relative order of
* value pairs. If omitted, the built-in comparator will be used.
* value pairs. If omitted or finds two entries to be equal, the built-in comparator will be used.
Copy link
Member

@gkalpak gkalpak Apr 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is more confusing than helpful. If they are interested they will read the details below.

@BobChao87
Copy link
Contributor Author

Fair enough about the breaking change. I wasn't sure either, but figured it would be better to write it up than gloss over it.

I'll get right to work on those changes.

@BobChao87 BobChao87 force-pushed the patch-14881-orderBy-default-fallback branch from 854c03f to 8f236af Compare April 14, 2017 10:05
@@ -599,7 +602,7 @@ function orderByFilter($parse) {
}
}

return compare(v1.tieBreaker, v2.tieBreaker) * descending;
return (compare(v1.tieBreaker, v2.tieBreaker) || defaultCompare(v1.tieBreaker, v2.tieBreaker)) * descending;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not fully understand this change, this should be just
return defaultCompare(v1.tieBreaker, v2.tieBreaker) * descending;

There is no reason for an external function to understand the internal tieBreaker structure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not intended to defer to the defaultCompare function over the user-provided function, but rather to provide a reasonable fallback should a user-provided function fail to the break the tie. See #14481 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lgalfaso, for better or worse, this tiebreaker structure is documented and has been used with custom comparator 😕 Do you think we should change that?
I thought it is useful to give the custom comparator a chance to break the tie in its own way (in the rare case where this will be needed).

@lgalfaso
Copy link
Contributor

lgalfaso commented Apr 14, 2017 via email

@BobChao87
Copy link
Contributor Author

Was there anything else pending or can we go ahead and mark this as ready to be merged? I think we've covered all the concerns that have been raised.

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also change the commit message to adhere to our guidelines. E.g.:

fix(orderByFilter): guarantee stable sort

If the user-provided comparator fails to break a tie, fall back to the built-in comparator.

Fixes #14881

@@ -460,6 +460,9 @@
* way. (When specifying a custom comparator, you also need to pass a value for the `reverse`
* argument - passing `false` retains the default sorting order, i.e. ascending.)
*
* If your comparator fails to differentiate between two items, they will fall back to the built-in
* comparator function, this produces a guarantee of stable sorting.
*
Copy link
Member

@gkalpak gkalpak May 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is the wrong place for this comment. It should be moved to L55 and be changed to something like:

If a custom comparator still can't distinguish between two items, then they will be sorted based on their index using the built-in comparator.

If user-provided comparison function fails to differentiate between two elements,
fall back to built-in comparator.

Fixes angular#14881
@BobChao87 BobChao87 force-pushed the patch-14881-orderBy-default-fallback branch from 8f236af to fe5f585 Compare May 15, 2017 06:10
@BobChao87
Copy link
Contributor Author

Apologies about the commit message, apparently it's been a while since I read the contributing guidelines and I use a similar scheme on another project.

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of nit. I will fix up while merging.

@@ -49,9 +49,8 @@
* second, or `1` otherwise.
*
* In order to ensure that the sorting will be deterministic across platforms, if none of the
* specified predicates can distinguish between two items, `orderBy` will automatically introduce a
* dummy predicate that returns the item's index as `value`.
* (If you are using a custom comparator, make sure it can handle this predicate as well.)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should not be removed. It is still necessary for the comparator to handle the tie-break value. E.g. if it has an array of strings, it should not expect that all values will be strings, because there is the possibility of getting passed a number (the index).

* specified predicates can distinguish between two items, `orderBy` will automatically introduce a
* dummy predicate that returns the item's index as `value`.
* (If you are using a custom comparator, make sure it can handle this predicate as well.)
* specified predicates nor the custom comparator can distinguish between two items, `orderBy` will
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is confusing imo. The custom comparator is not something additional to the predicates. They are used together (the predicates provide the values and the comparator compares them).

gkalpak pushed a commit that referenced this pull request May 19, 2017
If a user-provided comparator fails to differentiate between two items, fall
back to the built-in comparator (using the tie-breaker predicate).

Fixes #14881

Closes #15914
@gkalpak gkalpak closed this in 762580f May 19, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants