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

I have a hash that is being sorted by its value
foreach (reverse sort {$hash{$a} <=> $hash{$b} || $a cmp $b} (keys %ha +sh))
This prints in a verticle manner as we expect. How can I split it in half and print the first half on the on one verticle column and the rest of the hash on the other?

1 2 3 4 5 6 7 8 9 10 11
to
1 7 2 8 3 9 4 10 5 11 6

Replies are listed 'Best First'.
Re: split hash into two columns
by davidrw (Prior) on Mar 27, 2006 at 20:13 UTC
    my @keys = reverse sort {$hash{$a} <=> $hash{$b} || $a cmp $b} (keys % +hash); foreach my $i ( 0 .. int($#keys/2) ){ # printf "%s %s\n", @keys[$i, $i+int($#keys/2)+1]; # UPDATE: expanded to avoid 'uninitialized value' warning print $keys[$i]; my $j = $i+int($#keys/2)+1; print "\t" . $keys[$j] if $j <= $#keys; print "\n"; }
      Almost
      1 7 2 8 3 9 4 10 5 11 Use of uninitialized value in printf [...]. 6
      Maybe
      my @keys = sort { $hash{$b} <=> $hash{$a} || $b cmp $a } keys %hash; my $half = int($#keys / 2); printf "%s\t%s\n", @keys[$_, $_+$half] for 0 .. $half-1; printf "%s\n", $keys[$#keys] unless $#keys % 2;
        This is a bit more code than I thought. Why are all the examples of my hash being broken into an array? I'd have to imagine there's a way to calculate 1/2 of the hash or something because in the code above, it doesn't print the key and the value.
Re: split hash into two columns
by ikegami (Patriarch) on Mar 27, 2006 at 20:36 UTC
    An alternative:
    my @keys = sort { $hash{$b} <=> $hash{$a} || $b cmp $a } keys %hash; my @col1 = splice(@keys, 0, $#keys/2+1); my @col2 = @keys; while (@col2) { my $key1 = shift(@col1); my $key2 = shift(@col2); printf "%s => %s\t%s => %s\n", $key1, $hash{$key1}, $key2, $hash{$key2}; } while (@col1) { my $key1 = shift(@col1); printf "%s => %s\n", $key1, $hash{$key1}; }

    Updated and tested.

      Hi.

      2 questions for you. I modified this code so I could save all the data.

      What is $t ^= 1 doing? I've never seen ^= before.

      Secondly, this is resulting in the data being separated from left to right order, not verticle.

      10 9 8 7 6 5 4 3
      instead of
      10 5 9 4 8 3 7 2 6 1
      How can I change it? Thanks!

        Oops! Fixed in original node. I also made the code print both the keys and the values.

        $a ^= $b is a shortcut for $a = $a ^ $b. Both operators are documented in perlop. In context, $t ^= 1 causes $t and the return value of the expression to flip-flip between zero and one every time it is executed (assuming $t is originally zero or one).

Re: split hash into two columns
by ptum (Priest) on Mar 27, 2006 at 20:13 UTC

    Seems like if you get the size of the array produced by the reverse sort, you ought to be able to come up with some sort of incrementor:

    my $incrementor = int((scalar(@my_array) / 2));

    Then you could step through the array printing the elements at indices $i and $i + $incrementor on a line:

    for (my $i=0;$i<=$incrementor+1;$i++) { print $my_array[$i], "\t"; print $my_array[$i+$incrementor] if defined($my_array[$i+$incremen +tor]); print "\n"; }

    Code is untested and without warranty.


    No good deed goes unpunished. -- (attributed to) Oscar Wilde
Re: split hash into two columns
by johngg (Canon) on Mar 27, 2006 at 22:03 UTC
    This looks a little like a problem I had laying out buttons in a Perl/Tk application using the "grid" method for packing them into rows and columns. After a lot of head scratching I wrote a small module that exported four routines that fit a number of items in a list to a number of either rows or columns sorting either horizontally or vertically. In this problem we'd fit to two columns sorting vertically. Here is the module; be gentle with me, I wrote it a long time ago:-)

    # ==== package Grid; # ==== use Carp; use strict; use integer; use Exporter; our @ISA = qw(Exporter); our @EXPORT = qw( fitToColsVSort fitToColsHSort fitToRowsVSort fitToRowsHSort); # -------------- sub fitToColsVSort # -------------- { my($numItems, $numCols) = @_; my $rlOrder = []; my $leftOver = $numItems % $numCols; my $maxRowNo = $numItems / $numCols + ($leftOver ? 1 : 0); my $rowNo = 0; my $colNo = 0; for (1 .. $numItems) { push @$rlOrder, [$rowNo, $colNo]; $rowNo ++; if($rowNo == $maxRowNo) { if($leftOver) { $leftOver --; $maxRowNo -- unless $leftOver; } $rowNo = 0; $colNo ++; } } return $rlOrder; } # -------------- sub fitToColsHSort # -------------- { my($numItems, $numCols) = @_; my $rlOrder = []; foreach my $item (0 .. ($numItems - 1)) { push @$rlOrder, [($item) / $numCols, ($item) % $numCols]; } return $rlOrder; } # -------------- sub fitToRowsVSort # -------------- { my($numItems, $numRows) = @_; my $rlOrder = []; foreach my $item (0 .. ($numItems - 1)) { push @$rlOrder, [($item) % $numRows, ($item) / $numRows]; } return $rlOrder; } # -------------- sub fitToRowsHSort # -------------- { my($numItems, $numRows) = @_; my $rlOrder = []; my $leftOver = $numItems % $numRows; my $maxColNo = $numItems / $numRows + ($leftOver ? 1 : 0); my $rowNo = 0; my $colNo = 0; for (1 .. $numItems) { push @$rlOrder, [$rowNo, $colNo]; $colNo ++; if($colNo == $maxColNo) { if($leftOver) { $leftOver --; $maxColNo -- unless $leftOver; } $colNo = 0; $rowNo ++; } } return $rlOrder; } 1;

    and here is an example of it's use:-

    ... $rlButtonOrder = fitToColsVSort($numHosts, $numCols); foreach (0 .. $#nodeList) { my $buttonName = $nodeList[$_] . "Button"; $$buttonName = $hostSelectFrame->Button( ... -text => $nodeList[$_], ... -width => $longestName)->grid( -row => $rlButtonOrder->[$_]->[0], -column => $rlButtonOrder->[$_]->[1], ... ); $$buttonName->configure( -command => [\&gotoHost, $nodeList[$_]]); } ...

    In essence, what it does is take as arguments the number of items to be fitted and the number of rows or columns to fit them in; you choose the right routing for vertical or horizontal sorting to rows or columns. Returned is a LoL of row/column position for each element of the list to be output.

    Although the problem is different the algoriths should hopefully still work, counting the keys, using fitToColsVSort(11, 2) to get row/column places then sorting your hash and displaying it.

    In case you were wondering, the application I wrote it for was to fire up a terminal and open a telnet session to a host at the click of a button.

    Cheers,

    JohnGG

      I have found the script I wrote at the same time as Grid.pm to test the algorithms. As I said before, it was written some time ago before use strict; and use warnings; became habitual. It was written on a Solaris box, I have also run it with ActiveState and have just tested it again under Linux. Here it is if you are interested:-

      There's not a lot in the way of comments, it was not production code, sorry.

      Cheers,

      JohnGG

Re: split hash into two columns
by sulfericacid (Deacon) on Mar 27, 2006 at 20:42 UTC
    Here is my attempt without using arrays. Note this doesn't break them into two columns (it sort of almost does, it prints the first half and then the second half), but it's the best I could do. Maybe you could figure out how to hack it to do what you want. It'd actually be interesting to see if someone can fix my almost functional code.
    my $half = scalar keys %hash; $half = $half / 2 + 1; my $cnt = 0; foreach (keys %hash) { $cnt++; my $key = $_; if ($cnt <= $half) { print "$key => $hash{$key}\n"; } else { print "\t$key => $hash{$key}\n"; } }


    "Age is nothing more than an inaccurate number bestowed upon us at birth as just another means for others to judge and classify us"

    sulfericacid
      It'd actually be interesting to see if someone can fix my almost functional code

      It's still quite far off. davidrw's earlier solution (and the fix I added in my reply) is the what you get if you continue on your path.

        Yes, I thought about that uninitialized error and knew that would come up. By golly, if I can't come up with a dirty hack to do a pure hash solution I'm going to go crazy.

        I'll make another attempt if I can come up with something.



        "Age is nothing more than an inaccurate number bestowed upon us at birth as just another means for others to judge and classify us"

        sulfericacid
Re: split hash into two columns
by davidrw (Prior) on Mar 27, 2006 at 21:09 UTC
    and another approach:
    my @keys = reverse sort {$hash{$a} <=> $hash{$b} || $a cmp $b} (keys % +hash); my @idx = ( ( map { $_, $_+1+$#keys/2 } 0..$#keys/2-1 ), $#keys%2?():$ +#keys/2 ); for(my $i=shift(@idx); defined $i; $i=shift(@idx) ){ print $keys[$i]; ($i = shift(@idx)) && print "\t" . $keys[$i]; print "\n"; }
Re: split hash into two columns
by Anonymous Monk on Mar 27, 2006 at 21:31 UTC
    okay guys I think my example was misleading because I'm getting lost.

    I don't just want the keys as the output, I want everything. I just did that as a quick example and I apologize if it confused people.

    Example:

    %hash = ( test => 1, red => 3, blue => 14, size => 2, orange => 7, punk => 18, pink =>19, candle => 21, can => 11 );
    test 1 size 2 red 3 orange 7 can 11 blue 14 punk 18 pink 19 candle 21
    Just imagine it is sorted verticle from row A to row B. These are the results I need. Thanks
      That has already been answered.