1

I am looking for suggestions for a more efficient algorithm to determine if an array contains all the values from 1 through the length of the array. The solution I have devised works correctly using Ada2012.

------------------------------------------------------------------
-- Build a function to determine whether an array contains --
-- all the values from 1 through the length of the array. --
------------------------------------------------------------------
with Ada.Text_IO; use Ada.Text_IO;

procedure Sequence_test is
   type Sequence_Array is array(Positive range <>) of Integer;

   function Is_Sequence(Item : Sequence_Array) return Boolean is
      Flags : Array(Positive range 1..Item'Length) of Boolean := (Others => False);
   begin
      for Num of Item loop
         if Num in Flags'Range then
            Flags(Num) := True;
         else
            exit;
         end if;
      end loop;
      return (for all P of Flags => P = True);
   end Is_Sequence;
   A : Sequence_Array := (1,2,3,4,5,6);
   B : Sequence_Array := (6,5,4,3,2,1);
   C : Sequence_Array := (1,1,1,6,6,6);
   D : Sequence_Array := (1,2,3,4,6);
   E : Sequence_Array := (6,1,5,2,4,3);
   F : Sequence_Array := (1,1,1,2,3,4,5,9,10,11);
begin
   Put_Line("A is " & Boolean'Image(Is_Sequence(A)));
   Put_Line("B is " & Boolean'Image(Is_Sequence(B)));
   Put_Line("C is " & Boolean'Image(Is_Sequence(C)));
   Put_Line("D is " & Boolean'Image(Is_Sequence(D)));
   Put_Line("E is " & Boolean'Image(Is_Sequence(E)));
   Put_Line("F slice is " & Boolean'Image(Is_Sequence(F(3..7))));
end Sequence_test;

The output of my program is

A is TRUE
B is TRUE
C is FALSE
D is FALSE
E is TRUE
F slice is TRUE
Jim Rogers
  • 4,822
  • 1
  • 11
  • 24

2 Answers2

3

You could use some of the built-in set support. I don't think its guaranteed to execute quicker, but if you turn on full optimization and your compiler's optimizer is good, it might execute quicker. I say this because it could pack your array and compare it 32 bools at a time, rather than needing a loop iteration for each. Likewise it also could create your comparison array 32 bools at a time.

Of course, if your optimizer is really good it might figure out how to do the same thing with the code you had.

function Is_Sequence(Item : Sequence_Array) return Boolean is
  All_There constant : Array(Positive range 1..Item'Length) of Boolean 
    := (Others => True);
  Flags : Array(Positive range 1..Item'Length) of Boolean := not All_There;

begin
  for Num of Item loop
     if Num in Flags'Range then
        Flags(Num) := True;
     else
        exit;
     end if;
  end loop;
  return Flags = All_There;
end Is_Sequence;

The other suggestion I have for you is to replace that exit statement with a return False; You know at that point it doesn't match, so there's no point hanging out in the routine and doing more work.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
T.E.D.
  • 44,016
  • 10
  • 73
  • 134
  • Using the actual range of the passed-in array is an error. The value sequence must start at 1, so the Flags array must start at 1, no matter how long the passed-in array is. The use of a Flags array fails when the length of the passed-in array is greater than Integer'Last, for instance when the range of the passed-in array is Integer'First..Integer'Last. – Jim Rogers Feb 06 '17 at 15:37
  • @JimRogers - I was wondering about that (refer back to my note about not quite understanding the requirements). So in that case, I'll put those back, but consider this a strong suggestion to think about what happens to your algorithm in the case where a slice is passed in. – T.E.D. Feb 06 '17 at 15:38
  • One could simply change the requirements such that the passed-in array was defined as array(Positive range <>) of Integer – Jim Rogers Feb 06 '17 at 15:40
  • @JimRogers Wouldn't that still allow someone to pass a (5..10) array? – T.E.D. Feb 06 '17 at 15:43
  • Yes it will. Try it. The for loop testing the values is agnostic about the index range of the passed-in array. It uses whatever range is passed in. That range will be the range of the slice, not the range of the base type of the array. – Jim Rogers Feb 06 '17 at 15:46
  • @JimRogers - OK, I think I see. That's actually your attempt to rebase your sets at 1. So you're checking if the given array contains 1...its length (in any order), rather than checking if it contains 'first...'last (in any order). So you **want** B(4..6) to succeed, and A(4..6) to fail. – T.E.D. Feb 06 '17 at 16:00
2

On my computer, I get a slightly better result with

  for Num of Item loop
     exit when Num not in Flags'Range;
     Flags(Num) := True;
  end loop;
  return (for all P of Flags => P);

And more surprisingly, a named subtype seems to give even better result:

type Item_Length_Array is array(Positive range 1..Item'Length) of Boolean;
Flags : Item_Length_Array := (others => False);

Tested with:

Start_Time := Ada.Real_Time.Clock;
for I in 1..10_000_000 loop
   Ignore := Is_Sequence(A);
   Ignore := Is_Sequence(B);
   Ignore := Is_Sequence(C);
   Ignore := Is_Sequence(D);
   Ignore := Is_Sequence(E);
   Ignore := Is_Sequence(F(3..7));
end loop;
End_Time := Ada.Real_Time.Clock;
egilhh
  • 6,464
  • 1
  • 18
  • 19