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

Move `zip` and `zip?` to `Enumerable` and make it work with any number of `Indexable` or `Iterable` or `Iterator` #7453

Merged
merged 1 commit into from Mar 30, 2019

Conversation

Projects
None yet
5 participants
@asterite
Copy link
Member

commented Feb 18, 2019

Because Enumerable only provides internal iteration (there's no next, unlike Iterator) and I think because at the time zip was written there was no Iterator at all, zip used to only work against Indexable things.

This PR changes that. First, zip is moved to Enumerable because the only requirement the receiver has is to be able to traverse its elements. However, the arguments can now be any of Indexable, Iterable or Iterator, and there can be any number of them.

Some examples:

a = [1, 2, 3]
b = "a".."c"
c = 9.downto(1)

a.zip(b) # => [{1, "a"}, {2, "b"}, {3, "c"}]

b.zip(a) # => [{"a", 1}, {"b", 2}, {"c", 3}]

a.zip(b, c) # => [{1, "a", 9}, {2, "b", 8}, {3, "c", 7}]

# Don't do this at home :-P
(1..5).zip(Dir.open(".")) #=> [{1, "."}, {2, ".."}, {3, "man"}, {4, "CODE_OF_CONDUCT.md"}, {5, "compiler_spec"}] 

All of the above also work with the block version.

It's nice to see that the above snippet also works exactly the same as in Ruby :-)

It might be a bit strange that the receiver has to be Enumerable but the arguments have to be of different types, but the truth is that, at least in the std many/most of the type implement Enumerable, Iterable and Iterator at the same time.

Is this useful? I'm pretty sure it has its usecases (zipping two or three sequences at the same time). It can also be good for "competition" problems. But ultimately, this is a more complete and powerful version of the existing zip, and the implementation isn't that complex.

Bonus snippet: another way to do each_with_index:

['a', 'b', 'c'].zip(1..) do |e, i|
  puts "#{i}) #{e}"
end

(but it's probably a bit slower than each_with_index)

Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated
Show resolved Hide resolved src/enumerable.cr Outdated

@asterite asterite force-pushed the asterite:feature/zip-superpower branch from 017dab1 to dec2fe3 Feb 19, 2019

Move `zip` and `zip?` to `Enumerable` and make it work with any numbe…
…r of `Indexable` or `Iterable` or `Iterator`

@asterite asterite force-pushed the asterite:feature/zip-superpower branch from dec2fe3 to b7f7482 Feb 19, 2019

@asterite

This comment has been minimized.

Copy link
Member Author

commented Feb 19, 2019

@r00ster91 Thanks! I found it easier to apply the suggestions locally and then force push everything. Let me know if it's good now.

Show resolved Hide resolved src/enumerable.cr
Show resolved Hide resolved src/enumerable.cr
@straight-shoota
Copy link
Member

left a comment

Sounds good! 👍

# a.zip(b, c) # => [{1, 4, 8}, {2, 5, 7}, {3, 6, 6}]
# ```
def zip(*others : Indexable | Iterable | Iterator)
pairs = Array(typeof(zip(*others) { |e| break e }.not_nil!)).new(size)

This comment has been minimized.

Copy link
@yxhuvud

yxhuvud Mar 3, 2019

Contributor

How does that type resolution handle a other collection whose elements are nilable?

This comment has been minimized.

Copy link
@asterite

asterite Mar 3, 2019

Author Member

It's the tuple that becomes nilable because the compiler can't know whether the block will be called (or something like that). This doesn't affect individual elements.

@asterite asterite requested a review from bcardiff Mar 27, 2019

@asterite asterite added this to the 0.28.0 milestone Mar 30, 2019

@asterite asterite merged commit 4402703 into crystal-lang:master Mar 30, 2019

5 checks passed

ci/circleci: check_format Your tests passed on CircleCI!
Details
ci/circleci: test_darwin Your tests passed on CircleCI!
Details
ci/circleci: test_linux Your tests passed on CircleCI!
Details
ci/circleci: test_linux32 Your tests passed on CircleCI!
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details

@asterite asterite deleted the asterite:feature/zip-superpower branch Mar 30, 2019

@bcardiff

This comment has been minimized.

Copy link
Member

commented Mar 30, 2019

I’m late to the review. But I wanted to say that i liked the flexibility this PR brings. And also the tricks of using typeof of the yielding methods to be able to type the resulting array.

@asterite

This comment has been minimized.

Copy link
Member Author

commented Mar 30, 2019

@bcardiff Oops, sorry I didn't wait for your review. I remembered you said it was approved but I forgot you wanted to review it.

Yeah, typeof keeps rocking it :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.