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

Iterators.reverse gives unexpected results when zipping iterators of different lengths #25583

Closed
nalimilan opened this issue Jan 16, 2018 · 19 comments · Fixed by #50435
Closed
Labels
domain:collections Data structures holding multiple items, e.g. sets kind:breaking This change will break code

Comments

@nalimilan
Copy link
Member

In the following example, it sounds like a bug that reverse assumes that the last element of 1:10 goes with the last element of 1:2. This can give misleading results. If possible it would make sense to throw an error in these cases.

julia> first(Iterators.reverse(zip(1:10, 1:2)))
(10, 2)
@ararslan ararslan added the domain:collections Data structures holding multiple items, e.g. sets label Jan 16, 2018
@ararslan
Copy link
Member

cc @stevengj

@stevengj
Copy link
Member

stevengj commented Jan 16, 2018

This is related to #20499.

My sense is that if the zip arguments have lengths, then zip itself should throw an error if the lengths are unequal.

(If they don't have lengths, reverse will probably fail anyway because they are unlikely to be reversible, but to be on the safe side reverse(::Zip) can throw an error in this case.)

@stevengj
Copy link
Member

stevengj commented Jan 16, 2018

Note that a similar issue arises for reverse(::Generator), since generators also currently truncate unequal-length iterators.

@nalimilan
Copy link
Member Author

OK, added #20499 for triage.

@nalimilan
Copy link
Member Author

Triage decided not to throw an error when zip is passed arguments with different lengths, so reverse will have to throw the error instead.

@nalimilan nalimilan added the kind:bug Indicates an unexpected problem or unintended behavior label Feb 4, 2018
@mschauer
Copy link
Contributor

so reverse will have to throw the error instead

Reverse could also be fixed to work with Zips with differing length instead I believe. Is there reason this is not wanted?

@nalimilan
Copy link
Member Author

You mean, start with the element corresponding to the end of the shortest iterator? Makes sense.

@stevengj
Copy link
Member

stevengj commented Feb 20, 2018

@mschauer, that cannot be done efficiently in general. It might be extremely inefficient if one iterator is much longer than the other. (Not to mention the case of infinite iterators etc.)

@mschauer
Copy link
Contributor

mschauer commented Feb 20, 2018

One way to approach this is

reversefrom(r::UnitRange, n::Integer) = reverse(first(r):min(last(r),first(r)+n-1))
reversefrom(it, n) = reverse(take(it, n))
reversefrom(z::Zip1, n) = Zip1(reversefrom(z.a, n))
reversefrom(z::Zip2, n) = Zip2(reversefrom(z.a, n), reversefrom(z.b, n))
reversefrom(z::Zip, n) = Zip(reversefrom(z.a, n), reversefrom(z.z, n))
 
reverse(z::Zip1) = Zip1(reverse(z.a))
reverse(z::Union{Zip,Zip2}) = reversefrom(z, length(z))

but to be useful, reversefrom(it, n::Integer) needs to defined for some more iterators, e.g.
reversefrom(r::UnitRange, n::Integer) = reverse(first(r):min(last(r),first(r)+n-1)) = reverse(first(r):min(last(r),first(r)+n-1))
or alternatively reverse for some take iterators (this can be done with drop for finite iterators)
[edit:] needs to be implemented.

@mschauer
Copy link
Contributor

@stevengj It is hard to think of an iterator one can reverse efficiently from its last element, but not from an earlier element.

@stevengj
Copy link
Member

stevengj commented Feb 20, 2018

@mschauer, the problem is precisely that you can't do this from the existing iteration protocol functions, so each iterator would have to implement more functions for it to work.

reverse(take(it, n)) doesn't help because we don't have Reverse{Take} iterators implemented. You'd just get method errors.

@mschauer
Copy link
Contributor

mschauer commented Feb 21, 2018

Yes, I know that Reverse{Take} or reversefrom(it, n::Integer) needs to be implemented for this. What I do not know is if a PR is welcome or not. [Edited.]

@stevengj
Copy link
Member

I think it adds too much complexity to require every iterator to support reversefrom in order to work for this odd corner case of reversing the zip of unequal-length iterators. Better to just throw an error in that case.

@mschauer
Copy link
Contributor

Hm, it is not needed to require every iterator to support reversefrom. In fact

reversefrom(it, n) = n==length(it) ? reverse(it) : throw(ArgumentError("...")) # fixme: catch infinite iterators in a nice way

implements a fallback which also just throws an error if a special reversefrom is not implemented strictly extending the alternative of throwing an error at the cost of complexity of some ten lines.

@stevengj
Copy link
Member

stevengj commented Feb 21, 2018

For this functionality to actually be useful, a fair number of iterators will need to implement new methods, which is a lot of code devoted to a corner case of questionable utility.

@mschauer
Copy link
Contributor

Yes, I wrote above "but to be useful, reversefrom(it, n::Integer) needs to defined for some more iterators". I thought of cycle, repeated and ranges (countfrom is mostly covered by the reversibility of enumerate).

@mschauer
Copy link
Contributor

If anybody else is interested in having this, they can create a PR from https://github.com/JuliaLang/julia/compare/master...mschauer:reverse?expand=1

@brenhinkeller brenhinkeller added kind:breaking This change will break code and removed kind:bug Indicates an unexpected problem or unintended behavior labels Nov 19, 2022
@jariji
Copy link
Contributor

jariji commented Jul 13, 2023

Given

  reverse(v [, start=firstindex(v) [, stop=lastindex(v) ]] )


  Return a copy of v reversed from start to stop. See also Iterators.reverse for reverse-order iteration without
  making a copy, and in-place reverse!.

I think the current behavior is better classified as a bug. Throwing an error is probably a bug too, but at least it's not the wrong answer.

@adienes
Copy link
Contributor

adienes commented Aug 5, 2023

#50435

oscardssmith pushed a commit that referenced this issue Oct 26, 2023
…r equality (#50435)

fixes #45085, fixes
#25583, as per suggested by
@Moelf

Despite the fact that this might cause code to fail that previously
worked, I think this is a bugfix not a breaking change.

In particular, `Iterators.reverse` says:
> Given an iterator itr, then reverse(itr) is an iterator over the same
collection but in the reverse order

And in many cases, the previous behavior of `reverse(::Zip)` would _not_
return "the same collection"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:collections Data structures holding multiple items, e.g. sets kind:breaking This change will break code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants