1
\$\begingroup\$

Full text of the problem

Using names.txt (right click and 'Save Link/Target As...'), a 46K text file containing over five-thousand first names, begin by sorting it into alphabetical order. Then working out the alphabetical value for each name, multiply this value by its alphabetical position in the list to obtain a name score.

For example, when the list is sorted into alphabetical order, COLIN, which is worth 3 + 15 + 12 + 9 + 14 = 53, is the 938th name in the list. So, COLIN would obtain a score of 938 × 53 = 49714.

What is the total of all the name scores in the file?

I thought that a flowchart for Project Euler 22 would look nice, so I shook off the dust from my automatic flowchart generator (and made small modifications to it) and wrote a small script. I include the flowchart generator for convenience:

DEBUGGING = 1

class Object

    def log(previous_method='')
        if not DEBUGGING
            return
        end
        repr = if self.class == Enumerator then self.to_a else self end
        if [Array, String].include?(repr.class) and repr.length > 100
            if repr.class == String
                 repr = repr.split('') end
            repr = repr.first(30) + [". . ."] + repr.last(30)
            if self.class == String
                 repr = repr.join end

        end
        if previous_method.downcase == 'start'
            self.tap {|x| puts """
                   #{repr}
                   """}
        else
            self.tap {|x| puts """
                     | 
                     |     #{previous_method}
                     |
                     V

                   #{repr}
                   """}
         end
     end
end

def word_value(word)
    alphabet = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    word.split('')            #.log
        .map{|ch| alphabet.index(ch)} #.log
        .inject(&:+) #.log
end

File.read('eul.txt')                       .log("Readfile")
    .split(",")                            .log("Split on comma")
    .map{|w| w[1..-2]}                     .log("Remove quotes")
    .sort                                  .log("Sort lexicographically")
    .map {|w| word_value(w)}               .log("Sum of the alphabetical positions of tha chars")
    .each_with_index                       .log("Insert indexes")
    .map{|value, pos| value * (pos + 1)}   .log("Multiply word value by position")
    .inject(&:+)                           .log("Sum all")

