Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reduce the runtime of pick, fixes #285 #286

Merged
merged 12 commits into from
May 25, 2015
Merged

reduce the runtime of pick, fixes #285 #286

merged 12 commits into from
May 25, 2015

Conversation

brentpayne
Copy link
Contributor

This is code fixes #285

It reduces the runtime of pick from O(object properties * selection properties) to O(selection properties) or simply from O(nm) to O(n)

Additional bonus, pick and pickBy are now implemented using map, curry and a helper function.

…rties) to O(selection properties)

Additional bonus, pick and pickBy are now implemented using map, curry and a helper function.
@brentpayne brentpayne changed the title reduce the runtime of pick Issue #285 reduce the runtime of pick, fixes #285 Apr 25, 2015
@vqvu
Copy link
Collaborator

vqvu commented Apr 25, 2015

One thing, much of the code base uses function statements when possible. Can you change your function expressions to statements? Just to be consistent.

Other than that, looks good to me.

@svozza
Copy link
Collaborator

svozza commented Apr 25, 2015

And possibly curry objectOnly directly so the maps can be written:

...
return this.map(objectOnly(pickSelection));
...
return this.map(objectOnly(pickBySelection));
...

@brentpayne
Copy link
Contributor Author

updated

@vqvu
Copy link
Collaborator

vqvu commented Apr 26, 2015

I should have mentioned this earlier, but can you add a test for inherited and non-enumerable properties? Make sure we catch all of them.

@brentpayne
Copy link
Contributor Author

good call, pickBy will only select enumerable keys. pick will select enumerable or non-enumerable keys. The old behavior was to select only enumerable keys, but we stated we'd change this b/c other libraries do pick non-enumerable properties, except ramda.

Before I make the change, are we in agreement that pick and pickBy should select non-enumerables?

@vqvu
Copy link
Collaborator

vqvu commented Apr 28, 2015

pick should. Can we make it work for pickBy? If not, we can just put a note in the docs saying one works on non-enumerable and one doesn't.

@brentpayne
Copy link
Contributor Author

I believe I can make it work for pickBy as well. Expect an update tonight or tomorrow.

…owned by the instance or any prototype object in its prototype chain.

updated tests to account for selection of non-enumerable prototype member variables.
@brentpayne
Copy link
Contributor Author

update pickBy to select enumerable or non-enumerable member variables on the object or from any associated prototypes. Updated tests to account for this. Specifically tests that failed because objects have non-enumerable function member variables starting with the '_' character.

var obj = x; // variable used to transverse prototype chain

// function created here so we don't create one per loop
var testAndAdd = function (prop) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Function expression vs function statement again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this case, it is an optimization b/c it creates the function once instead of once per loop iteration. I placed a comment above it to note why this code is inconsistent with the rest of the library.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure what you mean. Couldn't you replace

var testAndAdd = function (prop) {
...
}

with

function testAndAdd(prop) {
...
}

Why would testAndAdd be created once per loop iteration?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ahh, got it I thought you meant put the declaration inside the loop as an anonymous function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@brentpayne
Copy link
Contributor Author

All changes made, but I haven't run modified tests on an ES3 browser/engine.

next();
var obj = x; // variable used to transverse prototype chain
function testAndAdd (prop) {
if (!(prop in out) && f(prop, x[prop])) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would result in overridden properties being passed to f. Example

function f(p, v) {
    return v === 1;
}

var obj1 = {v: 1};
var obj2 = Object.create(obj1);
obj2.v = 2;

_([obj2]).pickBy(f).toArray(_.log) // => [{v: 1}]

In the reverse, it'll also disallow picking on default properties of objects, like hasOwnProperty, but that's not as important.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you also add a test for the case I mentioned above? Call it "pickBy - overridden properties" or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

subtle, but yes.
I'll fix it so overridden properties never make it to f

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the test, but reverted my fix when I realized that it didn't originally fail. The reason why it doesn't fail is that the property may get tested twice, but always with the value relative to the object and not the prototype.
This is because testAndAdd is referrencing from the original object, obj2 here, rather than the prototype object, obj1. So v would always be 2.

I'm going to go ahead and prevent it from testing a property multiple times.

if (err) {
push(err);
next();
var seen = {}; // prevents testing overridden properties multiple times.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you think seen should be Object.create(null) so that properties from Object.prototype get tested?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good point

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed, added test the cover that case

Copy link
Collaborator

Choose a reason for hiding this comment

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

Doesn't Object.create fall prey to same problem as Object.getPrototypeOf in that IE8 doesn't support it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the best way to deal with this? Put the Object.create behind a if (es5) check? Use strict seen[prop] === true instead of prop in seen? None of the Object.prototype properties === true, right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The best way to deal with it is to drop support for IE8. ;) Yeah, I guess we could do

var seen = isES5 ? Object.create(null) : {}

And then the strict equals test (that's very nice, I never knew about that before).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Cool. Fixed in 73a8cfe, though I don't have IE8 lying around, so I can't confirm.

… dictionary prototype member variable names automatically being skipped
@vqvu
Copy link
Collaborator

vqvu commented May 25, 2015

Looks good. Thanks for the great work!

vqvu added a commit that referenced this pull request May 25, 2015
reduce the runtime of `pick`, fixes #285
@vqvu vqvu merged commit 30a8638 into caolan:master May 25, 2015
@vqvu vqvu added this to the v2.6.0 milestone May 25, 2015
@brentpayne brentpayne deleted the feature/pick-o-nk branch June 1, 2015 21:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reimplement pick as O(n), currently O(nm)
3 participants