https://bugs.ruby-lang.org/
https://bugs.ruby-lang.org/favicon.ico?1711330511
2013-05-18T18:53:07Z
Ruby Issue Tracking System
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=39420
2013-05-18T18:53:07Z
matz (Yukihiro Matsumoto)
matz@ruby.or.jp
<ul><li><strong>Status</strong> changed from <i>Open</i> to <i>Feedback</i></li></ul><p>Could you tell me a concrete use-case of your find_map and find_all_map?<br>
Usually a block for find/find_all gives boolean so that I personally have never wanted the return value from it.</p>
<p>Matz.</p>
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=88830
2020-11-29T23:40:57Z
modulitos (Lucas Swart)
<ul></ul><p>In Ruby 2.7, I think we can use <code>enumerable.lazy.filter_map{..}.first</code> as an equivalent for <code>.find_map{..}</code></p>
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=107324
2024-03-19T20:38:36Z
alexbarret (Alexandre Barret)
<ul></ul><p>Can we reconsider introducing <code>#find_map</code> please, especially since <code>#find_all_map</code> has been introduced as <code>#filter_map</code> in Ruby in 2.7?</p>
<p>Here are some examples</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="nb">require</span> <span class="s2">"minitest/autorun"</span>
<span class="c1"># Option 1</span>
<span class="k">def</span> <span class="nf">identifier</span><span class="p">(</span><span class="n">emails</span><span class="p">,</span> <span class="ss">pattern: </span><span class="sr">/\Ausername\+(?<identifier>[a-z|0-9]+)@domain\.com\z/i</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">emails</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span>
<span class="k">if</span> <span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">matches</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span>
<span class="k">break</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="c1"># Option 2</span>
<span class="k">def</span> <span class="nf">identifier</span><span class="p">(</span><span class="n">emails</span><span class="p">,</span> <span class="ss">pattern: </span><span class="sr">/\Ausername\+(?<identifier>[a-z|0-9]+)@domain\.com\z/i</span><span class="p">)</span>
<span class="n">matches</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">matches</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span> <span class="k">if</span> <span class="n">emails</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">TestIdentifierMethod</span> <span class="o"><</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
<span class="k">def</span> <span class="nf">test_identifier</span>
<span class="n">assert_equal</span> <span class="s1">'thecode'</span><span class="p">,</span> <span class="n">identifier</span><span class="p">(</span><span class="sx">%w[
username@domain.com
username+123@domainAcom
wrongusername+123@domain.com
username+123@wrongdomain.com
username+thecode@domain.com
]</span><span class="p">)</span>
<span class="n">assert_nil</span> <span class="n">identifier</span><span class="p">(</span><span class="sx">%w[
username@domain.com
username+123@domainAcom
wrongusername+123@domain.com
username+123@wrongdomain.com
]</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>Having a find_map would ease it a bit</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">def</span> <span class="nf">find_map</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&</span><span class="n">block</span><span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">collection</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
<span class="k">break</span> <span class="k">if</span> <span class="n">result</span> <span class="o">=</span> <span class="k">yield</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">result</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">identifier</span><span class="p">(</span><span class="n">emails</span><span class="p">,</span> <span class="ss">pattern: </span><span class="sr">/\Ausername\+(?<identifier>[a-z|0-9]+)@domain\.com\z/i</span><span class="p">)</span>
<span class="n">find_map</span><span class="p">(</span><span class="n">emails</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span>
<span class="p">(</span><span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">))</span> <span class="o">&&</span> <span class="n">matches</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>Here is a second use case</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Problem 2</span>
<span class="no">Pet</span> <span class="o">=</span> <span class="no">Struct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:name</span><span class="p">)</span>
<span class="no">Person</span> <span class="o">=</span> <span class="no">Struct</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">:name</span><span class="p">,</span> <span class="ss">:pet</span><span class="p">,</span> <span class="ss">keyword_init: </span><span class="kp">true</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">TestPetIdentitifer</span> <span class="o"><</span> <span class="no">Minitest</span><span class="o">::</span><span class="no">Test</span>
<span class="k">def</span> <span class="nf">setup</span>
<span class="vi">@some_people_with_pet</span> <span class="o">=</span> <span class="p">[</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Alex'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Olivier'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Romain'</span><span class="p">,</span> <span class="ss">pet: </span><span class="no">Pet</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'Darwin'</span><span class="p">)),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Mariano'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Sébastien'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Ben'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">)</span>
<span class="p">]</span>
<span class="vi">@people_with_no_pet</span> <span class="o">=</span> <span class="p">[</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Mariano'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Sébastien'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">),</span>
<span class="no">Person</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="ss">name: </span><span class="s1">'Ben'</span><span class="p">,</span> <span class="ss">pet: </span><span class="kp">nil</span><span class="p">)</span>
<span class="p">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">test_pet_found</span>
<span class="n">people</span> <span class="o">=</span> <span class="vi">@some_people_with_pet</span>
<span class="n">expected_pet</span> <span class="o">=</span> <span class="no">Pet</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="s1">'Darwin'</span><span class="p">)</span>
<span class="n">assert_equal</span> <span class="n">expected_pet</span><span class="p">,</span> <span class="n">people</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="o">&</span><span class="ss">:pet</span><span class="p">)</span><span class="o">&</span><span class="p">.</span><span class="nf">pet</span>
<span class="n">assert_equal</span> <span class="n">expected_pet</span><span class="p">,</span> <span class="n">find_map</span><span class="p">(</span><span class="n">people</span><span class="p">,</span> <span class="o">&</span><span class="ss">:pet</span><span class="p">)</span> <span class="c1"># -> people.find_map(&:pet)</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">test_pet_not_found</span>
<span class="n">people</span> <span class="o">=</span> <span class="vi">@people_with_no_pet</span>
<span class="n">assert_nil</span> <span class="n">people</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="o">&</span><span class="ss">:pet</span><span class="p">)</span><span class="o">&</span><span class="p">.</span><span class="nf">pet</span>
<span class="n">assert_nil</span> <span class="n">find_map</span><span class="p">(</span><span class="n">people</span><span class="p">,</span> <span class="o">&</span><span class="ss">:pet</span><span class="p">)</span> <span class="c1"># -> people.find_map(&:pet)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<p>Having <code>#find_map</code> allows these benefits</p>
<ul>
<li>The caller does not need to guard against <code>nil</code> like when <code>#find</code> returns nothing</li>
<li>
<code>#find_map</code> would be faster than <code>filter_map.first</code> or even <code>lazy.filter_map.first</code>
</li>
<li>It would add the parity with <code>filter_map</code>. <code>#find</code> is to <code>#filter</code> what <code>#find_map</code> is to <code>#filter_map</code>
</li>
</ul>
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=107326
2024-03-19T20:50:02Z
zverok (Victor Shepelev)
zverok.offline@gmail.com
<ul></ul><p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/53244">@alexbarret (Alexandre Barret)</a> There is a somewhat lesser-known trick which looks pretty close to your code:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># proposal:</span>
<span class="n">find_map</span><span class="p">(</span><span class="n">emails</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span>
<span class="p">(</span><span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">))</span> <span class="o">&&</span> <span class="n">matches</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span>
<span class="k">end</span>
<span class="c1"># a "trick"</span>
<span class="n">emails</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="n">match</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="ow">and</span> <span class="k">break</span> <span class="n">match</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span> <span class="p">}</span>
<span class="c1"># => "thecode"</span>
</code></pre>
<p>It might even be considered two tricks, depending on your point of view: the control-flow <code>and</code> allows to chain any statements to it (note it doesn't need extra parentheses after assignment), and <code>break value</code> allows to return a non-standard value from a block.</p>
<p>Not saying it is beautiful, just one more option.</p>
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=107328
2024-03-19T22:46:32Z
jeremyevans0 (Jeremy Evans)
merch-redmine@jeremyevans.net
<ul></ul><p><code>find_map</code> seems like a bad name as there is no map. map implies calling the same function over all elements in a collection, and in this case, there would be a single element (or none if nothing was found). Combining <code>find</code> and <code>then</code> seems like the simplest way now if you don't want to use <code>break</code>:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">emails</span><span class="p">.</span><span class="nf">find</span><span class="p">{</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span><span class="o">&</span><span class="p">.</span><span class="nf">then</span><span class="p">{</span> <span class="n">it</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span> <span class="p">}</span>
</code></pre>
<p>Personally, I would use the following approach is I think it is clearer:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">if</span> <span class="n">match</span> <span class="o">=</span> <span class="n">emails</span><span class="p">.</span><span class="nf">find</span><span class="p">{</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span>
<span class="n">match</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span>
<span class="k">end</span>
</code></pre>
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=107329
2024-03-19T23:08:37Z
alexbarret (Alexandre Barret)
<ul></ul><p>jeremyevans0 (Jeremy Evans) wrote in <a href="#note-5">#note-5</a>:</p>
<blockquote>
<p><code>find_map</code> seems like a bad name as there is no map. map implies calling the same function over all elements in a collection, and in this case, there would be a single element (or none if nothing was found). Combining <code>find</code> and <code>then</code> seems like the simplest way now if you don't want to use <code>break</code>:</p>
</blockquote>
<p>This is a good point. I always thought map was the action of transforming an element but it does have a implicit reference to an array or a collection.</p>
<blockquote>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">emails</span><span class="p">.</span><span class="nf">find</span><span class="p">{</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span><span class="o">&</span><span class="p">.</span><span class="nf">then</span><span class="p">{</span> <span class="n">it</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span> <span class="p">}</span>
</code></pre>
<p>Personally, I would use the following approach is I think it is clearer:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="k">if</span> <span class="n">match</span> <span class="o">=</span> <span class="n">emails</span><span class="p">.</span><span class="nf">find</span><span class="p">{</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span>
<span class="n">match</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span>
<span class="k">end</span>
</code></pre>
</blockquote>
<p>The code above doesn't work ^. <code>match</code> variable assigned is an email string not the matches found from the email.<br>
We implicitly thought that <code>find</code> was returning the matches not the item iterated over.</p>
<p><strong>EDIT: To some extent the code above kind of validate the idea of a <code>find_map</code>, the difference is that it's not applied at the same level than the code example given.</strong></p>
Ruby master - Feature #8421: add Enumerable#find_map and Enumerable#find_all_map
https://bugs.ruby-lang.org/issues/8421?journal_id=107330
2024-03-19T23:12:55Z
alexbarret (Alexandre Barret)
<ul></ul><p>zverok (Victor Shepelev) wrote in <a href="#note-4">#note-4</a>:</p>
<blockquote>
<p><a class="user active user-mention" href="https://bugs.ruby-lang.org/users/53244">@alexbarret (Alexandre Barret)</a> There is a somewhat lesser-known trick which looks pretty close to your code:</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># proposal:</span>
<span class="n">find_map</span><span class="p">(</span><span class="n">emails</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span>
<span class="p">(</span><span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">))</span> <span class="o">&&</span> <span class="n">matches</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span>
<span class="k">end</span>
<span class="c1"># a "trick"</span>
<span class="n">emails</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="n">match</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="ow">and</span> <span class="k">break</span> <span class="n">match</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span> <span class="p">}</span>
<span class="c1"># => "thecode"</span>
</code></pre>
<p>It might even be considered two tricks, depending on your point of view: the control-flow <code>and</code> allows to chain any statements to it (note it doesn't need extra parentheses after assignment), and <code>break value</code> allows to return a non-standard value from a block.</p>
<p>Not saying it is beautiful, just one more option.</p>
</blockquote>
<p>Thanks I learned something, and it makes sense thinking about it.<br>
Both <code>and</code> and <code>break</code> aren't things I'm used to using in Ruby code but I'm glad I know this trick now.<br>
Breaking with another value than the expected value returned from the enumerable method feels "smelly" but I don't think it's any worse than this.</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="c1"># Option 2</span>
<span class="k">def</span> <span class="nf">identifier</span><span class="p">(</span><span class="n">emails</span><span class="p">,</span> <span class="ss">pattern: </span><span class="sr">/\Ausername\+(?<identifier>[a-z|0-9]+)@domain\.com\z/i</span><span class="p">)</span>
<span class="n">matches</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="n">matches</span><span class="p">[</span><span class="ss">:identifier</span><span class="p">]</span> <span class="k">if</span> <span class="n">emails</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="n">matches</span> <span class="o">=</span> <span class="n">pattern</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<p>That would actually be what <code>#find_map</code> is the equivalent of</p>
<pre><code class="ruby syntaxhl" data-language="ruby"><span class="n">emails</span><span class="p">.</span><span class="nf">find_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="nb">method</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="p">}</span> <span class="o">==</span> <span class="n">emails</span><span class="p">.</span><span class="nf">find</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="n">result</span> <span class="o">=</span> <span class="nb">method</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="ow">and</span> <span class="k">break</span> <span class="n">result</span> <span class="p">}</span>
<span class="c1"># more equivalent than this alternative</span>
<span class="n">emails</span><span class="p">.</span><span class="nf">find_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="nb">method</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="p">}</span> <span class="o">==</span> <span class="n">emails</span><span class="p">.</span><span class="nf">lazy</span><span class="p">.</span><span class="nf">filter_map</span> <span class="p">{</span> <span class="o">|</span><span class="n">email</span><span class="o">|</span> <span class="nb">method</span><span class="p">(</span><span class="n">email</span><span class="p">)</span> <span class="p">}.</span><span class="nf">first</span>
</code></pre>