The flowchart is:

             | 
             |     Readfile
             |
             V

           "MARY","PATRICIA","LINDA","BAR. . .,"DARELL","BRODERICK","ALONSO



             | 
             |     Split on comma
             |
             V

           ["\"MARY\"", "\"PATRICIA\"", "\"LINDA\"", "\"BARBARA\"", "\"ELIZABETH\"", "\"JENNIFER\"", "\"MARIA\"", "\"SUSAN\"", "\"MARGARET\"", "\"DOROTHY\"", "\"LISA\"", "\"NANCY\"", "\"KAREN\"", "\"BETTY\"", "\"HELEN\"", "\"SANDRA\"", "\"DONNA\"", "\"CAROL\"", "\"RUTH\"", "\"SHARON\"", "\"MICHELLE\"", "\"LAURA\"", "\"SARAH\"", "\"KIMBERLY\"", "\"DEBORAH\"", "\"JESSICA\"", "\"SHIRLEY\"", "\"CYNTHIA\"", "\"ANGELA\"", "\"MELISSA\"", ". . .", "\"KRAIG\"", "\"JERRELL\"", "\"JEROMY\"", "\"HOBERT\"", "\"CEDRICK\"", "\"ARLIE\"", "\"WINFORD\"", "\"WALLY\"", "\"LUIGI\"", "\"KENETH\"", "\"JACINTO\"", "\"GRAIG\"", "\"FRANKLYN\"", "\"EDMUNDO\"", "\"SID\"", "\"PORTER\"", "\"LEIF\"", "\"JERAMY\"", "\"BUCK\"", "\"WILLIAN\"", "\"VINCENZO\"", "\"SHON\"", "\"LYNWOOD\"", "\"JERE\"", "\"HAI\"", "\"ELDEN\"", "\"DORSEY\"", "\"DARELL\"", "\"BRODERICK\"", "\"ALONSO\n"]


             | 
             |     Remove quotes
             |
             V

           ["MARY", "PATRICIA", "LINDA", "BARBARA", "ELIZABETH", "JENNIFER", "MARIA", "SUSAN", "MARGARET", "DOROTHY", "LISA", "NANCY", "KAREN", "BETTY", "HELEN", "SANDRA", "DONNA", "CAROL", "RUTH", "SHARON", "MICHELLE", "LAURA", "SARAH", "KIMBERLY", "DEBORAH", "JESSICA", "SHIRLEY", "CYNTHIA", "ANGELA", "MELISSA", ". . .", "KRAIG", "JERRELL", "JEROMY", "HOBERT", "CEDRICK", "ARLIE", "WINFORD", "WALLY", "LUIGI", "KENETH", "JACINTO", "GRAIG", "FRANKLYN", "EDMUNDO", "SID", "PORTER", "LEIF", "JERAMY", "BUCK", "WILLIAN", "VINCENZO", "SHON", "LYNWOOD", "JERE", "HAI", "ELDEN", "DORSEY", "DARELL", "BRODERICK", "ALONSO"]


             | 
             |     Sort lexicographically
             |
             V

           ["AARON", "ABBEY", "ABBIE", "ABBY", "ABDUL", "ABE", "ABEL", "ABIGAIL", "ABRAHAM", "ABRAM", "ADA", "ADAH", "ADALBERTO", "ADALINE", "ADAM", "ADAN", "ADDIE", "ADELA", "ADELAIDA", "ADELAIDE", "ADELE", "ADELIA", "ADELINA", "ADELINE", "ADELL", "ADELLA", "ADELLE", "ADENA", "ADINA", "ADOLFO", ". . .", "ZACHARY", "ZACHERY", "ZACK", "ZACKARY", "ZADA", "ZAIDA", "ZANA", "ZANDRA", "ZANE", "ZELDA", "ZELLA", "ZELMA", "ZENA", "ZENAIDA", "ZENIA", "ZENOBIA", "ZETTA", "ZINA", "ZITA", "ZOE", "ZOFIA", "ZOILA", "ZOLA", "ZONA", "ZONIA", "ZORA", "ZORAIDA", "ZULA", "ZULEMA", "ZULMA"]


             | 
             |     Sum of the alphabetical positions of tha chars
             |
             V

           [49, 35, 19, 30, 40, 8, 20, 41, 44, 35, 6, 14, 78, 46, 19, 20, 23, 23, 37, 41, 27, 32, 46, 50, 34, 35, 39, 25, 29, 53, ". . .", 82, 86, 41, 85, 32, 41, 42, 64, 46, 48, 56, 57, 46, 60, 55, 72, 72, 50, 56, 46, 57, 63, 54, 56, 65, 60, 74, 60, 78, 73]


             | 
             |     Insert indexes
             |
             V

           [[49, 0], [35, 1], [19, 2], [30, 3], [40, 4], [8, 5], [20, 6], [41, 7], [44, 8], [35, 9], [6, 10], [14, 11], [78, 12], [46, 13], [19, 14], [20, 15], [23, 16], [23, 17], [37, 18], [41, 19], [27, 20], [32, 21], [46, 22], [50, 23], [34, 24], [35, 25], [39, 26], [25, 27], [29, 28], [53, 29], ". . .", [82, 5133], [86, 5134], [41, 5135], [85, 5136], [32, 5137], [41, 5138], [42, 5139], [64, 5140], [46, 5141], [48, 5142], [56, 5143], [57, 5144], [46, 5145], [60, 5146], [55, 5147], [72, 5148], [72, 5149], [50, 5150], [56, 5151], [46, 5152], [57, 5153], [63, 5154], [54, 5155], [56, 5156], [65, 5157], [60, 5158], [74, 5159], [60, 5160], [78, 5161], [73, 5162]]


             | 
             |     Multiply word value by position
             |
             V

           [49, 70, 57, 120, 200, 48, 140, 328, 396, 350, 66, 168, 1014, 644, 285, 320, 391, 414, 703, 820, 567, 704, 1058, 1200, 850, 910, 1053, 700, 841, 1590, ". . .", 420988, 441610, 210576, 436645, 164416, 210699, 215880, 329024, 236532, 246864, 288064, 293265, 236716, 308820, 283140, 370728, 370800, 257550, 288512, 237038, 293778, 324765, 278424, 288792, 335270, 309540, 381840, 309660, 402636, 376899]


             | 
             |     Sum all
             |
             V

           871198282
