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

Add argument to Array#flatten to specify amount of levels to flatten. #3541

Closed
wants to merge 1 commit into from

Conversation

yxhuvud
Copy link
Contributor

@yxhuvud yxhuvud commented Nov 14, 2016

Ruby 1.8.7 added an optional numerical argument to Array#flatten to specify the amount of array levels that should be present. In practice, I've found that I almost always use flatten with an argument of 1 - in some situations it may protect against people that input arrays where the whole array shouldn't be flattened but just the topmost level.

Regarding the implementation, I'm not all that happy with the recursion criteria. It is pretty trivial apart from that. It may be a good idea to add tests for the types of the array, but I am not certain how to write that so I left it out for now.

@bcardiff
Copy link
Member

Hi @yxhuvud, the PR introduce some subtle but important issues regarding the type of the results.

Maybe a spec should be added to the existing flatten method.

Before this PR the following holds:

typeof([[1, 'a'], [[[[true], "hi"]]]].flatten).should eq(Array(Int32 | Char | Bool | String))

After your PR it does not. If I add that assertion to array_spec.cr:1678 :

Failures:

  1) Array flattens
     Failure/Error: typeof([[1, 'a'], [[[[true], "hi"]]]].flatten).should eq(Array(Int32 | Char | Bool | String))

       expected: Array(Bool | Char | Int32 | String)
            got: Array(Array(Array(Array(Array(Array(Bool) | String))) | Array(Char | Int32)) | Array(Array(Array(Array(Bool) | String))) | Array(Array(Array(Bool) | String)) | Array(Array(Bool) | String) | Array(Bool) | Array(Char | Int32) | Bool | Char | Int32 | String)

     # ./spec/std/array_spec.cr:1678

At the end of the day the following snippet is the cause of why the above assertion does not hold in this PR:

def foo(n)
  if n == 1
    3
  else
    [4]
  end
end

typeof(foo(1)) # => Int32 | Array(Int32)

It is important that the returning types are useful. Most of the time this means no union types, otherwise the user will need to cast/case the return value before using it at all.

It is impossible for crystal to determine the compile time type based on the values (number of steps to flatten).

If the case where only one step is need I would create a different operation. Maybe based on inject/concat.

@yxhuvud
Copy link
Contributor Author

yxhuvud commented Nov 14, 2016

@bcardiff Ok, yeah that makes a lot of sense. Hmm - for the single level case I assumed that flat_map &.itself would suffice, but that one seems to require a single type for each subarray (nonarray root objects work fine)

@yxhuvud yxhuvud closed this Nov 14, 2016
@bcardiff
Copy link
Member

I agree that flat_map &.itself would be great. But as you pointed out: it doesn't work right now. Maybe it can be tweaked a bit with some type check to make it compile.

Thanks! even if the PR didn't make it, it is still a contribution.

def flatten
FlattenHelper(typeof(FlattenHelper.element_type(self))).flatten(self)
def flatten(steps : Int = -2)
FlattenHelper(typeof(FlattenHelper.element_type(self, steps))).flatten(self, steps)

Choose a reason for hiding this comment

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

I am guessing might want to trap for steps <= -1 here [probably better performance]; otherwise, you'll probably want to do something like if steps <= -1 checks in the loops below in FlattenHelper [probably a bit slower]; otherwise FlattenHelper's loops could continue towards -infinity [well probably -max int].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, they can only continue until the arrays are flat. Note that the base case without arguments is -2.

But as bcardiff noted, the real issue is that the type signature is garbled.

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.

None yet

3 participants