Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Dynamic foreach loop 3

Status
Not open for further replies.

ejaggers

Programmer
Feb 26, 2005
148
0
0
US
I have a foreach that key sorts a hash. The keys can be either all numeric or alpha-numeric.
Is there a way to not repeat the foreach, and dynamically sort? i.e.

my @sortTpye = ( ‘sort’, ’sort{$a<=>$b}’ );
foreach ( &{“$sortType[$i]”} %hash ) {

}
 
Not sure why you'd want to do this, but this works for the task you've specified:
Code:
my %hash = qw/2 21 200 2425 24 244/;

my $i = 0; # or whatever, just testing
my @sortType = ( sub { $a cmp $b }, sub { $a <=> $b } );

foreach ( sort { $sortType[$i]->() } %hash ) {
      print "$_\n";
}
 
Your question is a bit vague, but if I understand it correctly the following code will probably work. One note, if you're going to be sorting lots of values, this will probably be very slow (since it runs a regex on each value pair) but it should work.
Code:
my (%h_num, %h_alpha);
map {$h_num{$_}++} (100, 10, 1000, 1);
map {$h_alpha{$_}++} qw/h1n1 h b a/;

foreach (sort sort_alpha_or_num keys %h_num) {
	print "$_ - $h_num{$_}\n";
}

foreach (sort sort_alpha_or_num keys %h_alpha) {
	print "$_ - $h_alpha{$_}\n";
}

sub sort_alpha_or_num {
	if ("$a$b" =~ /^\d+$/) {
		return $a <=> $b;
	} else {
		return $a cmp $b;
	}
}
You'll might also want to take a look at faq219-5095.
 
Post some sample data, preferably real sample data. Is there a lot of data? A cached key sort would be better, if there isn't that much data to sort then something like rharsh posted is probably better.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
rharsh,
My question leans more toward, "Dynamic Execution". In your example you still have more than one foreach. I understand what you're saying, except I haven't seen the map before.

ishnid,
Between you and KevinADC ( I think I figured it out:
my %hash1 = (
1 => 'a',
2 => 'b',
11 => 'c',
22 => 'd',
);
my %hash2 = (
'A' => 'Z',
'B' => 'W',
'C' => 'X',
'D' => 'Y',
);
@type = qw(cmp <=>);
foreach ( sort{eval "$a $type[1] $b"} keys %hash1 ) {
print "$_\n";
}

Thanks To All!!!
 
Honestly that solution looks bad. If all the data you have to sort is those two small hashes then fine, but evaling the sort routine for each iteration of the loop has to be very inefficient. If the real data is big, that could be very slow.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
KevinADC,
Actually the hash will never have over 100 elements if that many. But my question was about dynamic execution. The actual solution I'm going with is:

@list = sort AlphaNumericSort(keys %hash);
sub AlphaNumericSort {
return ($a cmp $b) || ($a <=> $b);
}

Thanks for the tip on the inefficient eval though.
 
I'd be surprised if that worked. That'll only sort values as numeric if they are equal when compared as strings.
 
Yea, that may not sort correctly because the numeric sort only gets evaluates if the ASCII sort is equal. But if thats the behavior you need then all is fine.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
ejaggers said:
My question leans more toward, "Dynamic Execution". In your example you still have more than one foreach.
I do have more than one loop because there are multiple hashes (%h_num, %h_alpha) - are you trying to combine the keys from both hashes in one list?

ishnid and Kevin are right: the sort ($a cmp $b) || ($a <=> $b) isn't going to work correctly. It might as well just be $a cmp $b - everything will be sorted by ASCII value.
 
Everything will not be sorted by ASCII values, it will be sorted by numeric value if the ASCII values are the same. This may indeed be the desired behavior, maybe the OP will let us know if it is or not.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
Maybe I'm confused - if the ascii values are the same (the same characters) don't $a and $b need to contain the same number?

Unless I'm mistaken, both CMP and <=> would return 0.
 
oops, yes, that is correct. It would actually need to compare something different than what $a and $b currently are if they are equal. My bad, you're not confused, I was.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
KevinADC/rharsh , this seems to work because the key will always either be numeric or alpha. What case do you think it won't work?

%hash = (
1 => 'A',
11 => 'b',
2 => 'v',
3 => 'v',
'd' => 'z',
'a' => 'z',
'b' => 'z',
'e' => 'z',
);
 
Sorry, Here's the code:
%hash = (
1 => 'A',
11 => 'b',
2 => 'v',
3 => 'v',
'd' => 'z',
'a' => 'z',
'b' => 'z',
'e' => 'z',
);
my @keys = sort SortAlphaNumeric(keys %hash);
print "$_\n" foreach @keys;
sub SortAlphaNumeric {
return ($a <=> $b) || ($a cmp $b);
}
 
Earlier:
Code:
sub AlphaNumericSort {
    return ($a cmp $b) || ($a <=> $b);
}
Now:
Code:
sub SortAlphaNumeric {
    return ($a <=> $b) || ($a cmp $b);
}
It does work that way. Though I'm going to guess you're not using the strict and warning pragmas. If you were watching for warnings, sorting an alpha character with the <=> operator throws one. If you're fine with that it'll work.
 
rharsh, yes i used strict in my code but not warnings. The difference I see from earlier and now is one sorts numbers first and the other sorts alpha first. Either will work for what i'm using it for because I will have either nums or alpha in my hash key. Do you still think this code is a problem?
 
Well no, neither won't work.
Take 11 and 2 as an example:
-as strings [tt]'11' lt '2'[/tt]
-as numbers [tt]11 > 2[/tt]
You need to somehow decide whether your hash is numeric or stringy before entering the sort routine, if you want a correct behavior.
A more complex situation:
-as strings [tt]'2A' lt '2B'[/tt]
-as numbers [tt]'2A' == '2B'[/tt]
Concerning the warning issue, you can locally say [tt]no warnings;[/tt] : this will only suppress the warnings in the block in which it is placed, if the warnings are globally on.

Franco
: Online engineering calculations
: Magnetic brakes for fun rides
: Air bearing pads
 
prex1, please give me a hash where this does not work. I haven't been able to come up with any. I tried 11,2 and '11','2', in the above hash and it sorted fine. Thanks for the no warnings tip, I tried it and it's pretty cool.
 
At this point I'm not really sure we are talking the same thing. My point is shown below:
Code:
%test=(11,'a',2,'b');
print join(',',sort{($a<=>$b)||($a cmp $b)}keys%test),"\n";
print join(',',sort{($a cmp $b)||($a<=>$b)}keys%test),"\n";
The result is that you get two different orderings by reversing the tests. In other words: if with one test, e.g. string before numeric, you might get what you are expecting to (that I don't know), for sure you are not getting the same by reversing the test, so the two cannot be equivalent.
Note that it is unrelevant whether you surround the numbers by apostrophes, perl decides how to interpret them (numeric or string) based on the operation you use: [tt]cmp[/tt] will force a stringification (and if you had '0' and '00' they will be different), [tt]<=>[/tt] will force interpreting as numbers (and 'abc' and 'xyz' will be equal, as they are both interpreted a zeroes).

Franco
: Online engineering calculations
: Magnetic brakes for fun rides
: Air bearing pads
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top