jirutka/corefines

View on GitHub
lib/corefines/enumerable.rb

Summary

Maintainability
A
55 mins
Test Coverage
require 'corefines/support/alias_submodules'
require 'corefines/support/classes_including_module'

module Corefines
  module Enumerable

    ##
    # @!method index_by
    #   Convert enumerable into a Hash, iterating over each element where the
    #   provided block must return the key to be used to map to the value.
    #
    #   It's similar to {::Enumerable#group_by}, but when two elements
    #   corresponds to the same key, then only the last one is preserved in the
    #   resulting Hash.
    #
    #   @example
    #     people.index_by(&:login)
    #     => { "flynn" => <Person @login="flynn">, "bradley" => <Person @login="bradley">, ...}
    #
    #   @example
    #     people.index_by.each(&:login)
    #     => { "flynn" => <Person @login="flynn">, "bradley" => <Person @login="bradley">, ...}
    #
    #   @yield [obj] gives each element to the block.
    #   @yieldreturn the key to be used to map to the value.
    #   @return [Hash]
    #
    module IndexBy
      Support.classes_including_module(::Enumerable) do |klass|

        refine klass do
          def index_by
            ::Hash[map { |elem| [ yield(elem), elem ] }]
          end
        end
      end
    end

    ##
    # @!method many?
    #   Returns +true+ if the enumerable has more than one element.
    #
    #   This method is functionally equivalent to <tt>enum.to_a.size > 1</tt>,
    #   or <tt>enum.select { ... }.length > 1</tt> when the block is given.
    #
    #   @example
    #     [1].many?             # => false
    #     [1, 2].many?          # => true
    #     [1, 2, 3].many?       # => true
    #     [1, nil].many?        # => true
    #     [1, 2, 3].many? { |n| n > 2 }  # => false
    #
    #   @overload many?
    #     @return [Boolean] +true+ if the enumerable has more than one element.
    #
    #   @overload many?(&block)
    #     @yield [obj] gives each element to the block.
    #     @return [Boolean] +true+ if the block returns a truthy value (i.e.
    #       other than +nil+ and +false+) more than once.
    #
    module Many
      Support.classes_including_module(::Enumerable) do |klass|

        refine klass do
          def many?
            cnt = 0
            if block_given?
              any? do |element|
                cnt += 1 if yield element
                cnt > 1
              end
            else
              any? { (cnt += 1) > 1 }
            end
          end
        end
      end
    end

    ##
    # @!method map_by(&block)
    #   Converts enumerable into a Hash, iterating over each element where the
    #   provided block must return an array of two elements: a key and a value
    #   to be added into an array that corresponds to that key.
    #
    #   It's similar to {::Enumerable#group_by}, but allows to map the value
    #   being added into a group.
    #
    #   @example
    #     a = [1, 2, 3, 4, 5]
    #     a.map_by { |e| [e % 2, e + 1] }
    #     #=> { 0 => [3, 5], 1 => [2, 4, 6] }
    #
    #   @example
    #     h = { "Lucy" => 86, "Ruby" => 98, "Drew" => 94, "Lisa" => 54 }
    #     h.map_by { |k, v| [score(v), k] }
    #     #=> { "A" => ["Ruby", "Drew"], "B" => ["Lucy"], "F" => ["Lisa"] }
    #
    #   @yield [obj] gives each element to the block.
    #   @yieldreturn [Array] an array with the key and the value.
    #   @return [Hash]
    #
    module MapBy
      Support.classes_including_module(::Enumerable) do |klass|

        refine klass do
          def map_by
            res = {}
            each do |e|
              k, v = yield(*e)
              (res[k] ||= []) << v
            end
            res
          end
        end
      end
    end

    ##
    # @!method map_send(method_name, *args, &block)
    #   Sends a message to each element and collects the result.
    #
    #   @example
    #     [1, 2, 3].map_send(:+, 3) #=> [4, 5, 6]
    #
    #   @param method_name [Symbol] name of the method to call.
    #   @param args arguments to pass to the method.
    #   @param block [Proc] block to pass to the method.
    #   @return [Enumerable]
    #
    module MapSend
      Support.classes_including_module(::Enumerable) do |klass|

        refine klass do
          def map_send(method_name, *args, &block)
            map { |e| e.__send__(method_name, *args, &block) }
          end
        end
      end
    end

    ##
    # @!method map_to(klass)
    #   Maps each element of this _enum_ into the _klass_ via constructor.
    #
    #   @example
    #     ['/tmp', '/var/tmp'].map_to(Pathname) # => [#<Pathname:/tmp>, #<Pathname:/var/tmp>]
    #
    #   @param klass [#new] the klass to map each element to.
    #   @return [Enumerable] a new array with instances of the _klass_ for
    #     every element in _enum_.
    #
    module MapTo
      Support.classes_including_module(::Enumerable) do |klass|

        refine klass do
          def map_to(klass)
            map { |e| klass.new(e) }
          end
        end
      end
    end

    include Support::AliasSubmodules

    class << self
      alias_method :many?, :many
    end
  end
end