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 'Hash#each_key_for' methods #5450

Closed

Conversation

marksiemers
Copy link
Contributor

src/hash.cr Outdated
# key # => "dos"
# ```
def each_key_for(value)
self.select { |k, v| v == value }.each_key
Copy link
Contributor

Choose a reason for hiding this comment

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

each.select { |(k, v)| v == value }.map &.[0]

raw select create copy of hash, slow and not accurate if hash change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I'll make that change.

Any danger to using &.[0]?

src/hash.cr Outdated
# key # => "dos"
# ```
def each_key_for(value)
each.select { |(k, v)| v == value }.map &.first
Copy link
Contributor

Choose a reason for hiding this comment

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

You should build a custom Iterator class for this. For example look at the valueiterator below.

Copy link
Contributor

Choose a reason for hiding this comment

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

i thought this first, but what need? this functionality not very needed, so if not super optimized fine too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@RX14 and @larubujo
For moving to a custom Iterator class, what advantages does it provide?

  • Better performance
  • More reliable
  • More readable
  • More reusable

It will be very similar to KeyIterator(K, V), from what I can tell.

Entry#fore may need to be overridden, unless there is a way to write KeyForValueIterator#next that I haven't figured out yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

@marksiemers more performance and more consistent with how the other iterators are implemented.

It wouldn't be hard, given the BaseIterator module and how the other iterators are implemented. It should in fact be only 3-4 lines in the def next impl.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually my bad, it can't be done with base_next.

def next
  current = @current
  while current
    if current.value == @value
      @current = current.fore
      return current.key
    else
      current = current.fore
    end
  end
  @current = current
  stop
end

@marksiemers
Copy link
Contributor Author

marksiemers commented Dec 25, 2017 via email

@marksiemers
Copy link
Contributor Author

@RX14 - LMK if this is what you had in mind.

@asterite
Copy link
Member

asterite commented Jan 2, 2018

Do we really need this method? What's the use case? There's even an iterator here! It seems super specific... I'm not sure such code belongs here. It's not even in Ruby's std.

@marksiemers
Copy link
Contributor Author

@asterite - It came from a discussion with @drosehn @bcardiff and a few others.
See here: #5248 (comment)

From @drosehn:

I certainly write a lot of hashes were the same value appears for multiple keys, and sometimes I do want to find all keys which match a given value. In those cases I loop through all keys to see if it matches the value I care about.

@drosehn
Copy link

drosehn commented Jan 2, 2018

For me, the main point of issue 5248 was that Hash#key? was a confusing name for what that method did. In one of the early responses to issue, @asterite wrote:

Ooh... Now I see the difference. Maybe key_for_value is a good name, or we could also leave it like that for now. It's a difference with Ruby but it doesn't matter

@asterite
Copy link
Member

asterite commented Jan 3, 2018

I personally can't approve this. It's not general enough to include it in the standard library, and it's so easy to implement outside of it.

# each_for_key
hash.each do |key, value|
  next unless value == target_value
end

# each_for_key (iterator)
each.select { |k, v| v == target_value }

I don't feel we need to include this custom logic in the standard library, and much less a custom iterator just for this.

@RX14
Copy link
Contributor

RX14 commented Jan 3, 2018

I disagree. I think it's a nice addition for completeness's sake, and there's going to be little if any change to the implementation of it, ever. If there's a usecase, I don't see why not.

@marksiemers
Copy link
Contributor Author

@RX14 @asterite @bcardiff @drosehn
Do we want to make a decision on this and either merge or close?

@drosehn
Copy link

drosehn commented Jan 27, 2018

I like the idea, but I'm not one to say whether it is useful enough to make it part of the stdlib. But at the very least, I suspect I'll refer back to the code in this pull request whenever I want a feature like this in my own code.

@marksiemers
Copy link
Contributor Author

@bcardiff - Any thoughts on this. each_key_for is a replacement for your originally suggested all_keys_for.

As I worked through it, each_key_for seemed to make more sense:

I've been working through this, and I think just having each_key_for will be a cleaner solution (and dropping all_keys_for):

Hash#each_key_for(value) : Iterator(K)
Hash#each_key_for(value, &block) : Nil
The second will yield to the block, and the first, you can call to_a to get an Array(K)

One less method and consistently singular with key_for

@bcardiff
Copy link
Member

@marksiemers sorry for the delay. I like the api . From @asterite last comment I think we could get rid of the custom iterator implementation though, but keep both methods.

So the implementation can be along each.select { |k, v| v == target_value }. I don't think there will be any performance difference.

Another alternative would be to take advantage of BaseIterator as the other hash iterators, but I believe the former should be enough.

WDYT?

@marksiemers
Copy link
Contributor Author

marksiemers commented Jan 30, 2018

@bcardiff - something similar was implemented in an earlier iteration of the PR.

It was changed to each.select { |(k, v)| v == value }.map &.first based on this comment from @larubujo :

raw select create copy of hash, slow and not accurate if hash change

See: dc82c69#r158608841

The iterator was added based on this comment from @RX14 :

You should build a custom Iterator class for this...more performance and more consistent with how the other iterators are implemented.

See: bb2904c#r158609466

It does use BaseIterator to an extent as it inherits from KeyIterator which includes BaseIterator. However, I don't think it is possible to use base_next to efficiently iterate for this functionality (since it is being filtered).

@bcardiff bcardiff closed this Jan 31, 2019
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

6 participants