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

I'm not a programmer, so I need some help with a task I need to accomplish. Hopefully one of you perl experts out there can help me out here.

I have a file containing about 1000 or so out of sequence numbers in a single column that I need to do the following with:

Convert the list of numbers from file "list.txt":

125
23
291
95
5
etc...

to a two-column list of all possible number pairings:
125 23
125 291
125 95
125 5
23 291
23 95
23 5
291 95
291 5
95 5
etc...

Can anyone provide me a script that would do this? I know it would involve putting the numbers into an array and then iterating over the array in a nested loop, but I have no idea how to actually code this.

Thanks in advance!!

Replies are listed 'Best First'.
Re: help writing simple perl script
by traveler (Parson) on Jul 27, 2006 at 16:39 UTC
    Here is some simple code to do it. Put it in a file and redirect the output to list.txt (e.g. foo.pl>list.txt).
    use strict; my @arr = <STDIN>; map chomp $_, @arr; foreach my $i (0..$#arr){ foreach my $j (0..$#arr){ print "$arr[$i] $arr[$j]\n" if $i != $j; } }
    Smaller versions surely exist...

    Update: If you want to read from list.txt use

    foo.pl<list.txt

      Actually this one almost gets me all the way there, but ends up printing out a lot of redundancy. I only want any given pair of numbers to appear only ONCE in the output (regardless of the order).

      In other words, for 4 numbers, instead of:

      1 2
      1 3
      1 4
      2 1
      2 3
      2 4
      3 1
      3 2
      3 4
      4 1
      4 2
      4 3

      ...I'd want the output to be:

      1 2
      1 3
      1 4
      2 3
      2 4
      3 4

      Is there a way to do this without using the Math::Combinatorics module (I don't have permissions to install Perl modules, and hate having to go through our IT admin, so would like to avoid this if possible)

        Ahh, ok, different problem. Just change the inner loop to
        foreach my $j ($i..$#arr){
      awesome! This one works great. Thanks so much!
Re: help writing simple perl script
by davido (Cardinal) on Jul 27, 2006 at 16:41 UTC

    Are you looking for all unique combinations? If so, here you go...

    use strict; use warnings; use Math::Combinatorics; # Get the data file name from command line. # Output filename is optional. my( $infile, $outfile ) = @ARGV; die "No filenames provided.\n" unless defined $infile; $outfile = ( defined $outfile ) ? $outfile : $infile . '.out'; # Read input data and put it in an array for later use. open my $in_handle, '<', $infile or die "Couldn't open $infile for input.\n$!"; my $dataset_aref; while ( my $number = <$in_handle> ) { chomp $number; next unless length $number > 0; push @{$dataset_aref}, $number; } close $in_handle; # Create a Math::Combinatorics object. my $combinat = Math::Combinatorics->new( count => 2, data => $dataset_aref, ); # Open an output file, and print out the combinations. open my $out_handle, '>', $outfile or die "Couldn't open $outfile for output.\n$!"; while ( my @combo = $combinat->next_combination ) { print $out_handle "@combo\n"; } close $out_handle or die "Couldn't close $outfile after output.\n$!";

    Usage example:

    perl mycombinat.pl data.txt data.out

    data.txt should contain a '\n' delimited list of elements. data.out will contain a list of pairings. If you don't specify an output filename one will be chosen for you (the extension '.out' will be added to whatever input filename you specified).

    This script uses Math::Combinatorics to find all unique combinations, two at a time. nC2.


    Dave

Re: help writing simple perl script
by ptum (Priest) on Jul 27, 2006 at 16:27 UTC

    A brute force approach, as you indicated, would iterate across the same array in a nested fashion. You would perhaps use code like this (untested):

    use strict; use warnings; my $fh; unless (open($fh,"</path/list.txt")) { die "Cannot open file: $!\n"; } my @number_array = (); while (<$fh>) { push @number_array, $_; } for (my $i=0;$i<$#number_array;$i++) { for (my $j=$i+1;$j<=$#number_array;$j++) { print $number_array[$i], "\t", $number_array[$j], "\n"; } }

    Update: fixed error in outer loop -- only needs to iterate up to (n-1)th array index.


    No good deed goes unpunished. -- (attributed to) Oscar Wilde
Re: help writing simple perl script
by Ieronim (Friar) on Jul 27, 2006 at 16:53 UTC
    Two completely different subs, the first is much faster, the second is much more flexible. You need to replace @ary with your actual data :)
    #!/usr/bin/perl use warnings; use strict; my @ary = (0..5); print map { "@$_\n" } pairs(@ary); print "\nCHOOSE:\n"; print map { "@$_\n" } choose(2, @ary); # hard-coded return of pairs sub pairs { my @result; foreach my $i (0..$#_-1) { foreach my $j ($i+1..$#_) { push @result, [$_[$i], $_[$j]]; } } return @result; } #more general solution: choose any combinations of k elms from @_: sub choose { no warnings 'recursion'; my $k = shift; my @ary = @_; my $n = scalar @ary; return if ($k > $n) || ($k < 1); if ($k == 1) { return map { [$_] } @ary; } my @AoA; foreach my $i (0..($n-$k)) { push @AoA, map { [$ary[$i], @{$_}] } choose($k-1, @ary[ $i+1.. +$#ary ]); } return @AoA; }

         s;;Just-me-not-h-Ni-m-P-Ni-lm-I-ar-O-Ni;;tr?IerONim-?HAcker ?d;print
Re: help writing simple perl script
by ambrus (Abbot) on Jul 27, 2006 at 16:14 UTC

    This seems to work.

    sed 'N;s/\n/ /' list.txt
    To make it portable, you may need to replace ; with a newline, and possibly \n with some more portable thing to match a newline.

    Update: indeed, this is a wrong solution. I didn't read the OP carefully. Sorry.

    Update: \n is said to be portable.

      thanks but that's not quite what I needed. It has to be "N-by-N", and that simply pairs the 1st with 2nd, 2nd with 3rd, 3rd with 4th, etc. I need to pair the 1st against the 2nd through N. The 2nd against the 3rd through N, etc.