Project

General

Profile

Actions

Feature #18181

open

Introduce Enumerable#min_with_value, max_with_value, and minmax_with_value

Added by kyanagi (Kouhei Yanagita) over 2 years ago. Updated over 1 year ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:105354]

Description

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:

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

  %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.)

  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.

  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.

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
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0