Ruby rocks: averaging an array of numbers to return a set number

I had a miniature programming challenge earlier today, creating an algorithm to manipulate an array of integers in Ruby. The array can be any length, but it needed to be modified to contain a maximum of, say, 5 elements. So, if the array length is less than 5 just return it, otherwise do something so reduce it to exactly 5 elements. Also, the order had to be maintained (apart from elements that have been removed).

I thought of a couple of ways of reducing the array entries: take the average of n elements of the array, or grab a element every n elements. The problem with the latter is that you lose information, whereas an average will take spikes in to account.

A colleague suggested iterating over the array, building another with chunks of the first. For example, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] would become [ [1, 2], [3, 4], [5, 6], [7, 8], [9, 10] ]. You could then iterate over the new array and average the contents, giving [1.5, 3.5, 5.5, 7.5, 9.5].

This sounded good, so I then set about writing this in Ruby. I'm coming from a PHP background, so I'm not 100% familiar with Ruby's Array methods (in fact, arrays having methods at all is just fantastic). So this is roughly what I started with:

def whittle(numbers, limit)
  return if numbers.length < limit
  chunk_length = (numbers.length / limit.to_f).ceil
  pointer = 0

  chunked = numbers.each_with_object([]) do |e, c|
    if c[pointer] && c[pointer].length == chunk_length
      pointer += 1
    end
    c[pointer] ||= []
    c[pointer] << e
  end

  chunked.map { |e| e.reduce(:+) / e.length.to_f }
end

limit = 5
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
whittle(numbers, limit) # => [1.5, 3.5, 5.5, 7.5, 9.5]

I calculated the size of each chunk, iterated over the numbers and built up a chunked array. I then mapped that array to a new one by averaging each chunk (with reduce(), divided by the chunk length).

This worked, but I had a feeling there was a better way. And this is Ruby: there's always a better way. I did a little Googlification, and found out about each_slice(), which chunks an array for you. The code ended up as this:

def whittle(numbers, limit)
  return if numbers.length < limit
  chunk_length = (numbers.length / limit.to_f).ceil
  results = []
  numbers.each_slice(chunk_length) do |a|
    results << a.reduce(&:+) / a.length.to_f
  end
  results
end

limit = 5
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
whittle(numbers, limit) # => [1.5, 3.5, 5.5, 7.5, 9.5]

I just love Ruby: it's succinctness is beautiful. Any advances on this code?

← Previous post: Reset PostgreSQL auto increment value in Rails Next post: Finding the process ID (PID) of a command in unix →
comments powered by Disqus