DEV Community

Vishal
Vishal

Posted on • Edited on

Collections in Ruby

Ruby’s Enumerable provides a way to traverse, search, sort and transform Collection objects like Hash and Array . It’s an elegant process to manipulate your collections and one of the best features of Ruby.

each, each_with_index, map

These methods are more commonly used to iterate and transform a collection. each and each_with_index as the name suggests will iterate through every element of the array and map will transform the collection into another collection as stated within the block.

Suppose we have an array arr = ['a', 'b', 'c', 'd']

And we want to transform the array to hold the upper case of the alphabet and it’s index, how we do go about achieving this:

    # desired output = ['A:0', 'B:1', 'C:2', 'D:3']

    arr.each_with_index do |char, index|
      char.upcase + ':' + index.to_s
    end
    => ["a", "b", "c", "d"]

That didn’t work quite as expected. As you see, the result just spit out the original array without doing any manipulation. This is where map is useful where any action within the each block will manipulate the collection.

    arr2 = arr.each_with_index.map do |char, index|
      char.upcase + ':' + index.to_s
    end
    => ["A:0", "B:1", "C:2", "D:3"]

map will iterate through every element of the array and add the result to the output. map is perfect for arithmetic operations like finding the square of all numbers: (1..4).map { |i| i * i } #=> [1, 4, 9, 16]

select, reduce, inject, each_with_object

These methods usually fold one collection into another based on the criteria within the code block.

select is normally used to filter a collection. For example:

    # select number of pencils with a count more than 2

    pencils = { 'blue' => 2, 'red' => 3, 'yellow' => 1, 'green' => 2 }
    pencils.select { |key, value| value > 2 }
    => {'red'=>3}

    # select only positive numbers from an Array (shorthand)

    [-2, -1, 0, 1, 2].select(&:positive?)
    => [1, 2]

reduce or inject as the documentation suggests combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.

Here are few commonly used operations that involve reduce

    # find sum of integers in Array

    [0, 5, 10].reduce(:+)
    => 15

    # find single integer that is not a duplicate using bitwise XOR operation

    # 1^2 = 3
    # 3^2 = 1

    [1, 2, 2].reduce(:^)
    => 1

each_with_object iterates the given block for each element with an arbitrary object given, and returns the initially given object.

    => convert an Array to Hash <=
    users = User.all
    users.each_with_object({}) do |user, dispatch|
      dispatch.merge(user.name => user.email)
    end
    => {'John Doe' => 'john@mail.com'}, {'Jane Doe' => 'jane@mail.com' }

    => Count number of occurrences <=
    nums = [1, 2, 3, 4, 4, 2, 1, 5, 3, 5]
    nums.each_with_object(Hash.new(0)) do |num, hash|
      hash[num] += 1
    end
    => {1=>2, 2=>2, 3=>2, 4=>2, 5=>2}

*_by methods

These methods are usually used to group or sort a collection. For a quick demonstration here are few examples:

    a = %w(apple banana cat)
    a.max_by(&:length)
    => "banana"

    => second longest string in an array <=
    a.max_by(2, &:length)[-1]
    => "apple"

    a.min_by(&:length)
    => "cat"

    a = %w(apple banana cat bat)

    => group by first character of string <=
    a.group_by { |w| w[0] }
    => {"a"=>["apple"], "b"=>["banana", "bat"], "c"=>["cat"]}

    a.sort_by(&:length)
    => ["cat", "bat", "apple", "banana"]

Conclusion

Out of the many Enumerable methods, each , each_with_index , map and _by methods are more frequently used in my experience. The sheer elegance and simplicity of using these methods is one reason I love programming in Ruby.

Documentation: https://docs.ruby-lang.org/en/master/Enumerable.html

Top comments (0)