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

Hello; hoping someone can help me because i'm somewhat puzzled...I'm a relative Perl newbie...

I have a multidimensional array which is pulling arrays of three elements from a file that is a list of three-number lines.

In other words, I'm taking a file that looks like:

1 2 20 2 3 15 3 4 3 4 5 17 5 6 28 6 1 23 1 7 1 2 7 4 3 7 9 4 7 16 5 7 25 6 7 36

And producing an array structured like:

my @item_array = {{1, 2, 20}, {2, 3, 15}, {3,4,3}, {4, 5, 17}, {5,6,28}, {6,1,23}, {1, 7, 1}, {2, 7, 4}, {3, 7, 9,}, {4, 7, 16}, {5, 7, 25}, {6, 7, 36}};

All of this is written and works. I'd now like to sort @item_array by the third element in each sub array (i.e. {2, 3, 15} should come before {1, 2, 20}, etc.).

I found code which I think should do this:

my @sorted_array = sort { $a->[2] <=> $b->[2] } @item_array;

However, when I print this out, it seems that all it's doing is turning all of the sub arrays into the last sub array. In other words, @sorted_array is just twelve instances of {6, 7, 36}.

Does anyone have any idea why this is happening? What would the proper code be to sort this array by the third element of each child array?

Replies are listed 'Best First'.
Re: Sort multidimensional array by third item
by kennethk (Abbot) on Nov 12, 2010 at 17:17 UTC
    Minus some misformatted delimiters on your my @item_array = ... line in the OP, everything you've posted seems good. Specifically,

    #!/usr/bin/perl use strict; use warnings; my @item_array = ([1, 2, 20], [2, 3, 15], [3,4,3], [4, 5, 17], [5,6,28], [6,1,23], [1, 7, 1], [2, 7, 4], [3, 7, 9,], [4, 7, 16], [5, 7, 25], [6, 7, 36], ); my @sorted_array = sort { $a->[2] <=> $b->[2] } @item_array; for (@sorted_array) { print join("\t", @$_), "\n"; }

    Outputs:

    1 7 1 3 4 3 2 7 4 3 7 9 2 3 15 4 7 16 4 5 17 1 2 20 6 1 23 5 7 25 5 6 28 6 7 36

    Note I copied/pasted the sort. As well, note that curly brackets are used in hash references, square brackets in array references, and parentheses in list construction. See perlreftut.

    Are you sure that your array contains what you think it contains? See How can I visualize my complex data structure?. The short answer is is use Data::Dumper to make sure your array contains what you think it does: use Data::Dumper; print Dumper \@item_array;

Re: Sort multidimensional array by third item
by choroba (Cardinal) on Nov 12, 2010 at 17:22 UTC
    Your code should produce this kind of a structure rahter
    @item_array = ([1, 2, 20], [2, 3, 15], [3,4,3], [4, 5, 17], [5,6,28], +[6,1,23], [1, 7, 1], [2, 7, 4], [3, 7, 9,], [4, 7, 16], [5, 7, 25], [ +6, 7, 36])
    Then, sort would work. You can use Data::Dumper to check what your structures really are.
Re: Sort multidimensional array by third item
by rosalindwills (Initiate) on Nov 12, 2010 at 20:28 UTC

    Whoops.

    OK, so DataDumper revealed that my script is in fact producing an array full of empty references and I've evidently confused myself more than I thought...consequently I have a new question.

    I have a file that looks like this:

    7 1 2 20 2 3 15 3 4 3 4 5 17 5 6 28 6 1 23 1 7 1 2 7 4 3 7 9 4 7 16 5 7 25 6 7 36 -1

    What I would like to do is take all the lines that are not the first or last line and make a sortable multidimensional array out of them, as described above.

    My current code is:

    #!/usr/bin/perl use warnings; use strict; use Data::Dumper; #open initial file open FILE, "graph.txt" or die "Error with original file: $!"; #open empty file to write to #open NEWFILE, ">output.txt" or die "Error with output file: $!"; #Get array with lines of file my @line_array = <FILE>; #reduce array to content info only shift(@line_array); pop(@line_array); #initialize item array and something to hold it during loop our @item_array; our @hold_array; #convert each edge string to an array, push it onto item_array for (my $i = 0; $i < scalar(@line_array); $i++) { our $line = $line_array[$i]; @hold_array = split(/\s/, $line); push(@item_array, \@hold_array); } print Dumper @item_array;

    This produces in Dumper:

    $VAR1 = [ '6', '7', '36' ]; $VAR2 = $VAR1; $VAR3 = $VAR1; $VAR4 = $VAR1; $VAR5 = $VAR1; $VAR6 = $VAR1; $VAR7 = $VAR1; $VAR8 = $VAR1; $VAR9 = $VAR1; $VAR10 = $VAR1; $VAR11 = $VAR1; $VAR12 = $VAR1;

    Pretty sure this isn't what I want...am I approaching this the wrong way?

    Thank you both for your help, by the way...you're awesome! =)

      Since @hold_array is outside the scope of your for loop, you are repeatedly pushing a reference to the same array to the end of your @item_array. An easier way to achieve your goal would be using anonymous arrays - see Making References in perlreftut. This might look like:

      for my $line (@line_array) { push(@item_array, [split(/\s/, $line)]); }

      If you want to use an explicit temporary storage array, you could use a variable that is scoped to the loop using my. This way, each iteration will create a new array with the same name, so each reference will be different.

      for my $line (@line_array) { my @hold_array = split(/\s/, $line); push(@item_array, \@hold_array); }

      Note I've swapped to Foreach Loops syntax to avoid possible indexing errors.

      I also note you use our a lot. Each time you do that, you are creating a global variable. Is there some reason you want to do that? Likely, you should be using my instead.

      Why not be a little more flexible and defensive ...

      my @unsorted; while (<FILE>) { my @fields = m/([0-9]+)/g; @fields == 3 and push @unsorted, \@fields; } my @sorted = sort { $a->[2] <=> $b->[2] } @unsorted;

      Enjoy, Have FUN! H.Merijn
Re: Sort multidimensional array by third item
by rosalindwills (Initiate) on Nov 12, 2010 at 21:16 UTC
    Ahhh...that makes sense and works perfectly...brilliant! =) Thanks so much to all of you!
Re: Sort multidimensional array by third item
by 7stud (Deacon) on Nov 12, 2010 at 22:30 UTC

    Note that instead of this:

    my @line_array = <FILE>; shift(@line_array); pop(@line_array);

    You can use an array slice:

    @line_array = @line_array[1..-2];  #I guess you can't (see following posts)

    Another solution:

    ... ... my @lines = (<$INFILE>)[1..-2]; my @unsorted = map [split], @lines; my @sorted = sort {$a->[2] <=> $b->[2]} @unsorted; for my $arr_ref (@sorted) { print "@$arr_ref\n"; }
      Your indices are wrong. The expression 1..-2 returns an empty list. See Range Operators in perlop. This will work if you either use 2 positive (@line_array = @line_array[1 .. @line_array-2];) or 2 negative (@line_array = @line_array[1-@line_array .. -2];) limits on the range.
        Seeing Range operators in perlop is of absolutely no help since it doesn't mention negative indexes.
          A reply falls below the community's threshold of quality. You may see it by logging in.