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

Okay..I have a Users Online script that also shows how many users are on each page... I want it to order the showing via how many users... so if it is like this: 12 users page_url.. 6 users page_url.. 14 users page_url.. 1 user page_url.. then it will put it in decending order, the highest amount first, then down. and if there is like, for example, 3 pages that have the same amount of users, like 2 users page_url... 2 users page_url2... 2 users page_url3... then it will just rank it in any way...I don't care how that part is ranked. This is the code used: everything is held in foreach $loc (@locations){ } and the number of users online is in ${$loc}{online} (then ${$loc}{users} which says "user" or "users" depending on the number, and $loc which is the actual location, so it all is mainly like this:
foreach $loc (@locations){ print "${$loc}{online} ${$loc}{users} $loc\n"; }
so, how do I have it display it in foreach depending on the # of users?

Replies are listed 'Best First'.
Re: Stupid, yet simple, sort question
by davorg (Chancellor) on Oct 24, 2001 at 20:46 UTC

    Argh! You're using symbolic references aren't you. Please don't do that. It's nasty.

    Here's how you do it using your system. Hopefully someone else will have more time and will be able to show you how you should do it using real references.

    foreach my $loc (sort {${$b}{online} <=> ${$a}{online}} @locations) { print "${$loc}{online} ${$loc}{users} $loc\n"; }
    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you don't talk about Perl club."

Re: Stupid, yet simple, sort question
by ChemBoy (Priest) on Oct 24, 2001 at 20:42 UTC

    As noted in chatter,

    foreach my $loc (sort {$b->{users} <=> $a->{users}} @locations){ ... }

    should do the trick.

    Update: updated to what joealba was suggesting as I updated it (though without the intermediate step implied by what I said in chatter right before ;-).

    Update 2: or, using the correct field...

    foreach my $loc (sort {$b->{online} <=> $a->{online}} @locations){ ... }

    <sheepish grin>



    If God had meant us to fly, he would *never* have given us the railroads.
        --Michael Flanders

      Just sort it right the first time.. don't reverse it after.
      foreach my $loc (sort {$b->{users} <=> $a->{users}} @locations) { ... }
      UPDATE: I wanna be like ChemBoy when I grow up. :)

      UPDATE2:Kage, please do yourself a favor. Restructure your data structures into one nice neat hash.
      %pages = ( 'page1' => 2, 'page2' => 24, 'page3' => 8, ); foreach my $loc (sort {$pages{$b} <=> $pages{$a}} keys %pages) { my $s = "s" unless $pages{$loc} == 1; print "$pages{$loc} user$s accessing $loc\n"; ... }

      Update 129: See the entire recoded solution below