\$\endgroup\$
  • \$\begingroup\$ Please include a summary of the challenge being solved. \$\endgroup\$ – 200_success May 12 '15 at 5:49
2
\$\begingroup\$

In the following, I'll refer to Rubocop's Ruby Style Guide a lot, simply because it's pretty comprehensive, and mostly has me nodding in agreement.

All that being said, Ruby is flexible, and your style obviously works. But because of that flexibility Ruby coders are also really big on conventions and keeping style consistent. There's a pretty unified and fixed notion of what's the "correct" style to use, and the style guide I linked to is a good reference. You're free to disagree of course, just know that you'll probably be going against the opinions of the vast majority of Rubyists.

As for the code itself:

  • Better to use Object#kind_of? than comparing class to class directly, as #kind_of? will also match a subclass against its super-classes. Presuming your objects adhere to the Liskov substitution principle (as they certainly should), it's the right thing to do.

  • But alternatively, you could extend String and Array with methods to return truncated descriptions. That way, your #log method won't have to mess around with class checks at all. Call the method, say, #truncated_description because there's no reason to be cryptic. For Array it might be:

    class Array
      def truncated_description
        return inspect if count <= 40
        (first(20) + ['...'] + last(20)).inspect
      end
    end
    

    You can do something similar for String or perhaps Enumerable. Point is, it'll let you get the description to log by saying

    obj.respond_to?(:truncated_description) ? obj.truncated_description : obj.to_s
    
  • I'm not a fan of repr. I imagine it's short for representation, but overly-shortened variable names isn't very Rubyesque, and it took me a moment too long to figure out what it might be.

  • The #log method is pretty long for a Ruby method. Some of it is multiline strings, true, but still. Aim for short methods. Like, really short ones. It helps/forces you to factor your code into methods that do one thing, and only one thing. Of course, you can't always get away with it, and here, where you're extending an existing class, you don't want to pollute that class with tons of methods. I consider that last point a good reason to condone longer methods. Still, you can save some lines.

  • You don't need #tap; you're already extending Object, so you can just return self from your method, and it's functionally the same.

  • However, I'd be tempted to not extend Object at all (it's a pretty big deal to monkeypatch the base class, and not to done lightly), and instead use #tap to send stuff on to a logging method. Or even a proc. E.g.:

    flow_logger = -> (obj, message = nil) do
      if message
        puts <<-END.gsub(/^\s*\|/, '')
          |
          |  |
          |  | #{message}
          |  |
          |  V
          |
        END
      end
      p obj
    end
    
    (0...10).tap { |obj| flow_logger[obj] }
      .to_a.tap { |obj| flow_logger[obj, "to_a"] }
      .map { |n| n % 3 == 0 }.tap { |obj| flow_logger[obj, "divisible by 3"] }
      .map(&:to_s).tap { |obj| flow_logger[obj, "to_s"] }
      .sort.tap { |obj| flow_logger[obj, "sort the strings"] }
      .join.tap { |obj| flow_logger[obj, "join 'em"] }
    

    Obviously, there's a lot of duplicated code there, but it's still an alternative to monkeypatching Object.

  • Note that in the above: a) I'm using p which implicitly calls #inspect on its arguments, rather than puts or string interpolation, which calls #to_s, and b) I'm using heredocs, and c) I'm trimming the heredoc output.

There are more things I could suggest, and just plain many ways to do things. But I'll stop here. Hopefully you can use the above for something.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.