gryphon has asked for the wisdom of the Perl Monks concerning the following question:
Greetings fellow monks,
I just finished an interesting coding problem for work. We have an online test-taking & grading system, and while doing some updates for it, I needed an algorithm that renders a numeric sequence in order by letter, regardless of the numbers within the sequence. For example:
5, 100, 2, 8, 40 ...becomes... B, E, A, C, D
After some tinkering, I came up with the following:
my @original_array = qw(5, 100, 2, 8, 40);
my @sequence;
my @ordered = sort @original_array;
for (my $y = 0; $y < scalar @ordered; $y++) {
for (my $x = 0; $x < scalar @ordered; $x++) {
push @sequence, $x + 1
if ($ordered[$x] == $original_array[scalar @sequence]);
}
}
my @letter_sequence = map { local $_ = $_; tr/1-9/a-h/; $_ } @sequence
+;
I was wondering if any of you all had encountered such a problem in the past and if so if you have any code for it that's shorter than what I've posted. The above works, as far as I can tell, but I have a sneeking suspicion that there's a much better way to write it. (Oh, and the @original_array will never be bigger than 9 elements.) Thanks.
gryphon
code('Perl') || die;
•Re: Render numeric sequence as array of letters
by merlyn (Sage) on May 19, 2003 at 21:19 UTC
|
It looks like you want ordered rank.
my @original = qw(5, 100, 2, 8, 40);
my @sorted_indicies = sort { $original[$a] <=> $original[$b] } 0..$#or
+iginal;
my @rank;
@rank[@sorted_indicies] = 0..$#sorted_indicies;
my @letters = map { chr(ord('A') + $_) } @rank;
print "@letters\n";
That gives me "B E A C D", just like you want.
-- Randal L. Schwartz, Perl hacker
Be sure to read my standard disclaimer if this is a reply. | [reply] [d/l] |
Re: Render numeric sequence as array of letters
by tilly (Archbishop) on May 19, 2003 at 21:23 UTC
|
Without testing (no Perl on this machine...), I am willing to bet that your code does not work. 100 is going to sort first because sort is lexigraphical. But here is an alternate solution that should work fine even for long sequences.
my @original_array = qw(5, 100, 2, 8, 40);
# Build lookup hash
my %num2letter;
my $char = "A";
foreach my $num (sort {$a <=> $b} @original_array) {
$num2letter{$num} = $char++;
}
my @letter_sequence = @num2letter{@original_array};
(Note - if you are looping over an array searching for something, then you probably wanted a hash...)
Update: Change map to a more efficient slice as suggested by Aristotle, remove accidental space within a my.
| [reply] [d/l] |
|
Any reason not to use a slice here?
my @letter_sequence = @num2letter{@original_array};
Makeshifts last the longest. | [reply] [d/l] |
|
You are exactly right, there is no reason not to use the more efficient slice operation. I just wasn't thinking that way.
| [reply] |
Re: Render numeric sequence as array of letters (index sort + slice)
by tye (Sage) on May 19, 2003 at 21:21 UTC
|
my @data= ( 5, 100, 2, 8, 40 );
my @indices= sort { $data[$a] <=> $data[$b] } 0..$#data;
my @letters;
@letters[@indices]= ('A'..'Z');
print "@letters\n";
so long as you don't have more than 26 numbers.
- tye | [reply] [d/l] |
Re: Render numeric sequence as array of letters
by Abigail-II (Bishop) on May 19, 2003 at 22:10 UTC
|
I assume you want B E D A C as output, since
B E A C D corresponds with 100 40 5 2 8.
I'd use something similar to a Schwartzian Transfer:
my @original = qw /5 100 2 8 40/;
my $s = "A";
my @sorted = map {$_ -> [1]}
sort {$b -> [0] <=> $a -> [0]}
map {[$_ => $s ++]} @original;
Abigail | [reply] [d/l] |
|
# Tested code; outputs:
# B E A C D
my @original = (5, 100, 2, 8, 40);
my $s = 'A';
my $n = 1;
my @sorted = map { $_->[2] }
sort { $a->[1] <=> $b->[1] }
map { [ @$_, $s++ ] }
sort { $a->[0] <=> $b->[0] }
map { [ $_, $n++ ] }
@original;
print "@sorted\n";
| [reply] [d/l] |
Re: Render numeric sequence as array of letters
by HatMan (Novice) on May 20, 2003 at 02:30 UTC
|
This has caught a lot of people's imagination! Here's my offering. It's a shame the original array is called 'original_array', otherwise it could have been a lot shorter!
my @original_array = qw/ 5 100 2 8 40 /;
my @sequence;
@sequence[
sort {
$original_array[$a] <=> $original_array[$b]
}(0 .. $#original_array)
] = 'A' .. 'Z';
print "@sequence\n";
output
B E A C D
Rob
| [reply] [d/l] [select] |
Re: Render numeric sequence as array of letters
by BrowserUk (Patriarch) on May 19, 2003 at 21:24 UTC
|
sub grade{
my (%h, %i);
@h{ ('A' .. 'I')[0..$#_] } = @_;
@i{ values %h } = keys %h;
@i{ sort{ $b <=> $a } keys %i };
}
my @n=(5, 100, 2, 8, 40);
print grade @n;
B E D A C
Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"When I'm working on a problem, I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong." -Richard Buckminster Fuller
| [reply] [d/l] |
Re: Render numeric sequence as array of letters
by perlguy (Deacon) on May 19, 2003 at 21:18 UTC
|
my $count = 65; # 65 is the letter 'A' in ASCII
my @original_array = qw(5 100 2 8 40);
my %original_map = map {
$_ => chr($count++)
} sort { $a <=> $b } @original_array;
my @letter_sequence = map $original_map{$_}, @original_array;
print "@letter_sequence\n";
Update: Good call, merlyn. Here is one to eliminate the duplicates, I think:
my %seen;
my %original_map = map {
$_ => chr($count++)
} grep !$seen{$_}++,
sort { $a <=> $b } @original_array;
| [reply] [d/l] [select] |
|
| [reply] |
Re: Render numeric sequence as array of letters
by Not_a_Number (Prior) on May 20, 2003 at 17:35 UTC
|
Maybe a bit late, but there's an issue in the OP's code that nobody else has explicitly mentioned, namely that the first line:
my @original_array = qw(5, 100, 2, 8, 40);
will actually put the commas in @original_array along with the numbers themselves, as would have been pointed out if -w had been on
In this instance, it doesn’t matter much, since if I understand correctly, perl will uncomplainingly do a numerical sort on strings as long as each begins with what it interprets as a number.
In other cases, however, putting unneeded commas in a qw(list) could lead to some surprises. Try this:
my @ary = qw ( 1, foo, 2, bar5, 33 );
for ( @ary ) {
#print only the numbers:
print if /^\d+$/
}
Dave | [reply] [d/l] [select] |
Re: Render numeric sequence as array of letters
by mce (Curate) on May 20, 2003 at 13:44 UTC
|
Hi,
This is a nice question for the Perl Minigolf contests.
---------------------------
Dr. Mark Ceulemans
Senior Consultant
BMC, Belgium
| [reply] |
Re: Render numeric sequence as array of letters
by Aristotle (Chancellor) on May 21, 2003 at 12:21 UTC
|
Assuming A-Z will suffice:
my @original_array = qw(5 100 2 8 40);
my @letter_seq = do {
my %letter_for;
@letter_for{sort { $a <=> $b } @original_array} = ('A' .. 'Z');
@letter_for{@original_array};
};
Makeshifts last the longest. | [reply] [d/l] |
|
I believe this code has similar problems to my initial posting, which merlyn was quick to point out. If the @original_array contains one duplicate, for example, then the same grade is assigned for both, but one letter will be skipped in the process.
Just a heads up.
| [reply] [d/l] |
|
my @original_array = qw(5 100 2 8 8 40);
my @letter_seq = do {
my %letter_for;
@letter_for{@original_array} = ();
@letter_for{sort { $a <=> $b } keys %letter_for} = ('A' .. 'Z');
@letter_for{@original_array};
};
Makeshifts last the longest. | [reply] [d/l] |
|
|