From: "kyanagi (Kouhei Yanagita)" Date: 2021-09-21T08:53:02+00:00 Subject: [ruby-core:105356] [Ruby master Feature#18181] Introduce Enumerable#min_with_value, max_with_value, and minmax_with_value Issue #18181 has been updated by kyanagi (Kouhei Yanagita). `enum.group_by { ... }` holds all of the elements of enum, so it consumes a lot of memory and is very slow if enum is enormous. ``` ruby require 'benchmark' Benchmark.bm(16) do |x| enum = -1000000..1000000 x.report("group_by") { enum.group_by { |i| i.abs }.min } x.report("map.min_by") { enum.map { |i| [i.abs, i] }.min_by(&:first) } x.report("min_with_value") { enum.min_with_value { |i| i.abs } } x.report("each") { enum.each { |i| i.abs } } end ``` ``` user system total real group_by 1.238664 0.243779 1.482443 ( 1.483547) map.min_by 0.721992 0.047738 0.769730 ( 0.770767) min_with_value 0.236433 0.004105 0.240538 ( 0.240596) each 0.150815 0.000000 0.150815 ( 0.151704) ``` `#min` and `#min_by` return single element even through there are multiple smallest elements. There may be cases where it is useful to return multiple elements, but single element will be enough for most cases. Writing `elements.first` everytime is a little bit of a pain. Besides, size of the elements may become very large. (if almost all of the elements are mapped into the same value) I think `#min_with_value` should return the element as primary (i.e. the element is first, the block's value is second) because `#min` and `#min_by` return the element. Additionally, this is similar to the way `#each_with_index` and `#each_with_object` pass their block arguments. ``` ruby enum.each_with_index do |elem, i| # <- the element is first, the other is second. end enum.each_with_object([]) do |elem, obj| # <- the element is first, the other is second. end ``` Naming issue: The behavior of this method is described as "The minimum element by the block value and its value". Based on this, the name will be something like `min_element_by_value_and_value` or `min_element_by_value_with_value` if verbosely named. Considering the method `#min` which returns the element, the word "element" is thought to be able to omitted. So I suggest the short name `#min_with_value`. (I think `#min_by_with_value` is also possible.) The word "with" implies that "min(_element)" and "value" are different objects. ---------------------------------------- Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_value https://bugs.ruby-lang.org/issues/18181#change-93770 * Author: kyanagi (Kouhei Yanagita) * Status: Open * Priority: Normal ---------------------------------------- PR is https://github.com/ruby/ruby/pull/4874 I propose `Enumerable#min_with_value`, `max_with_value` and `minmax_with_value`. These methods work like this: ``` ruby %w(abcde fg hijk).min_with_value { |e| e.size } # => ['fg', 2] %w(abcde fg hijk).max_with_value { |e| e.size } # => ['abcde', 5] %w(abcde fg hijk).minmax_with_value { |e| e.size } # => [['fg', 2], ['abcde', 5]] ``` Motivation: When I use `Enumerable#min_by`, I sometimes want to get not only the minimum element but also the value from the given block. (e.g.: There are many points. Find the nearest point and get distance to it.) ``` ruby elem = enum.min_by { |e| foo(e) } value = foo(elem) ``` This works, but I'd like to avoid writing foo() twice. (Consider a more complex case.) This can be written without repeated foo() like belows, but it is slightly complicated and needs extra arrays. ``` ruby value, elem = enum.map { |e| [foo(e), e] }.min_by(&:first) ``` If the size of enum is enormous, it is hard to use intermediate arrays. `Enumerable#min_with_value` solves this problem. I think `min_with_value` is the best name I could think of, but any suggestions for better names would be appreciated. -- https://bugs.ruby-lang.org/ Unsubscribe: