Chinaunix首页 | 论坛 | 博客
  • 博客访问: 20139
  • 博文数量: 3
  • 博客积分: 102
  • 博客等级: 民兵
  • 技术积分: 40
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-03 17:00
文章分类
文章存档

2012年(1)

2011年(2)

分类: Python/Ruby

2012-05-19 11:16:40

看到这篇文章挺好,所以转来,以防将来找不到了.
转:
The Josephus Problem

What is the Josephus problem? To quote from Concepts, Techniques, and Models of Computer Programming (a daunting title if ever there was one):

Flavius Josephus was a roman historian of Jewish origin. During the Jewish-Roman wars of the first century AD, he was in a cave with fellow soldiers, 40 men in all, surrounded by enemy Roman troops. They decided to commit suicide by standing in a ring and counting off each third man. Each man so designated was to commit suicide...Josephus, not wanting to die, managed to place himself in the position of the last survivor.

In the general version of the problem, there are n soldiers numbered from 1 to n and each k-th soldier will be eliminated. The count starts from the first soldier. What is the number of the last survivor?

I decided to model this situation using objects in three different scripting languages, Perl, Ruby, and Python. The solution in each of the languages is similar. A Person class is defined, which knows whether it is alive or dead, who the next person in the circle is, and what position number it is in. There are methods to pass along a kill signal, and to create a chain of people. Either of these could have been implemented using iteration, but I wanted to give recursion a whirl, since it's tougher on the languages. Here are my results.


点击(此处)折叠或打开

  1. package Person;
  2. use overload q("") => \&to_s;

  3. # Create a new, living Person with the given position
  4. sub new {
  5.     my $invocant = shift;
  6.     my $class = ref($invocant) || $invocant;
  7.     my $pos = shift;
  8.     my $self = { "position" => $pos,
  9.                  "alive" => 1,
  10.                  "succ" => undef };
  11.     return bless($self,$class);
  12. }

  13. # Getter/Setter for successor
  14. sub succ : lvalue {
  15.     my $self = shift;
  16.     $self->{succ}
  17. }

  18. # Create a chain of people
  19. sub createChain {
  20.     my $self = shift;
  21.     my $n = shift;
  22.     return $self unless $n;
  23.     
  24.     my $succ = Person->new($self->{position}+1);
  25.     $self->succ = $succ;
  26.     $succ->createChain($n-1)
  27. }

  28. # Pass on the killing message
  29. sub circularKill {
  30.     my $self = shift;
  31.     my ($pos,$nth,$remaining)=@_;

  32.     return $self->{succ}->circularKill($pos, $nth, $remaining)
  33.         unless $self->{alive};
  34.     return $self unless $remaining > 1;
  35.     
  36.     if ($pos == $nth) {
  37.         $self->{alive} = 0;
  38.         $pos = 0;
  39.         $remaining--;
  40.     }
  41.     $self->{succ}->circularKill($pos+1, $nth, $remaining)
  42. }

  43. # Print descriptive information
  44. sub to_s{
  45.     my $self = shift;
  46.     "Person #".$self->{position}.", ".($self->{alive} ? "alive" : "dead")
  47. }

  48. # Circle of $n people, kill every one out of every $m
  49. $m = 3;
  50. $n = 40;

  51. $first = new Person(1);
  52. $last = $first->createChain($n-1);
  53. $last->succ = $first;

  54. $winner = $first->circularKill(1,$m,$n);
  55. print "Winner: ", $winner, "\n";

