vikee has asked for the wisdom of the Perl Monks concerning the following question:
Hello,
I need to randomize a word, could anybody help me?
for example the word is "hotel" and I need something that
randomizes that word. ( "htleo", "otelh", "tlohe",...)
Thx
Re: Randomize word
by hv (Prior) on Sep 07, 2004 at 12:44 UTC
|
use List::Util qw/ shuffle /;
my $word = 'hotel';
my $random = join '', shuffle split //, $word;
print "$random\n";
Hugo | [reply] [d/l] |
Re: Randomize word
by tachyon (Chancellor) on Sep 07, 2004 at 12:45 UTC
|
The process you describe is a shuffle. If you don't want to use List::Util which AFAIK is not core (for <5.8 ) then you could just use a Fisher Yates shuffle like this:
sub random {
my @array = split //, shift;
for (my $i = @array; --$i; ) {
my $j = int rand ($i+1);
@array[$i,$j] = @array[$j,$i];
}
return join '', @array;
}
print random('hello');
UpdateAristotle notes that List::Util is core from 5.8.
| [reply] [d/l] |
|
If you're going to roll your own shuffle, there's no reason you need to work on an array:
sub rand2 {
my $foo = shift;
for (my $i = length($foo); --$i; ) {
my $j = int rand ($i+1);
(substr($foo, $i, 1), substr($foo, $j, 1)) = (substr($foo, $j,
+ 1), substr($foo, $i, 1))
}
return $foo;
}
But then, there's no strong argument for shuffling in-place, either:
sub rand3 {
my $foo = shift;
my $bar = '';
while (length $foo) {
$bar .= substr($foo, rand(length $foo), 1, '');
}
return $bar;
}
Benchmark results:
Rate rand1 rand2 rand3
rand1 2928/s -- -27% -78%
rand2 4008/s 37% -- -70%
rand3 13282/s 354% 231% --
Update: Benchmarked BrowserUK's shuffleword; it was about half as fast as rand3.
Caution: Contents may have been coded under pressure.
| [reply] [d/l] [select] |
|
| [reply] |
Re: Randomize word
by BrowserUk (Patriarch) on Sep 07, 2004 at 13:56 UTC
|
Why expend all those cycles breaking the string into and array and then more cycles to stick'em all back together again :) (It's even fair!)
#! perl -slw
use strict;
sub shuffleWord {
my( $l, $p ) = length( $_[0] )-1;
$p = int rand $l - $_ and
substr( $_[ 0 ], $_ , 1 ) ^=
substr( $_[ 0 ], $_ + $p, 1 ) ^=
substr( $_[ 0 ], $_ , 1 ) ^=
substr( $_[ 0 ], $_ + $p, 1 ) for 0 .. $l;
return $_[ 0 ];
}
print shuffleWord $ARGV[ 0 ];
__END__
P:\test>test.pl antidisastablishmentarismmania
teinisasdrslaathiaismmtabnnima
P:\test>test.pl antidisastablishmentarismmania
ibsrniitmsaaadnnaealttiimsmhsa
P:\test>test.pl antidisastablishmentarismmania
iiarsshaminaaittlmtnbasmsednia
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
| [reply] [d/l] |
|
Last character never get changed
| [reply] |
|
sub shuffleWord {
my( $l, $p ) = length( $_[0] );
$p = int rand $l - $_ and
substr( $_[ 0 ], $_ , 1 ) ^=
substr( $_[ 0 ], $_ + $p, 1 ) ^=
substr( $_[ 0 ], $_ , 1 ) ^=
substr( $_[ 0 ], $_ + $p, 1 ) for 0 .. $l;
return $_[ 0 ];
}
With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
| [reply] [d/l] |
Re: Randomize word
by borisz (Canon) on Sep 07, 2004 at 12:51 UTC
|
use List::Util qw/shuffle/;
my @w = split //, 'hotel';
for ( 1 .. 10 ) {
print shuffle(@w), "\n";
}
| [reply] [d/l] |
Re: Randomize word
by Roy Johnson (Monsignor) on Sep 07, 2004 at 14:08 UTC
|
With a sufficiently recent Perl, you can let a hash randomize for you:
$_='hotel';
%f=map {$_=>1} split //;
print join('',keys %f), "\n";
If your Perl isn't sufficiently recent, it will yield the same result every run. Note: this is just fun-hack solution, not a real, recommended one.
Update: I cannot imagine how I didn't notice what Anonymous Monk did. Make that:
$_='rotohotel';
my $len=0;
my %f=map {$len++ => $_} split //;
print join('', values %f), "\n";
Caution: Contents may have been coded under pressure.
| [reply] [d/l] [select] |
|
That wouldn't work if the word to shuffle contains duplicate letters.
| [reply] |
|
Look again. Duplicate letters work.
However, ActivePerl v5.6.1 build 633 actually returns unshuffled results for "rotohotel", "r1t2h3tel" and "r1t2h3tels"!! Nothing guarantees that order is lost in a hash.
May I recommend a variation:
$_='rotohotel';
my $len;
my %f=map {rand() . '|' . $len++ => $_} split //;
print join('', values %f), "\n";
It seems values() is implemented as map { $hash{$_} } sort keys %hash) on this build!!
Update: ok, so it's not always sorted:
%hash = map { $_ => $_ } qw(m i f e z l x v b r d o h y j q s t k n g
+c a p w u);
print(join('', values(%hash)), "\n");
# prints: abcdefghijklmnopqrstuvwxyz SORTED
%hash = map { $_ => $_ } qw(m i f e z l x 2 v b r d o h y j q s t k n
+g c a p w u);
print(join('', values(%hash)), "\n");
# prints: abcdefghijklmnop2qrstuvwxyz NOT SORTED
%hash = map { $_ => $_ } qw(6 3 5 0 1 2 9 8 7 4);
print(join('', values(%hash)), "\n");
# prints: 0123456789 SORTED
%hash = map { $_ => $_ } qw(6 3 5 0 34 1 2 33 9 8 7 4);
print(join(':', values(%hash)), "\n");
# prints: 0:1:2:3:4:5:6:7:8:9:33:34 SORTED
%hash = map { $_ => $_ } qw(a aaaaaaa aaaa aaa aa aaaaa aaaaaaaaaa);
print(join(':', values(%hash)), "\n");
# prints: aa:aaaaaaa:a:aaaaaaaaaa:aaaaa:aaaa:aaa NOT SORTED
| [reply] [d/l] [select] |
Re: Randomize word
by Nkuvu (Priest) on Sep 07, 2004 at 12:57 UTC
|
In the spirit of TIMTOWTDI (mostly because I'm not sure if List::Util is in the core distribution), I offer this snippet of code:
#!/usr/bin/perl
use strict;
my @word_array = split '', "iguana";
fisher_yates_shuffle(\@word_array);
print "Word is ", join ('', @word_array), "\n";
# From perldoc -q shuffle:
sub fisher_yates_shuffle {
my $deck = shift; # $deck is a reference to an array
my $i = @$deck;
while ($i--) {
my $j = int rand ($i+1);
@$deck[$i,$j] = @$deck[$j,$i];
}
}
I would like to know if there's a simple way to remove the extraneous @word_array variable. Maybe I'm just overtired, but I couldn't see a way to get an array reference from the return of the split... | [reply] [d/l] |
|
print "Word is: ", join '',
@{ fisher_yates_shuffle( [ split '', "iguana" ] ) };
Of course, you'd have to return the reference passed in though.
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail
"Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
| [reply] [d/l] |
|
You are correct on the tired bit. And thanks for the correct syntax -- must have been a mental block on this when I was looking at it earlier.
I do remember trying bizarre things like @( split '', "iguana") and I'm not sure why I thought that would work. I suppose I should just stick to my original overtired excuse. I was, after all, looking at the sunrise from the wrong side (as in, I was up all night, and went to bed shortly after posting this).
| [reply] [d/l] |
|
Re: Randomize word
by Anonymous Monk on Sep 07, 2004 at 12:50 UTC
|
$ perl -MList::Util=shuffle -wle 'print join "", shuffle split //, shi
+ft' hotel
othle
| [reply] [d/l] |
Re: Randomize word
by zdog (Priest) on Sep 12, 2004 at 07:17 UTC
|
If you only need one randomization, here's a solution I came up with:
my $word = "whatever";
print join '', sort { rand() <=> rand() } split //, $word;
Zenon Zabinski | zdog | zdog@perlmonk.org
| [reply] [d/l] |
|
$ perl -we'use Data::Dumper; ++$Data::Dumper::Sortkeys; ++$count{join
+"", sort \
{rand()<=>rand()}split //, "abcde"} for 1..100000; print Dumper \%coun
+t;'|less
showed a very uneven distribution, though I couldn't make out what the determining factors were. Compare to:
$ perl -we'use Data::Dumper; ++$Data::Dumper::Sortkeys; use List::Util
+ "shuffle"; \
++$count{join "", shuffle split //, "abcde"} for 1..100000; print Dump
+er \%count;'|less
| [reply] [d/l] [select] |
|
Good looking out: obviously not optimal. I tried to figure out for myself what was going wrong.
First, I simplified the code itself a little further:
print join "", sort { rand() <=> 0.5 } split //, "abcde";
That does basically the same thing, except calls rand() once less per comparison.
Staring at it didn't do much good for me so I came up with a little test script of my own:
use strict;
use warnings;
sub compare {
my ($a, $b) = @_;
my $rand = rand();
print "$a: $rand; $b: 0.5\n";
return $rand <=> 0.5;
}
print join "", sort { compare($a, $b) } split //, "abcde";
The output of which was something like:
$ perl test.pl
a: 0.876941476789401; b: 0.5
c: 0.0768385185833438; d: 0.5
b: 0.203632482365844; c: 0.5
c: 0.234952540459695; a: 0.5
a: 0.746254431212112; d: 0.5
b: 0.648884088019244; e: 0.5
ebcda
After running this a few times and looking at the results, it hit me. This is going to be a rough explanation, but I'll give it a shot. After comparing the first four letters to one another it selects the "smallest" one (the one with the smallest rand() anyway) and compares it to the final letter: 'e' in this case. Because 'e' is the last to be compared (in the initial go, at least) and because the rand() number is regenerated at each comparison, it always has roughly a 50% chance of being the "smallest" since it has to only pass a single test to be the "smallest". The letters in front of it must not only pass their first test, but will always have to be compared to the letters following it, thus reducing their chances of being the "smallest".
In this way, my "solution" favors the letters towards the end and is more likely to stick them in front. That's why the distribution is so uneven. If you follow sort()'s algorithm ( quicksort, I believe (C's qsort(), I think ) ) through, it'll make sense.
I hope that makes some sense ...
Zenon Zabinski | zdog | zdog@perlmonk.org
| [reply] [d/l] [select] |
|
|
A reply falls below the community's threshold of quality. You may see it by logging in. |
|
|