Project

General

Profile

Feature #18181

Updated by kyanagi (Kouhei Yanagita) about 3 years ago

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]] 
 ``` 

 Corresponding to `#min(n)`, an integer argument can be passed to `#min_with_value` or `#max_with_value`. 

 ``` ruby 
   %w(abcde fg hijk).min_with_value(2) { |e| e.size } # => [['fg', 2], ['hijk', 4]] 
   %w(abcde fg hijk).max_with_value(2) { |e| e.size } # => [['abcde', 5], ['hijk', 4]] 
 ``` 

 ## 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. 

 ## Benchmark 

 https://bugs.ruby-lang.org/issues/18181#note-2 

 ## Example 

 Solving a traveling salesman problem in nearest neighbor algorithm. 

 ``` ruby 
 require 'set' 
 Point = Struct.new(:x, :y) 
 points = Set.new([Point.new(1, 1), Point.new(2, 4), Point.new(3, 3), Point.new(2, 2), Point.new(0, 1)]) 

 total = 0 
 current = points.first 
 points.delete(current) 
 path = [current] 

 until points.empty? 
   current, distance = points.min_with_value do |point| 
     Math.hypot(current.x - point.x, current.y - point.y) 
   end 
   total += distance 
   points.delete(current) 
   path << current 
 end 

 p path 
 p total 
 ``` 

Back