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 Enumerable#index_with. #32523

Merged
merged 2 commits into from May 21, 2018

Conversation

Projects
None yet
8 participants
@kaspth
Member

kaspth commented Apr 10, 2018

In the app I'm working on I've wished that index_by had a buddy that would
assign the hash value instead of the key multiple times.

Enter index_with. Useful when building a hash from a static list of
symbols. Before you'd do:

POST_ATTRIBUTES.map { |attr_name| [ attr_name, public_send(attr_name) ] }.to_h

But now that's a little clearer and faster with:

POST_ATTRIBUTES.index_with { |attr_name| public_send(attr_name) }

It's also useful when you have an enumerable that should be converted to a hash,
but you don't want to muddle the code up with the overhead that it takes to create
that hash. So before, that's:

WEEKDAYS.each_with_object(Hash.new) do |day, intervals|
  intervals[day] = [ Interval.all_day ]
end

And now it's just:

WEEKDAYS.index_with([ Interval.all_day ])

It's also nice to quickly get a hash with either nil, [], or {} as the value.

@kaspth kaspth self-assigned this Apr 10, 2018

@jonathanhefner

This comment has been minimized.

jonathanhefner commented Apr 10, 2018

I've also needed this method on numerous occasions, but I named it index_to. What do you think of that name?

Also, for the last code snippet, I think you meant WEEKDAYS.index_with { [ Interval.all_day ] }, yes?

@albertoalmagro

This comment has been minimized.

Contributor

albertoalmagro commented Apr 13, 2018

I like it, 👍

@khiav223577

This comment has been minimized.

khiav223577 commented Apr 23, 2018

Wanna to have this method in ruby too! 👍

kaspth added some commits Apr 10, 2018

Add Enumerable#index_with.
In the app I'm working on I've wished that index_by had a buddy that would
assign the hash value instead of the key multiple times.

Enter index_with. Useful when building a hash from a static list of
symbols. Before you'd do:

```ruby
POST_ATTRIBUTES.map { |attr_name| [ attr_name, public_send(attr_name) ] }.to_h
```

But now that's a little clearer and faster with:

````ruby
POST_ATTRIBUTES.index_with { |attr_name| public_send(attr_name) }
```

It's also useful when you have an enumerable that should be converted to a hash,
but you don't want to muddle the code up with the overhead that it takes to create
that hash. So before, that's:

```ruby
WEEKDAYS.each_with_object(Hash.new) do |day, intervals|
  intervals[day] = [ Interval.all_day ]
end
```

And now it's just:

```ruby
WEEKDAYS.index_with([ Interval.all_day ])
```

It's also nice to quickly get a hash with either nil, [], or {} as the value.

@kaspth kaspth merged commit 41147e3 into rails:master May 21, 2018

1 check was pending

codeclimate Code Climate is analyzing this code.
Details

@kaspth kaspth deleted the kaspth:enumerable-index-with-extension branch May 21, 2018

@kaspth

This comment has been minimized.

Member

kaspth commented May 21, 2018

I've also needed this method on numerous occasions, but I named it index_to. What do you think of that name?

@jonathanhefner index_to makes me think of our to and from Array/String extensions.

index_to also doesn't suggest its relationship to the existing index_by strongly enough to me. Of course, it's arguable if index_with does that better. But by and with do seem to imply a certain closeness, as well as their respective difference.

Also, for the last code snippet, I think you meant WEEKDAYS.index_with { [ Interval.all_day ] }, yes?

I just noticed I'd forgotten to push that bit. Thanks!

@jonathanhefner

This comment has been minimized.

jonathanhefner commented May 21, 2018

@kaspth Thank you for your response! Even though it's too late to change, I still want to defend my name choice. 😉 The connection between index_by and index_to is like the connection between "a letter written by someone" and "a letter written to someone." There is an implication of an arrow--in the case of a letter, the arrow from the writer to the reader; in the case of a Hash, the arrow from the key to the value.

Also, to me, index_with sounds more like an alias of index_by. For example, I would have guessed ["abc"].index_with(&:upcase) creates the index keys using upcase.

Either way, it's nice to have this in Rails.

@mcary

This comment has been minimized.

mcary commented May 31, 2018

I'm with @jonathanhefner: index_with sounds synonymous to index_by. My first impression when I read the sample code is that I expect the block after the "with" to generate the index, not the value.

If this goes to Ruby, I could imagine one of these method names being clearer:

  • index_for
  • map_to
  • with_values
  • indexing
  • indexing_values
  • index_of

index_of and index_for seem to have a clearer relationship to index_with, but index_of in some languages means what Array#index means in Ruby, so that's a potential source of confusion. indexing_values is a bit long. And indexing sounds more declarative than imperative, which might be confusing. Same for index_to (suggested above).

Proc#to_h(enumerable) might be another way of generating an Hash from an enumerable and a proc, but using proc with a block is not as concise as passing a block to an Enumerable method, and maybe really the Enumerable should contain arrays of arguments rather than assuming only one argument...

@vaibhavatul47

This comment has been minimized.

vaibhavatul47 commented Jun 1, 2018

I agree with @mcary, that index_with gives an impression that the values from block will be used as keys in hash rather than values in hash. IMO, map_to is a great suggestion.

bogdanvlviv added a commit to bogdanvlviv/rails that referenced this pull request Jun 7, 2018

Fix example of `index_with`
- Clarify executor of `public_send`.
- Do not wrap `Interval.all_day` into [] since
  an array is expected as a returned value.

Related to rails#32523.

[ci skip]
@kaspth

This comment has been minimized.

Member

kaspth commented Jul 2, 2018

@jonathanhefner it's only too late once we've shipped Rails 6. At least I'm happy to keep going on this.

I'm still not on board with the _to versions. index_to makes me go: to whom? And I find a pneumonic like "index by key" and "index with value" just as obvious. _with wins because it flows better. index_with_values is closer and clearer, but then I don't mind some conciseness in the vein of select/collect/reject (note the similarity in wording there as well).

I can see how it sounds like a straight alias though.

@hachi8833 hachi8833 referenced this pull request Sep 24, 2018

Merged

Add Array#index_with #746

@reggieb

This comment has been minimized.

reggieb commented Oct 12, 2018

If this was the normal behaviour for array.to_h:

[1,2,3].to_h # => {1 => 1, 2 => 2, 3 => 3}

And the array of pairs behaviour was just a special case:

[[:a, 1], [:b, 2]] # => { a: 1, b: 2}

Then the index_with behaviour could be achieved with:

hash = [1,2,3].to_h
hash.transform_values { |v| v * 2 } # => { 1 => 2, 2 => 4, 3 => 6 }

Which I think is easier to understand. It would also then be just as simple to modify the keys rather than the values:

hash.transform_keys { |k| (k + 96).chr.to_sym } # => {a: 1, b: 2, c: 3}

Therefore perhaps a better solution is:

class Array
  def to_h
    if all? {|i| i.kind_of?(Array) && i.length == 2}
      super
    else
      map {|x| [x,x]}.to_h
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment