Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

secret code generator

by xiaoyafeng (Deacon)
on Dec 19, 2006 at 05:42 UTC ( [id://590607]=perlquestion: print w/replies, xml ) Need Help??

xiaoyafeng has asked for the wisdom of the Perl Monks concerning the following question:

Hi gurus,

I have big trouble!Last day, a python fan have claimed perl is a garbage language.python is much better than it. And raise a question to test perl's "ability":

using letters of a string,enumerate all secret code.

below is my code(I admit it's a garbage)
$letters = shift @ARGV ||"ABC"; @letter = split //,$letters; for $a (1..$#letter) { for $b (1..$#letter) { for $c (1..$#letter) { $scode = $letter[$c].$letter[$b].$letter[$a] +; print $scode."\n"; } } }
below is his code (with python)
import string DEFAULT_CHAR_SET = string.letters + string.digits + st +ring.punctuation class PasswordGenerator(object) : def __init__(self, seeds = DEFAULT_CHAR_SET) : self.seeds = seeds self.Outer = None self.cursor = 0 def next(self) : self.cursor += 1 if self.cursor == len(self.seeds) : self.cursor = 0 if not self.Outer : self.Outer = PasswordGenerator(s +elf.seeds) else : self.Outer.next() def value(self) : if self.Outer : return self.Outer.value() + self.seeds +[self.cursor] else : return self.seeds[self.cursor] def __iter__(self) : while 1 : yield self.value() self.next() if __name__ == "__main__" : g = PasswordGenerator() for i in g : print i


I'm a perl fan and zealot,but not a accomplished programmer.So my question is:
1.Who can transfer his code to perl, or tell me algorithm of his code? I don't know python at all!
2.We could write a better code with perl,couldn't we?

Thanks in advance for answer my foolish and childish question!
All responds are appreciated!

Replies are listed 'Best First'.
Re: secret code generator
by tilly (Archbishop) on Dec 19, 2006 at 06:33 UTC
    He is just creating all combinations of letters, numbers and puncuation. The obvious, but wrong, solution is to use string increment. That is wrong because you don't, for instance, get punctuation characters. Here is a silly solution in Perl.
    use Math::Fleximal; my $flex = ["a".."z", "A".."Z", 0..9, split //, qq(!"#$%&'()*+,-./:;<=>?@[\\]^_ +`{|}~)]; my $x = Math::Fleximal->new("a", $flex); my $one = Math::Fleximal->new("b", $flex); while (1) { print $x->to_str; print "\n"; $x = $x->add($one); }
    A less silly solution (ie one that doesn't push all of the work under the covers) takes just a bit more effort.
    #! /usr/bin/perl -w use strict; my @chars = ("a".."z", "A".."Z", 0..9 , split //, qq(!"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~)); my $x = 0; while (++$x) { nested_for( sub {print join "", @_, "\n";} , map \@chars, 1..$x ); } sub nested_for { ret_iter(@_)->(); } sub ret_iter { my $fn = shift; my $range = shift; my $sub = sub {$fn->($_, @_) for @$range}; return @_ ? ret_iter($sub, @_) : $sub; }
    (Astute people may notice that I borrowed from Re (tilly) 1 (perl): What Happened...(perils of porting from c).)

    Update: As johngg noted, I did not get \ in the punctuation set. Fixed.

      A simpler way to populate the @chars array.

      my @chars = map {pack q{C}, $_} 0x21 .. 0x7e;

      Cheers,

      JohnGG

        Simpler still

        my @chars = map chr, 0x21 .. 0x7e;

        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
        Simpler, yes. Easier to understand, no - unless you use ASCII codes all day long.

        Also, note BrowserUK's solution, your map can be written simply as map chr, 0x21 .. 0x7e;

        Update: BrowserUK noted himself :)

        -- Hofmator

        Code written by Hofmator and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

      wow! smart code. nest sub is a good idea!
Re: secret code generator
by BrowserUk (Patriarch) on Dec 19, 2006 at 10:21 UTC

    All that OO and recursion and an iterator seems overkill for what can be done with a straight forward iterator:

    sub gen2 { my @set = map chr,@_; my @is = 0; return sub { my $di = $#is; { if( ( $is[ $di ] ||= 0 ) == $#set ) { $is[ $di ] = 0; unshift @is, $di = 0 if --$di < 0; redo; } ++$is[ $di ]; return [ @set[ @is ] ]; } }; } my $gen2 = gen2( 33 .. 126 ); print @$_ while $_ = $gen2->();

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Why bother with a function and an anonymous function and all those nasty indices?
      use strict; my @chr = map chr, 33..126; my @stack; my $fresh; while (1) { if (not @stack) { $fresh++; } else { shift @{$stack[-1]}; if (not @{$stack[-1]}) { pop @stack; $fresh++; redo; } } push @stack, map [@chr], 1..$fresh; $fresh = 0; print map $_->[0], @stack; print "\n"; }
      Update: Fixed off by one bug that BrowserUk noticed.
        Why bother with a function

        Because it allows for code reuse:

        my $lower = gen( 97 .. 122 ); my $nums = gen( 48 .. 57 );
        an anonymous function

        Because it allows me to run multiple independent generators:

        #! perl -slw use strict; sub gen { my @set = map chr,@_; my @is = 0; return sub { my $di = $#is; { if( ( $is[ $di ] ||= 0 ) == $#set ) { $is[ $di ] = 0; unshift @is, $di = 0 if --$di < 0; redo; } ++$is[ $di ]; return [ @set[ @is ] ]; } }; } my $lower = gen( 97 .. 122 ); my $nums = gen( 48 .. 57 ); print join'', @{ $lower->() }, '-', @{ $nums->() } for 1 .. 100;
        and all those nasty indices?

        Ignoring that [-1] is an index, one reason is efficiency.

        I ran a benchmark of your original code above against my code above and it came out to be 7000% slower!

        Now, I realise that either there is a bug in the code (which can be fixed) or my attempt to tweak it so that it would self-terminate after producing a set number of iterations may have broken it. But, having spent an hour trying to figure out how it is meant to work, I failed. So I could not make the correction.

        And that bring me to the second reason. Those "nasty indices", make for a eminently understandable algorithm. The generating statement, return [ @set[ @is ] ]; is obviously an array slice using the array of indices @is to select characters from the set. The rest of the code is a simple loop incrementing the indices in sequences, and 'rolling over', when each one reaches the number of characters in the set. This is what Limbic~Region dubbed the odometer pattern.

        In your first attempt, you have

        1. a loop;
        2. calling a sub which generates an anonymous sub and a list of array references, and passes them to ...
        3. another sub which calls another sub passing the anonymous sub and the list of array references;
        4. which creates another anonymous sub that calls the first anonymous sub in a loop,
        5. and then recurses on itself passing the second anonymous sub to loop over the first anonymous sub.

        It took me nearly an hour to try and type that description, and I'm still not sure it's right. I can see no benefit in that level of complexity for any task, let alone this one. Even if it worked. And to think I get accused of posting unmaintainable code.

        When I wrapped your second attempt into a sub in order to benchmark it, it produced reams of warnings. There appears to be an out-by-one error when it is run multiple times. Possible due to my attempt to make it stop after a set number of iterations? But since I (again) couldn't work out how to correct the error, attempting to trace the contents of your @stack which seems to accumulate a lot of content, all but the first element of which you discard, defeated me. Again, unwarranted complexity.

        To my critique of the use of OO

        Some of the benefits I've attributed to my solution--multiplicity and code reuse--could also be cited as reason for using OO in the Python solution posted by the OP, but I would like to counter them.

        The class contains a constructor, 2 methods--value and next and an iterator. In order for the object state to make a transition, both the methods have to be called in sequence, which is a useless split of functionality, and is probably why it is seen as necessary to add the iterator method. But, unintuitive, the value method has to be used to get the current value before the next method is called, which means that the value method has to temporarily cache the value.

        Finally, as there is no possibility of resetting the generator, it becomes a 'use once and discard' instantiation. The only instance variables it has are those used entirely for it's own internal workings. And the only methods it has, other than the instantiator, could equally effectively be combined into a single method. This makes it (IMO, but also that of others), a "useless use of OO". OO for the sake of OO. OO dogma.

        So each of these objects is a single use object, with only internal use state and a single method for getting the results. That is an unusual, but accurate description of an iterator subroutine. And exactly what my solution provided. I don't have Python--I threw it away about a week after I installed it because I really disliked the significant whitespace aspect of it--so I couldn't benchmark that. Instead, I converted it verbatim to Perl and compared it against my solution. Even without going through the hoops of adding performance sapping accessor/mutator methods, it's about half the speed.

        More importantly, it's considerably more complex. The individual methods are simple enough, and easy enough to construct and maintain, but the complexity lies in the recursive nature of the code and data structure with one instance methods calling those of it's nested instances. There is 'hidden' state maintained in the depth of the structure--the length of the passwords being generated--along with the current state of the overall result being maintained across those multiple, nested instances. This makes it very difficult to debug or trace. Harder even that a normal recursive function.

        Imagine for instance if it was decided to limit the length of the passwords to say 8 characters so that the iterator will eventually self terminate. It becomes necessary to add a test to see how deep the chain of instances is, by chaining down the self.Outer chain until it bottoms out counting as you go. But that would also require that the depth test was only initiated from the top level instance, which is another test to be run at every iteration.

        In summary

        • I use (anonymous) subroutines to allow code reuse.
        • I use a factory subroutine to allow for multiplicity.
        • I use indices because they are easy to construct. Easy to trace. Efficient, and easy to both understand and maintain.
        • I avoided OO because it's inefficient, unnecessary and I see no benefit in using it for this application.
        • I avoided recursion because it's inefficient and harder to debug.

        I know 'efficiency' doesn't rate too highly around here, but since to generate a list of all the passwords to a maximum of 8 characters would require 5.6e15 iterations, each microsecond saved will save 177 years on the overall task! With over 50% savings relative to the (lightweight OO) version, and 7000% compared to your first attempt, these are not insignificant savings.

        Even if you ignore efficiency on the basis that no one would attempt to generate such a large number of passwords--though I see no other use for this code--(IMO), my solution wins over the others on the grounds of usability and maintainability also.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: secret code generator
by SheridanCat (Pilgrim) on Dec 19, 2006 at 06:44 UTC
    First off, getting into arguments about which language is better will get you nowhere. Both languages are popular because both allow people to get things done in the way they find useful. Language wars accomplish nothing.

    I ran the Python code and followed along. I see what it's doing, but I can't translate to Perl at the moment. It's basically doing recursion and it seems that when it finishes you'll have all the combinations of DEFAULT_CHAR_SET printed out. It's gonna run a long time.

    As for writing better code in Perl, you'd need to define what "better" means. I'm sure the Python programmer wouldn't find it better at all. That's the nature of language wars.

Re: secret code generator
by MonkOfAnotherSect (Sexton) on Dec 22, 2006 at 11:21 UTC
    </lurk>
    His Python code is not terribly pleasant:
    1/ there's absolutely no need for an object, much less repeated recursive object creation
    2/ The spacing is bizarre (though that might be the fault of your pasting)
    3/ (minor nit) self.Outer should be self.outer
    4/ His code is slooooow
    5/ I consider myself a reasonable Python programmer ;-) but trying to follow that gives me a headache
    6/ As others have noted it's rather inflexible Here's a shorter simpler faster more-flexible version plus a test to demonstrate that his version is more than 5** times slower under Py2.5!!:

    def make_combos(curr, *rest): if rest: rest = make_combos(*rest) else: rest = [""] for y in rest: for x in curr: yield y + x def password_generator(seeds=DEFAULT_CHAR_SET, minlen=1, maxlen=8): chars = [seeds]*minlen for i in range(minlen, maxlen+1): for elem in make_combos(*chars): yield elem chars.append(seeds) # insert the code for PasswordGenerator here def test_combos(): for pwd in password_generator(): yield pwd def test_generator(): for elem in PasswordGenerator(): yield elem if __name__ == "__main__": for test_type in [test_combos, test_generator]: for i in range(3): test = test_type().next start = time.time() for j in range(1000000): test() # change to "print test()" if you want output print time.time() - start, print
    [**To be fair, his version is slightly faster for smaller numbers and gets worse the larger the number of passwords required...]<lurk>
      I consider myself a reasonable Python programmer ;-) but trying to follow that gives me a headache

      Phew! And I thought it was just my lack of Python skills, and the repetitive banging of my head on the wall that was the cause :)


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://590607]
Approved by grep
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (7)
As of 2024-04-19 08:28 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found