-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
Fix behavior of foldr
on empty arrays (#20144)
#20160
Conversation
When foldr is called with two arguments and an empty array, mapfoldr does not make a check for empty array and tries to access the first element. This check is present in mapfoldl. The commit adds equivalent behavior to mapfoldl.
@@ -131,7 +131,13 @@ mapfoldr(f, op, v0, itr) = mapfoldr_impl(f, op, v0, itr, endof(itr)) | |||
Like `mapfoldr(f, op, v0, itr)`, but using the first element of `itr` as `v0`. In general, | |||
this cannot be used with empty collections (see `reduce(op, itr)`). | |||
""" | |||
mapfoldr(f, op, itr) = (i = endof(itr); mapfoldr_impl(f, op, f(itr[i]), itr, i-1)) | |||
function mapfoldr(f, op, itr) | |||
i = endof(itr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for the trailing semicolon; remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forgot to remove it, sorry.
Removed.
@@ -16,6 +17,7 @@ | |||
@test Base.mapfoldl((x)-> x ⊻ true, |, [true false true false false]) == true | |||
@test Base.mapfoldl((x)-> x ⊻ true, |, false, [true false true false false]) == true | |||
|
|||
@test foldr(+, Int64[]) == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a comment to each of these tests referencing the relevant issue and pull request?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha. Done.
Thanks for reviewing!
Welcome to Julia @Vsart! Much thanks for this great first pull request! |
mapfoldr(f, op, itr) = (i = endof(itr); mapfoldr_impl(f, op, f(itr[i]), itr, i-1)) | ||
function mapfoldr(f, op, itr) | ||
i = endof(itr) | ||
if i == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I belive you don't need the i = endof(itr)
here, instead, you can do if isempty(itr)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pabloferz, the existing implementation uses i
in the call to mapfoldr_impl
(see just below)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, missed that. Carry one then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is endof(itr) == 0
correct for all iterator types though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wondered the same given the mapfoldl
implementation takes what might be a more general approach. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, start
and done
is definitively better and general enough (the endof(itr) == 0
is actually what threw me out)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied the check from mapfoldr_impl
(here) which tests if the i
argument is 0, I'm assuming that if the one I implemented in mapfoldr
should change so should that check?
foldr
on empty arrays (#20144)foldr
on empty arrays (#20144)
The build failure on OSX from Travis seems to be unrelated. |
@nanosoldier |
I'd maybe make some of the reduce tests more specific and also test for type equality via |
@tkelman About the type equality tests, do you mean testing for example
or you meant something else? Currently this is not true (for both the new julia> typeof(foldl(+, Int8[]))
Int32
julia> typeof(foldl(+, Int16[]))
Int32
julia> typeof(foldl(+, Int32[]))
Int64
julia> typeof(foldl(+, Int64[]))
Int64 Note that this behavior is not particular to empty arrays. |
Your benchmark job has completed - possible performance regressions were detected. A full report can be found here. cc @jrevels |
Right, reductions currently widen mostly to make them less susceptible to overflow. But where we or packages care at all about the return type, it's worth encoding that specificity into the tests. If we ever intend to change the return type, we'll have to visibly change the tests. What testing the types would avoid is accidentally changing the return types and not noticing. |
This looks good to me. Can all the reviewer please approve the changes? |
@@ -131,7 +131,13 @@ mapfoldr(f, op, v0, itr) = mapfoldr_impl(f, op, v0, itr, endof(itr)) | |||
Like `mapfoldr(f, op, v0, itr)`, but using the first element of `itr` as `v0`. In general, | |||
this cannot be used with empty collections (see `reduce(op, itr)`). | |||
""" | |||
mapfoldr(f, op, itr) = (i = endof(itr); mapfoldr_impl(f, op, f(itr[i]), itr, i-1)) | |||
function mapfoldr(f, op, itr) | |||
i = endof(itr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the endof
could be moved to after the if
now, but I don't know if there are any collections where endof
would be expensive enough for it to matter when isempty
is true
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense though, the only reason it's before is because I was previously using the i
value in the if
. Should I make the change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably not worth waiting in the CI queue for again, if nobody else has any more substantial feedback. Maybe next time someone's working around this bit of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option is that you could leave the i = endof(itr)
and use if done(itr, i)
instead of if isempty(itr)
. Other than that, I don't have more comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pabloferz I considered that option over isempty
but I found out that because of the following behavior I couldn't use it
julia> itr = Float64[]
0-element Array{Float64,1}
julia> i = endof(itr)
0
julia> done(itr, i)
false
It would only return true
when i == 1
for the empty array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's right (forgot that this recurses backwards). Then, it is fine as is right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be squashed on merge, lgtm
Thanks @Vsart! |
This is a fix for #20144.
When fixing #7465, a check was added based on
mr_empty
tomapfoldl
, which is called by bothfoldl
andreduce
, so they would have better behavior with empty arrays.This implements an equivalent check on
foldr
so that it will have the same behavior asfoldl
andreduce
.I also added tests to make sure "folding" an operator such as
+
on an empty array gives the expected result.This is my first PR, so sorry for any mistakes.