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

Hi guys !

I have a hash of arrays (the number of arrays is random and each array can hold different number of values), each array holds a list of numbers. The hash is built on runtime according to user's input from STDIN (each iteration builds one array), so far so good.
What I need is to verify that in each array the numbers are consecutive, all the arrays together should holds numbers from 0 to the higher number the user entered, and no number should appear more than once, for example:
Example 1: (iteration 1): 0 1 2 (iteration 2): 4 6 5 ### Wrong input - missing 3, and second array is not consecutive Example 2: (iteration 1): 4 5 6 (iteration 2): 0 1 2 3 4 ### Wrong - 4 appears twice Example 3: (iteration 1): 4 5 6 7 8 (iteration 2): 0 1 2 3 ### this one is OK
In general, I can join all the arrays to a big one, sort it, and check that each array value equals the array's index, but I'll fall in case the user enters:
first: 0 1 3 second: 2 4 5
The joined array will qualify but the little ones not. anyone has a short idea (long solutions I have in plenty, but probably very ugly) ?

Thanks

Hotshot

Replies are listed 'Best First'.
Re: joining and sorting arrays
by strat (Canon) on Dec 04, 2001 at 22:18 UTC
    If you just want to find out if something's missing, you could try something like the following...
    my @list1 = qw(0 1 3); my @list2 = qw(2 4 6 5); my @sortedBiglist = sort { $a <=> $b } (@list1, @list2); my @predefList = (0..$#sortedBiglist); if ("@sortedBiglist" eq "@predefList"){ print "all ok\n"; }
      my @biglist = qw(0 1 3); # already saved data while (my @newList = &GetNewList() ){ @newList = sort { $a <=> $b } (@newList); unless ($bigList[-1] == $newList -1){ # Error } # unless else { my @testList = ((scalar @bigList)..($#bigList+$#newList)); # build + template list to compare unless ("@testList" eq "@newList"){ # Error } # unless else { push (@bigList, @newList); } # else } # unless } # while # -------------------------------------- sub GetNewList { return (qw(2 4 6 5)); # or whatever } # GetNewList
Re: joining and sorting arrays
by ehdonhon (Curate) on Dec 04, 2001 at 22:19 UTC

    You could do this, which would be O(N): Run through element in every array. For each one, you would do this:

    1. Increment a counter
    2. Add to a running total
    3. Keep track of the highest value you find.

    Now, if the highest value you find isn't equal to your counter - 1 (subtract one because of the zero), you know you have a problem. And, if the total is not equal to the summation of 1 to your higest value, then you have a problem also. Otherwise, everything is correct.

      No. Fairly easy to find counter examples. Just start with a small working example:

      0 1 2 3 4 5
      then take two numbers near the middle that aren't 1 apart and add 1 to one of them and subtract 1 from the other:
      0 2 2 3 3 5

      Seems that a correct, fast soltuion would be a modified merge sort. I have a working example of such but I'm dubious of posting it as it now occurs to me that this is would be a fairly good, fairly advanced homework problem while I'm having a hard time coming up with a practical use for such.

      Perhaps you should tell us why you need this?

      Update: Just realized that the problem is even simpler than I first understood. I initially thought that the last example was a "good" example, not a "bad" example. I've updated my counter example to reflect that.

      This also means that a modified merge sort is overkill. Instead I'd just stuff the first element of each list into a hash and start at 0, look up the list that starts there, move down it verifying sequentialness, look up the list that starts at the next number, etc. I'm still not posting demonstration code, however, for most of the reasons I stated.

              - tye (but my friends call me "Tye")
(jeffa) Re: joining and sorting arrays
by jeffa (Bishop) on Dec 04, 2001 at 22:30 UTC
    Don't forget that you could test for consecutiveness as the user enters the numbers, something along the lines of:
    if ($input =~ /^\d+$/ and $input == $last + 1) { # process it } else { die "not consecutive from $input to $last"; }
    would catch the error on the spot, you might even choose to let the user try again.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    F--F--F--F--F--F--F--F--
    (the triplet paradiddle)
    
      the problem is that the new input isn't have to be consecutive to the last (in each iteration the user enters a series of numbers - theses should be consecutive), only the joined array should be consecutive

      Hotshot
        The idea is still valid though. it just doesn't replace checks after the data entry.
        break up the task
        Ensure each new array is internally consecutive
        easiest to do at data entry, when you don't have to think about all the other arrays
        all the arrays together should hold 0 to #, and no number apears more than once.
        after data entry. join the arrays, and sort it, as you are doing now

        It adds a step, but doen't add much complexity.

        This is assuming that the data is entered within 2 loops, that you can have the the check reset. something like:
        my %list; for my $outer=(1..5){ my $last=0; for my $inner=(0..9){ my $number=<STDIN>; unless ($last==0) { unless ($number==$last+1){ die("bad input"); } $list{$outer}[$inner]=$number; } }
        Course its very rough code (and probably won't work without tweaking {or a total rewrite}) the principal is there.
Re: joining and sorting arrays
by Zaxo (Archbishop) on Dec 04, 2001 at 23:20 UTC

    That's such tightly constrained input that only the first value of a row is needed to check it.

    sub are_consecutive { my $i = $_[0]; $_ == $i++ or return 0 for @_; 1; } are_consecutive(@{$_}) or die "User can't count.$/" for values %hsh; my @big = sort {$a <=> $b} map {@{$_}} values %hsh; $big[0] == 0 && are_consecutive(@big) or die "Bad big.$/"; # originally had soln in terms of $big[0] == 0 and $big[-1] == $#big # rejected because equal number of missing and doubled terms not detec +ted
    The usual hash for checking uniqueness is unneeded here because of the additional arithmetic properties you want.

    What application requires that a user partition 0..N this way?

    Update: cleaned up and corrected for offsetting double errors

    After Compline,
    Zaxo

Re: joining and sorting arrays
by Masem (Monsignor) on Dec 04, 2001 at 22:58 UTC
    sub consecutive_array { return scalar ( $_[0]..$_[-1] ) && # option: ! grep { /\D/ } @_ && # checks for digits only join "_", @_ eq join "_", ($_[0]..$_[-1]); } #UPDATE, need to sort this array too to work, oops :-) my @bigarray = sort map { ( $_->{array} ) } keys %hash; my $valid = $bigarray[0] == 0 && consectutive_array( @bigarray ) && ! grep { !consectutive_array( $_->{array} ) } keys %hash;
    Update forgot to sort the big array

    -----------------------------------------------------
    Dr. Michael K. Neylon - mneylon-pm@masemware.com || "You've left the lens cap of your mind on again, Pinky" - The Brain
    "I can see my house from here!"
    It's not what you know, but knowing how to find it if you don't know that's important

Re: joining and sorting arrays
by George_Sherston (Vicar) on Dec 04, 2001 at 22:47 UTC
    It sounds like you want the big array, before it's sorted, to be all the numbers 1 to n in sequence. Is that right? If so, TIMTOWTDI, but the following will give you an array (@probs) of all the numbers that are not exactly one larger than the one before:
    push @probs, grep {pop @array != $array[$#array] + 1} @array;


    § George Sherston