Re: Stupid, yet simple, sort question
by jeroenes (Priest) on Oct 24, 2001 at 21:32 UTC
    A few style remarks:
    1. try 'use strict' for a change.
    2. try to avoid reeling in outside info in your namespace... use nested structures instead. perlref, perldsc, perllol to name a few. A link to perlreftut can be found in my homenode.
    3. read up on sort and perlop. Use these kind of constructs to sort nested structures
      @sorted_keys = sort { $HoH{$b}{'number'} <=> $HoH{$a}->{'number'} } ke +ys %HoH;
    4. The require 'config.txt' looks questionable, to say the least. Try to use a real config format, like AppConfig.
    5. Try to minimalize the use of reserved words (see perlfunc ). They are very confusing..
    So, your code:
    open(FILE,$log); flock (FILE,3); @users=<FILE>; close(FILE); foreach $lines (@users){ chop($lines); ($ips,$times,$locationold)=split('×',$lines ); $timeoff=$time-$times; push @locations,$locationold if (!$exists{$locationold}); $exists{$locationold}=1; ${$locationold}{online}++; foreach $loc (@locations){ if(${$loc}{online} < "2") { ${$loc}{users} = "user"; } else { ${$loc}{users} = "users"; } } foreach $loc (@locations){ print "${$loc}{online} ${$loc}{users} $loc\n"; }
    Could be written as:
    use strict; use warnings; use CGI::Carp qw/fatalsToBrowser/; open(FILE,$log) or die "Couldn't open $log: $!"; flock (FILE,3) or die "Couldn't lock $log: $!"; my @users=<FILE>; close(FILE); chomp @users; my %locations; foreach my $lines (@users){ my ($ips,$times,$location)=split('×',$lines); $timeoff=$time-$times; if ( not exists $locations{$location}) { $locations{$location}={online => 1}; } else { $locations{$location}->{'online'}++; } } $location{$_}->{'usertext'}=$location{$_}->{'online'}==1? 'user' : 'us +ers' for keys %locations; foreach my $loc (sort { $b->{'online'} <=> $a->{'online'} }keys %locat +ions){ my $href = $locations{$loc}; print $href->{'online'}." ".$href->{'usertext'}."$loc\n"; }
    To start with. I have added: Hashes of hashes, strict compliance (sort of), die statement as open check, fatalstobrowser, and more things.... it's only a start. Code still unchecked, may contain typos etc.

    Hope this gets you on your way....

    Jeroen
    "We are not alone"(FZ)

    Updated... still more to come, I guess.

Re: Stupid, yet simple, sort question
by broquaint (Abbot) on Oct 24, 2001 at 20:38 UTC
    This ought do the trick -
    foreach $loc (reverse sort { int(${$a}{online}) <=> int(${$b}{online}) + } @locations)

    HTH

    broquaint

    Update: And if I look the at code a little closer, it may even work (dereferenced the info we want, thanks davorg)

Re: Stupid, yet simple, sort question
by Kage (Scribe) on Oct 24, 2001 at 20:44 UTC
    Okay...I got 3 answers...all three sound right...but I don't know which to use..I need the most efficient one..
    #1: sort{ $a->{users} ,<=> $b->{users} } @locations #2 for my $loc ( sort { $a->{users} <=> $b->{users} } @locations ) #3 foreach $loc (reverse sort { int($a) <=> int($b) } @locations)
    Which one is the most efficient one? I need one that can take at least 3,000 different ranks...thats the stress limit of my script and it must be able to handle the stress limit..

      None of them will work, as they all assume that you're using hard references - which you aren't.

      Unless I've completely misunderstood your code.

      --
      <http://www.dave.org.uk>

      "The first rule of Perl club is you don't talk about Perl club."

        As noted in chatter, davorg was correct about Kage using symbolic referencees but was incorrect in thinking that the syntax $a->{key} does not work when $a isn't a "hard" reference (that is, when $a simply contains the name of some global hash variable).

        Of course, use strict would prevent it from working, but that also prevents ${$a}{key} from working.

                - tye (but my friends call me "Tye")
Re: Stupid, yet simple, sort question
by Kage (Scribe) on Oct 24, 2001 at 21:01 UTC
    this is most all of what it does:
    require "config.txt"; $refresh="60"; $ip=$ENV{'REMOTE_ADDR'}; $time=time; open(FILE,$log); flock (FILE,3); @users=<FILE>; close(FILE); foreach $lines (@users){ chop($lines); ($ips,$times,$locationold)=split('×',$lines); $timeoff=$time-$times; push @locations,$locationold if (!$exists{$locationold}); $exists{$locationold}=1; ${$locationold}{online}++; foreach $loc (@locations){ if(${$loc}{online} < "2") { ${$loc}{users} = "user"; } else { ${$loc}{users} = "users"; } } foreach $loc (@locations){ print "${$loc}{online} ${$loc}{users} $loc\n"; }
      require "config.txt"; my $refresh="60"; my $ip=$ENV{'REMOTE_ADDR'}; my $time=time; open(FILE,$log) or die "Couldn't open $log: $!\n"; flock (FILE,3); my @users=<FILE>; close(FILE); my %pages; foreach my $line (@users){ chomp($line); my ($ips,$times,$location)=split('×',$line); $pages{$location}++; } foreach my $loc (sort {$pages{$b} <=> $pages{$a}} keys %pages) { my $s = "s" unless $pages{$loc} == 1; print "$pages{$loc} user$s accessing $loc\n"; }

      Always check the return value from things such as open and flock. And it'd be better to use the symbolic constants from Fcntl rather than hardcoding `3' (assuming traditional values that would seem to be LOCK_SH|LOCK_EX, which doesn't make much sense).