Cf8a610127d1108dfe67f673320b5fe5

I had this question recently on an interview exam and I'm looking to find the best way to do it. As you can see I have already answered the problem myself with a working solution so I'm not asking people to try and answer interview questions for me. I just am not happy with my solution and think it can probably be done better. I will copy the full text of the question below:

************

Using Ruby, define the following method
>
> def sort_csv(filename, fields, reverse = false)
>
> end
>
> Where the "filename" argument is the filename of a CSV file (example
> attached), the "fields" argument is an array of one or more fields
> which defines how the list should be sorted, and the "reverse" field
> defines the sort order (descending when reverse == false or nil,
> ascending otherwise)
>
> For example, this line of code would sort the file by last name, and
> in the case where the last name is the same, sort by birthday:
> sort_csv("/home/user/interview.csv", ["LASTNAME", "BIRTHDATE"])
>
> Eye color should by sorted in the following order: "Red", "Blue",
> "Turquoise", "Brown", "Green"
> Hair color should be sorted alphabetically but "Black" and "Brown"
> should appear next to each other in the sorted list (before Blonde)
>
> Define helper methods or include libraries as needed using only
> Ruby core libraries

ID FIRSTNAME LASTNAME HAIRCOLOR EYECOLOR BIRTHDATE POSITION
1 Maybelle Joja Black Green 8/6/1987 4
2 Tawnie Goldie Blonde Blue 5/13/1987 2
3 Ike Roydon Brown Blue 7/7/1984 4
4 Sherman Nick Red Brown 2/7/1978 3
5 Alaina Magdalen Brown Turquoise 7/18/1974 1
6 Leigh Erma Green Red 1/4/2001 2
7 Hedley Jonty Brown Green 10/27/1986 1
8 Tayler Napier Blonde Blue 8/25/1988 1
9 Freeman Lindy Brown Green 10/3/1950 2
10 Mack Elmer Blonde Green 5/24/1974 5
11 Idonea Shantae Brown Brown 1/3/1957 3
12 Ariel Mike Black Blue 11/16/1957 5
13 Elinor Erma Brown Green 10/19/1951 4
14 Johna Lillie Brown Blue 12/17/1989 3
15 Clinton Corbin Blonde Brown 5/9/1974 2
16 Rebecca Shelly Black Green 6/8/1986 1
17 Debbi Ivory Blonde Brown 5/15/1965 5
18 Roderick Paulie Brown Brown 10/17/2007 2
19 Johnie Roydon Black Blue 9/16/1951 1
20 Essence Morgana Brown Turquoise 2/12/1989 5

require 'csv'

def sort_csv(filename, fields = ["ID"], reverse = false)
  csv_data = load_csv_data(filename)

  sort_fields = get_sort_indexes(fields, csv_data['headers'])
  sorted = custom_sort(csv_data['headers'], csv_data['rows'], sort_fields)
  sorted.reverse! if reverse
  
  write_csv_file(filename, sorted, csv_data['headers'])
end

def custom_sort(csv_headers, csv_data, sort_indexes)
  eyecolor_sort = ["Red","Blue","Turquoise","Brown","Green"]
  hair_colors = csv_data.map {|c| c[csv_headers.index("HAIRCOLOR")]}.sort.uniq
  if hair_colors.include?('Brown') and hair_colors.include?('Black')
    brown = hair_colors.delete('Brown')
    hair_colors.insert(hair_colors.index("Black") + 1, brown)
  end
  
  sorted = csv_data.sort do |a,b|
    indexes = sort_indexes.dup
    
    until indexes.empty? do
      idx = indexes.shift
      if (a[idx] != b[idx]) or indexes.empty?
        if csv_headers[idx] == "EYECOLOR"
          # do eyecolor sort
          comparison = "eyecolor_sort.index(a[idx]) <=> eyecolor_sort.index(b[idx])"
        elsif csv_headers[idx] == "HAIRCOLOR"
          # do hair sort
          comparison = "hair_colors.index(a[idx]) <=> hair_colors.index(b[idx])"
        else 
          # do alpha sort
          comparison = "a[idx] <=> b[idx]"
        end
      end
      break if comparison
    end
    eval(comparison)
  end
  
  sorted
