1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
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 !
Wolfbyte
August 4, 2008, August 04, 2008 10:41, permalink
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 and see if I could find a way to extract the index into the Sorter (to make the code blocks neater)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
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| p 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(&block) @sorts << block 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 do |a,b| eye_order.index(a[idx]) <=> eye_order.index(b[idx]) end when "HAIRCOLOR" sorter.add_sort do |a,b| r = [a[idx],b[idx]] if r.index("Blonde") and r.index("Brown") b[idx] <=> a[idx] else a[idx] <=> b[idx] end end else sorter.add_sort { |a,b| a[idx] <=> b[idx] } end end sorter end
Wolfbyte
August 4, 2008, August 04, 2008 10:52, permalink
You can't seem to delete an entry once you've made it. Frustrating
macournoyer
August 4, 2008, August 04, 2008 14:38, permalink
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.
Wolfbyte
August 5, 2008, August 05, 2008 01:37, permalink
@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
Craing
August 14, 2008, August 14, 2008 19:08, permalink
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
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