https://bugs.ruby-lang.org/https://bugs.ruby-lang.org/favicon.ico?17113305112021-09-20T16:55:35ZRuby Issue Tracking SystemRuby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937692021-09-20T16:55:35Zsawa (Tsuyoshi Sawada)
<ul></ul><p>This is a frequently seen use case. I use code like this:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hi jkl mn)</span><span class="p">.</span><span class="nf">group_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">min</span> <span class="c1"># => [2, ["fg", "hi", "mn"]]</span>
</code></pre>
<p>and I don't see a strong need to shorten it into a single method although I am not strongly against doing so. However, I have some concerns with your proposal:</p>
<ol>
<li>
<p>Its use will be limited if it only picks a single element when the min/max element is not unique. I propose that it should rather hold an array of the corresponding elements.</p>
</li>
<li>
<p>I don't think the word "value" is appropriate. By this word, what comes to mind most naturally is the min/max value rather than the enumerated element. Perhaps a word like "element(s)" should be used.</p>
</li>
<li>
<p>I don't think the order of the elements you proposed is appropriate. With <code>each_with_object</code>, the enumerated entity is followed by the accumulating object, i.e., the word order matches the order of the elements within the subarray. I think you should follow this practice.</p>
</li>
<li>
<p>The methods <code>min</code>/<code>max</code> are used without a block, <code>min_by</code> / <code>max_by</code> are used with a block. And in this case, you are using a block.</p>
</li>
</ol>
<p>Thus, I think your proposal should be modified into something like the following (which looks so similar to what can be done already as I have shown above):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hi jkl mn)</span><span class="p">.</span><span class="nf">min_by_with_elements</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span> <span class="c1"># => [2, ["fg", "hi", "mn"]]</span>
</code></pre> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937702021-09-21T08:52:56Zkyanagi (Kouhei Yanagita)
<ul></ul><p><code>enum.group_by { ... }</code> holds all of the elements of enum,<br>
so it consumes a lot of memory and is very slow if enum is enormous.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">require</span> <span class="s1">'benchmark'</span>
<span class="no">Benchmark</span><span class="p">.</span><span class="nf">bm</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">x</span><span class="o">|</span>
<span class="n">enum</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1000000</span><span class="o">..</span><span class="mi">1000000</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"group_by"</span><span class="p">)</span> <span class="p">{</span> <span class="n">enum</span><span class="p">.</span><span class="nf">group_by</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span><span class="p">.</span><span class="nf">abs</span> <span class="p">}.</span><span class="nf">min</span> <span class="p">}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"map.min_by"</span><span class="p">)</span> <span class="p">{</span> <span class="n">enum</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="p">[</span><span class="n">i</span><span class="p">.</span><span class="nf">abs</span><span class="p">,</span> <span class="n">i</span><span class="p">]</span> <span class="p">}.</span><span class="nf">min_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:first</span><span class="p">)</span> <span class="p">}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"min_with_value"</span><span class="p">)</span> <span class="p">{</span> <span class="n">enum</span><span class="p">.</span><span class="nf">min_with_value</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span><span class="p">.</span><span class="nf">abs</span> <span class="p">}</span> <span class="p">}</span>
<span class="n">x</span><span class="p">.</span><span class="nf">report</span><span class="p">(</span><span class="s2">"each"</span><span class="p">)</span> <span class="p">{</span> <span class="n">enum</span><span class="p">.</span><span class="nf">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">i</span><span class="o">|</span> <span class="n">i</span><span class="p">.</span><span class="nf">abs</span> <span class="p">}</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<pre><code> 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)
</code></pre>
<p><code>#min</code> and <code>#min_by</code> return single element even through there are multiple smallest elements.<br>
There may be cases where it is useful to return multiple elements,<br>
but single element will be enough for most cases.<br>
Writing <code>elements.first</code> everytime is a little bit of a pain.<br>
Besides, size of the elements may become very large. (if almost all of the elements are mapped into the same value)</p>
<p>I think <code>#min_with_value</code> should return the element as primary (i.e. the element is first, the block's value is second)<br>
because <code>#min</code> and <code>#min_by</code> return the element.<br>
Additionally, this is similar to the way <code>#each_with_index</code> and <code>#each_with_object</code> pass their block arguments.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">enum</span><span class="p">.</span><span class="nf">each_with_index</span> <span class="k">do</span> <span class="o">|</span><span class="n">elem</span><span class="p">,</span> <span class="n">i</span><span class="o">|</span> <span class="c1"># <- the element is first, the other is second.</span>
<span class="k">end</span>
<span class="n">enum</span><span class="p">.</span><span class="nf">each_with_object</span><span class="p">([])</span> <span class="k">do</span> <span class="o">|</span><span class="n">elem</span><span class="p">,</span> <span class="n">obj</span><span class="o">|</span> <span class="c1"># <- the element is first, the other is second.</span>
<span class="k">end</span>
</code></pre>
<p>Naming issue:</p>
<p>The behavior of this method is described as "The minimum element by the block value and its value".<br>
Based on this, the name will be something like <code>min_element_by_value_and_value</code> or <code>min_element_by_value_with_value</code> if verbosely named.</p>
<p>Considering the method <code>#min</code> which returns the element, the word "element" is thought to be able to omitted.<br>
So I suggest the short name <code>#min_with_value</code>. (I think <code>#min_by_with_value</code> is also possible.)<br>
The word "with" implies that "min(_element)" and "value" are different objects.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937712021-09-21T09:00:34Zkyanagi (Kouhei Yanagita)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/93771/diff?detail_id=60809">diff</a>)</li></ul> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937722021-09-21T09:01:45Zkyanagi (Kouhei Yanagita)
<ul></ul><p>I added an example code using <code>#min_with_value</code>.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937742021-09-21T14:43:21ZDan0042 (Daniel DeLorme)
<ul></ul><p>I like this proposal, but I think @sawa's idea has value also. It would be simple to accommodate both uses with a single syntax:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg xx hijk)</span><span class="p">.</span><span class="nf">min_with_value</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span> <span class="c1"># => [2, 'fg', 'xx']</span>
<span class="n">v</span><span class="p">,</span><span class="n">elem</span> <span class="o">=</span> <span class="sx">%w(abcde fg xx hijk)</span><span class="p">.</span><span class="nf">min_with_value</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span> <span class="c1">#in the normal case where you only want the 'first' minimum</span>
<span class="n">v</span><span class="p">,</span><span class="o">*</span><span class="n">elems</span> <span class="o">=</span> <span class="sx">%w(abcde fg xx hijk)</span><span class="p">.</span><span class="nf">min_with_value</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span> <span class="c1">#when you want all of them</span>
</code></pre> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937772021-09-22T00:48:31Zkyanagi (Kouhei Yanagita)
<ul><li><strong>Description</strong> updated (<a title="View differences" href="/journals/93777/diff?detail_id=60810">diff</a>)</li></ul> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=937782021-09-22T00:51:02Zkyanagi (Kouhei Yanagita)
<ul></ul><p>Description about <code>#min_with_value(n)</code> added. It corresponds to <code>#min(n)</code>.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=938332021-09-24T18:46:58ZEregon (Benoit Daloze)
<ul></ul><p>:+1: I've needed this several times.<br>
I think the semantics in the description are the best and clearest.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=950872021-12-03T05:04:56Zmatz (Yukihiro Matsumoto)matz@ruby.or.jp
<ul></ul><p>Just for confirmation, is it OK that <code>min_with_value</code> returns only a single value?<br>
I don't like the name <code>_with_value</code>. It explains that the method returns an additional value with the minimum/maximum value, but no additional information on the value.</p>
<p>Matz.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=951232021-12-03T15:00:49ZDan0042 (Daniel DeLorme)
<ul></ul><p>What about <code>min_with</code> ?</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_with</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span> <span class="c1"># => ['fg', 2]</span>
<span class="c1"># ^min ^size</span>
</code></pre> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=951262021-12-03T15:40:51Zcvss (Kirill Vechera)cv-c@jetware.io
<ul></ul><p>There's also a frequent similar problem with <code>#find</code> when you need to find the first matched value instead of entry. But since it involves two semantically different code parts, it a bit more complex and cannot be implemented nicely with the same approach:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">find_with_value</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="p">(</span><span class="n">size</span> <span class="o">=</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span><span class="p">).</span><span class="nf">even?</span> <span class="o">&&</span> <span class="n">size</span> <span class="p">}</span> <span class="c1"># => ['fg', 2]</span>
</code></pre>
<p>We can create some lazy mapping enumerator providing pairs <code>[entry, value]</code>, which we unroll in the second chain just as much as we need and without repeating the computation of value:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">module</span> <span class="nn">Enumerable</span>
<span class="k">def</span> <span class="nf">with_map</span> <span class="o">&</span><span class="n">block</span>
<span class="n">map</span><span class="p">{</span><span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="p">[</span><span class="n">e</span><span class="p">,</span> <span class="n">block</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">e</span><span class="p">)]}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">with_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">find</span><span class="p">{</span><span class="n">_2</span><span class="p">.</span><span class="nf">even?</span><span class="p">}</span> <span class="c1"># => ['fg', 2]</span>
</code></pre>
<p>Back to <code>#min_with_value</code>, it could be written as:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">with_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">min_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:last</span><span class="p">)</span> <span class="c1"># => ['fg', 2]</span>
</code></pre>
<p>We can add some shorthand methods specific to this enumerator like these:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">with_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">min_by_value</span> <span class="c1"># => ['fg', 2]</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">with_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">min</span> <span class="c1"># => ['fg', 2] - #min is overwritten as an alias to min_by_value</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">with_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">find_by_value</span><span class="p">(</span><span class="o">&</span><span class="ss">:even?</span><span class="p">)</span> <span class="c1"># => ['fg', 2]</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">with_map</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">find</span><span class="p">(</span><span class="o">&</span><span class="ss">:even?</span><span class="p">)</span> <span class="c1"># => ['fg', 2] - #find is overwritten as an alias to find_by_value</span>
</code></pre> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=951592021-12-06T04:44:32Zkyanagi (Kouhei Yanagita)
<ul></ul><p>matz (Yukihiro Matsumoto) wrote in <a href="#note-9">#note-9</a>:</p>
<blockquote>
<p>Just for confirmation, is it OK that <code>min_with_value</code> returns only a single value?</p>
</blockquote>
<p>I think that returning a single value will suit most cases.<br>
It matches that <code>min</code> also returns a single value.<br>
But if it is considered that returning multiple values is better, I will not disagree with it.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=951602021-12-06T04:54:18Zko1 (Koichi Sasada)
<ul></ul><p>Another idea (evaluate a given block with a result):</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">class</span> <span class="nc">Enumerator</span>
<span class="k">def</span> <span class="nf">with_value</span><span class="p">(</span><span class="o">&</span><span class="n">b</span><span class="p">)</span>
<span class="n">v</span> <span class="o">=</span> <span class="n">each</span><span class="p">(</span><span class="o">&</span><span class="n">b</span><span class="p">)</span>
<span class="p">[</span><span class="n">v</span><span class="p">,</span> <span class="n">b</span><span class="p">.</span><span class="nf">call</span><span class="p">(</span><span class="n">v</span><span class="p">)]</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">pp</span> <span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_by</span><span class="p">.</span><span class="nf">with_value</span><span class="p">{</span><span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span><span class="p">}</span>
<span class="c1">#=> ["fg", 2]</span>
</code></pre>
<p>It is general for <code>..._by</code> methods. If given block has side-effect or consumes huge time, it is not acceptable, though.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=957942022-01-04T22:53:28Zshan (Shannon Skipper)
<ul></ul><p>For a single value, a "then" suffix might be nice.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span>
<span class="c1">#=> "fg"</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_by_then</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span>
<span class="c1">#=> 2</span>
</code></pre>
<p>It mirrors #min_by and #then with the same block.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_by</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">).</span><span class="nf">then</span><span class="p">(</span><span class="o">&</span><span class="ss">:size</span><span class="p">)</span>
<span class="c1">#=> 2</span>
</code></pre> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=957952022-01-05T01:05:31Zbaweaver (Brandon Weaver)keystonelemur@gmail.com
<ul></ul><p>I like the more generic suggestion from <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/17">@ko1 (Koichi Sasada)</a> on <code>with_value</code>, I had a similar idea before reading it when <a class="user active user-mention" href="https://bugs.ruby-lang.org/users/6603">@shan (Shannon Skipper)</a> pinged me about this ticket.</p>
<p>The other potential is using the <code>_map</code> suffix as is the case with <code>filter_map</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="c1"># => ['fg', 2]</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">max_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="c1"># => ['abcde', 5]</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">minmax_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="c1"># => [['fg', 2], ['abcde', 5]]</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">min_map</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="c1"># => [['fg', 2], ['hijk', 4]]</span>
<span class="sx">%w(abcde fg hijk)</span><span class="p">.</span><span class="nf">max_map</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">e</span><span class="o">|</span> <span class="n">e</span><span class="p">.</span><span class="nf">size</span> <span class="p">}</span>
<span class="c1"># => [['abcde', 5], ['hijk', 4]]</span>
</code></pre>
<p>This has precedence, though I do wonder how many functions we accumulate in Enumerable that are effectively the composition of two or more Enumerable methods.</p>
<p>More musing, and in a general sense, I'm reminded of Elixir extracting Enumerable-like methods into <code>Enum</code> and allowing potential composition and the rejected prior syntax of <code>Enumerable.:method</code> which ties into a <a href="https://dev.to/baweaver/ruby-2-7-the-pipeline-operator-1b2d" class="external">fairly old post</a> on my hopes for the also rejected pipeline operator that was proposed.</p>
<p>I may write a bit on that general idea, but I do see great value in the general concept of <code>min_with_value</code> as I myself have needed it a few times in the past.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=961362022-01-25T07:33:37Zkyanagi (Kouhei Yanagita)
<ul></ul><p>Another name proposal:</p>
<p>When I write <code>array.min_with_value { foo }</code>, I mean "minimize foo with an element in array".</p>
<p>So <code>array.minimizing { foo }</code> (and <code>maximizing</code>) may be the candidate.</p> Ruby master - Feature #18181: Introduce Enumerable#min_with_value, max_with_value, and minmax_with_valuehttps://bugs.ruby-lang.org/issues/18181?journal_id=1007872022-12-24T05:50:31Zsawa (Tsuyoshi Sawada)
<ul></ul><p>Related to <a class="issue tracker-2 status-1 priority-4 priority-default" title="Feature: `map_min`, `map_max` (Open)" href="https://bugs.ruby-lang.org/issues/17097">#17097</a></p>