end

def load_csv_data(file)
  data = {}
  csv = CSV.open(file, 'r')
  data['headers'] = csv.shift
  data['rows'] = []
  csv.each do |row|
    data['rows'] << row
  end
  csv.close
  data
end

def write_csv_file(file, data, headers)
  output = File.open("#{file}.sorted", 'wb')
  CSV::Writer.generate(output) do |csv|
    csv << headers
    data.each do |d|
      csv << d
    end
  end
  output.close
end

def get_sort_indexes(fields, data)
  idx = []
  fields.each do |f|
    idx << data.index(f)
  end
  idx
end

Refactorings

No refactoring yet !

861f311cc4a077c439099d0e5d251e73

Wolfbyte

August 4, 2008, August 04, 2008 10:41, permalink

1 rating. Login to rate!

This version constructs an object responsible for doing the search (Sorter) from a collection of code blocks and then allows you to apply them as many times as you want. If I were continuing with it I'd like to see the build_sorter method cleaned up to allow the end user to add and remove "custom sort" routines without adding another case

require 'csv'

def sort_csv2(filename, fields=["ID"], reverse=false)
  data = CSV.read(filename)
  headers = data.shift
  sorter = build_sorter headers, fields
  data.sort! {|a,b| sorter.sort(a,b) }
  data.reverse! if reverse
  File.open( "#{filename}.sorted", "wb" ) do |output|
    CSV::Writer.generate(output) do |csv|
      [headers,*data].each {|r| csv << r }
    end
  end
end

class Sorter
  def initialize
    @sorts = []
  end
  def sort(a,b)
    @sorts.each do |s|
      r = s.call(a,b)
      return r unless r == 0
    end
    return 0
  end
  def add_sort(idx,&block)
    @sorts << Proc.new { |a,b| block.call(a[idx],b[idx])} if block_given?
    self
  end
end

def build_sorter( headers, fields )
  sorter = Sorter.new
  eye_order = %w{ Red Blue Turquoise Brown Green }
  fields.each do |field_name|
    idx = headers.index field_name
    case field_name
      when "EYECOLOR"
        sorter.add_sort(idx) do |a,b| 
          eye_order.index(a) <=> eye_order.index(b)
        end
        
      when "HAIRCOLOR"
        sorter.add_sort(idx) do |a,b| 
          ([a,b].index("Blonde") and [a,b].index("Brown")) ? b <=> a : a <=> b
        end
        
      else
        sorter.add_sort(idx) { |a,b| a <=> b }
    end
  end
  sorter
end
861f311cc4a077c439099d0e5d251e73

Wolfbyte

August 4, 2008, August 04, 2008 10:52, permalink

No rating. Login to rate!

You can't seem to delete an entry once you've made it. Frustrating

Bfec5f7d1a4aaafc5a2451be8c42d26a

macournoyer

August 4, 2008, August 04, 2008 14:38, permalink

No rating. Login to rate!

You can edit your entry using the link on the top right.
Author of original code posting can delete your entry but not commenters, just like on blogs.

861f311cc4a077c439099d0e5d251e73

Wolfbyte

August 5, 2008, August 05, 2008 01:37, permalink

No rating. Login to rate!

@macournoyer - Cool. I re-posted a minor modification to the whole 54 lines and then decided that as it was a change in a few lines of code it would make a better edit. Not an issue but just something for me to be aware of

41d1031b10ba13040b4aad73bfd52159

Craing

August 14, 2008, August 14, 2008 19:08, permalink

1 rating. Login to rate!

Hi,
buddy it really nice work done by you It really good article about the custom sort in ruby you can also find other FAQs at http://www.interviewmadeeasy.info/ruby

Thanks

Your refactoring





Format Copy from initial code

or Cancel