What's good:

  • Support for statement modifiers (ie, the 'if' or 'unless' after a line
  • Last evaluated expression is assumed to be the return value (look at sub succ)
  • Once the class is actually defined, everything seems fairly clean
  • It runs, quickly, and gets the right answer
What's bad:
  • It looks ugly as hell, and feels like a hack. Look at the new routine! Without the help of Programming Perl (aka The Camel), I would have been clueless how to write this.
  • Also under the "it's a hack" heading, I don't like how each subroutine begins by shifting $self off of the arguments stack. This seems unnatural.
  • Overloading the "stringification" operator was a little roundabout (look at the use overload line. Again, this felt unnatural, and I wouldn't have had a clue how to do it without The Camel.

So, in conclusion, defining classes in Perl is decidedly inelegant, and unintuitive. If I were to do it often, I'd have to cut and paste that new routine wherever I went. That's a BIG stumbling block, and it would probably be enough to keep me from using OO in Perl. In fact, it has been for the past several years.



点击(此处)折叠或打开


  1. 点击(此处)折叠或打开

    1. class Person
    2.     attr_reader :position, :succ, :alive
    3.     attr_writer :position, :succ, :alive
    4.     
    5.     # Everyone is alive, initially
    6.     def initialize(pos)
    7.         @position = pos
    8.         @alive = true
    9.     end
    10.     
    11.     # For creating a linked chain of people
    12.     def createChain(n)
    13.         return self unless n>0
    14.         @succ = Person.new(@position + 1)
    15.         @succ.createChain(n-1)
    16.     end
    17.     
    18.     # Kill every nth person
    19.     # Current position in the cycle is pos
    20.     # there are remaining people remaining
    21.     # Stop killing if we're the last one.
    22.     def kill(pos,nth,remaining)
    23.         return @succ.kill(pos,nth,remaining) if !@alive
    24.         return self if (remaining == 1)
    25.         
    26.         if pos == nth
    27.             @alive = false
    28.             puts self
    29.             pos = 0
    30.             remaining-=1
    31.         end
    32.         @succ.kill(pos+1,nth,remaining)
    33.     end
    34.     
    35.     # Information about this person
    36.     def to_s
    37.         "Person \##@position, #{@alive ? 'alive' : 'dead'}"
    38.     end
    39. end

    40. # Set n to anything much higher (like 10, say)
    41. # And the program hangs, or has an "Illegal Instruction"
    42. n = 7

    43. first = Person.new(1)
    44. last = first.createChain(n-1)
    45. last.succ = first

    46. winner = first.kill(1,3,n)
    47. # If I use puts "Winner: " + winner, I get an error:
    48. # in `+': failed to convert Person into String (TypeError)
    49. #puts "Winner: " + winner
    50. puts "Winner: ", winner



I wanted to do some OO however, so I checked out Python and Ruby. Here's the same problem coded using each of them.What's good:

  • Since this was my first Ruby script, I can't claim to have written good, idiomatic code, but it sure looks nice to me. It's far more elegant than the Perl mess, and significantly shorter as well.
  • I like the attr_reader and attr_writer shortcuts.
  • "stringification" overloading was pretty simple, especially since this is done frequently in the online reference.
  • As in Perl, there are statement modifiers and the last statement is the return value, a fact which I used in most of these routines.
  • I like the flexible interpolation via #{}
What's bad:
  • While the code looks great, the execution sucks. Ruby's limit on stack depth seems to be set somewhere around 60, which is absurdly low. This clearly prevents setting n particularly high. While n=40 worked in both Perl and Python, Ruby gives an "Illegal Instruction" error or just hangs, which I eventually figured out was its way of saying that the depth limit had been reached. There may be some way around it, but this limitation seems pretty atrocious.
  • When there's an error in a Ruby program, the error messages tend to be pretty useless, usually along the lines of "There's an error in line x", if that. When I had n set at 40, I'd just get an "Illegal Instruction" error, which was incredibly misleading. Setting the --debug flag didn't help in this department.
  • Also, and I may just be missing something here, puts "Winner: " + winner told me that it couldn't convert a Person into a String, which it clearly could, since puts winner worked fine.

So in conclusion, I really liked coding in Ruby, but the execution just wasn't there. If there are any Ruby fans out there who know how to fix the problems I mentioned, I'd be thrilled to hear from you.


点击(此处)折叠或打开

  1. class Person:
  2.     def __init__(self,pos):
  3.         self.pos = pos
  4.         self.alive = 1
  5.     def __str__(self):
  6.         return "Person #%d, %s" % (self.pos, self.alive)
  7.     
  8.     # Creates a chain of linked people
  9.     # Returns the last one in the chain
  10.     def createChain(self,n):
  11.         if n>0:
  12.             self.succ = Person(self.pos+1)
  13.             return self.succ.createChain(n-1)
  14.         else:
  15.             return self

  16.     # Kills in a circle, getting every nth living person
  17.     # When there is only one remaining, the lone survivor is returned
  18.     def kill(self,pos,nth,remaining):
  19.         if self.alive == 0: return self.succ.kill(pos,nth,remaining)
  20.         if remaining == 1: return self
  21.         if pos == nth:
  22.             self.alive = 0
  23.             pos=0
  24.             remaining-=1
  25.         return self.succ.kill(pos+1,nth,remaining)

  26. # n people in a circle
  27. # kill every mth person
  28. n = 40
  29. m = 3

  30. first = Person(1)
  31. last = first.createChain(n-1)
  32. last.succ = first

  33. print "In a circle of %d people, killing number %d" % (n,m)
  34. winner = first.kill(1,m,n)
  35. print "Winner: ", winner

What's good:

  • It's very compact (shortest of the three), mostly because of the lack of lines to end blocks (ie, "end" in Ruby or "}" in Perl). Not having these lines does feel a little weird, but I think I could get used to it.
  • I like the printf-style formatting via the % operator. I can't say whether I like it more than the direct interpolation in Ruby and Perl, however.
  • Unlike in Ruby, the program ran without a hitch, and got the right answer.
What's bad:
  • __init__ and __str__? This seems ugly, though that may be part of the "never touch anything starting with __" credo coming in from my C background.
  • Passing self as the first parameter of every routine makes Python's OO seem almost as hackish as Perl's or PHP's. I much prefer Ruby's system of using @ to indicate an instance variable, rather than "self.".
  • I wish I could use tabs instead of four spaces to indicate indentation.
  • No statement modifiers, and there has to be an explicit return statement. These aren't major drawbacks, but I'd rather have them than not.

Python isn't quite as clean as Ruby, though it certainly trounces Perl. It would be hard not to trounce Perl. The performance was much better than in Ruby, however: Python ran the script for n=40 without any hitches. In the debugging department, syntax errors included helpful information, including where in the line the error occured.

Now for the comparison. First of all, I'll throw Perl right out. I love the language, but not for object-oriented programming. To write a purely procedural program I'd take it over both Ruby and Python any day of the week, but not for OO.

If I had my choice in the matter, I would use Ruby. It's syntax seems cleaner, and it's object orientation doesn't seem hackish in the least. It's performance, however, left a lot to be desired. Granted, deep recursion probably isn't the most widely used technique, but there's no reason it shouldn't work. For a different sort of problem, I'd likely choose Ruby, though I'm worried I might have to switch over to Python if I ran into similar problems.

And that brings us to the aforementioned beast. It seems to present the middle ground in this problem. It's syntax is fairly clean though, as I mentioned, I'd rather not have to type "self." all the time. But on the plus side, it could actually solve the problem without crashing.

So for this round, the winner is Python, though I really wish it had been Ruby. For most problems, I'll go with Ruby. It's more enjoyable to code in, and that's what I'm coding for--enjoyment.

阅读(2545) | 评论(2) | 转发(1) |
0

上一篇:博客已升级,请注意变更地址

下一篇:没有了

给主人留下些什么吧!~~

3783335812012-05-23 11:22:57

恩,比较清晰的比较。。。学习了!

1471893852012-05-21 21:53:54

跟Python,Ruby,PERL相比,java是不是已经过时了?