1. Technology
You can opt-out at any time. Please refer to our privacy policy for contact information.

CSV Example: Sorting and Column Widths

By

You can see the complete code that goes along with this article here, or download a zip file.

The rows in the CSV are not sorted. Sally Schoolteacher wants them sorted by name. More specifically, by last then then first name, but doesn't want the names listed in Last, First format. Sally Schoolteacher likes things a certain way, and she simply won't have it any other way.

But first off, how do you sort an array of hashes to begin with? Sorting an array of integers or strings is simple, you would simply call the Array#sort method and voila, Ruby does it. But to sort anything more complex, you're going to have to learn a bit about how Array#sort (or more accurately, Enumerable#sort, since Array uses the Enumerable mixin) method works. It will use a sorting algorithm (most likely quicksort) and during that process need to compare two elements of the array. It must known if the elements are equal or if not, which one should come first in the sorted array.

This is typically accomplished by using the "spaceship operator" <=>. If the left operand is less than the right, it will return -1, if the right is less than the left, it will return 1 and if the they're equal, it will return 0. And to do the actual compare, we use the Enumerable#sort's block argument. It will take two array elements to be compared. It doesn't matter to you which of these elements it represents, just do the comparison and let the sorting algorithm worry about the rest. So, here's a quick example of how to sort an array of integer strings based on the numeric value, instead of sorting them as strings.


ary = [ "10", "7", "1", "100", "9" ]
puts ary.join(',')

ary.sort!{|a,b| a.to_i <=> b.to_i }
puts ary.join(',')

Now that you know how to sort arrays, we can apply that to our array of hashes representing our grades. In the end, we're sorting on string value of the name reversed. In other words, we split the name, reverse it, join it then compare it. This preserves the format of the name in the hash and allows us to sort it how Sally Schoolteacher wants.


# Sort names by last name, then first name
grades.sort! do|a,b|
  a['Name'].split(' ').reverse.join(' ') <=> b['Name'].split(' ').reverse.join(' ')
end

Also note that the Ruby exclamation point idiom is used here. There are two sort methods. Just plain sort will return the sorted array, and sort! will sort the array in place.

Determining the Column Widths

Sally Schoolteacher wants a nicely formatted table. She wants all the columns to line up just right, so we're going to have to calculate the maximum width for every column. This means the maximum width of both the column name in the header and of all the data in that column. To keep things organized, we'll make a hash for this. Keys are the column names, values are the column widths.

At the core of how this is done is the Enumerable#max method. This is not a difficult one to understand. Given an array (or any other Enumerable object), it will iterate over it and return the element with the highest value. So [4, 5, 10, 7].max will return 10. What's a bit more tricky is how we form the array of the widths of all of the row data.

First, take a look at the code. Note that we're forming an array and calling Enumerable#max.


# Determine column widths
column_widths = {}
columns.each do|c|
  column_widths[c] = [ c.length, *grades.map{|g| g[c].length } ].max
end

The first element of the array is the column name's width. Next, grades is mapped and splatted into the array. The Enumerable#map method is takes an array and, for each element of the array, replaces that element with the object returned by the block you passed to the map method. It might be a bit difficult to explain, perhaps it would be best to just show it.


# Adds one to every element in an array
# Returns a new array
def add_one(ary)
  ary.map do|i|
    i + 1
  end
end

a = [1,2,3,4,5]
b = add_one(a)
puts b.join(',')

# => 2,3,4,5,6

Here, we're using it to iterate over the entire list of grades, pull out the value for that column (be it a name or a grade) and get its length. We then splat the mapped array into the array we're forming. We could have also formed the array, flattened it and then called Enumerable#max.

  1. About.com
  2. Technology
  3. Ruby
  4. Beginning Ruby
  5. Strings
  6. Practical Examples
  7. CSV Example: Sorting and Column Widths

©2014 About.com. All rights reserved.