4  Vectors

A consequence of the strong typing model implemented in Ada even Strings are to be of fixed size - requiring a declaration before being used. While reading text files, a String buffer is provided though it is almost certain that all the lines are not of the same length. Such variable length dynamic datastructures are a necessity yet the safety of the data structures at run time.

In this projectlet, a simple container library package Ada.Containers.Vectors is exploited as a way to provide dynamically sized arrays of e.g. Integers.

4.1 Learning Objectives

  • Safe manipulation of dynamically sized arrays (vectors)

  • Iteration

  • Fixed Point datatype

4.2 Library foundation

The predefined library offers the generic package Ada.Containers.Vectors which needs to be instantiated with a specific data type in order to support the dynamic arrays of that datatype. In our projectlet, we use the facility to create dynamic arrays of say octal, decimal or hexadecimal digits. In addition dynamic arrays for Natural numbers is also created. The support is utilized for a variety of recreational mathematical algorithms.

4.3 Custom Vectors

~/bin/codemd ../../toolkit/adalib/src/numbers.ads -x Define -l
0006 |    subtype OctalDigit_Type is Natural range 0 .. 7;
0007 |    subtype DecimalDigit_Type is Natural range 0 .. 9;
0008 |    subtype HexadecimalDigit_Type is Natural range 0 .. 15;
0009 | 
0010 |    package OctalVector_Pkg is new Ada.Containers.Vectors
0011 |      (Natural, OctalDigit_Type);
0012 |    subtype OctalDigits_Type is OctalVector_Pkg.Vector;
0013 | 
0014 |    package DecimalVector_Pkg is new Ada.Containers.Vectors
0015 |      (Natural, DecimalDigit_Type);
0016 |    subtype DecimalDigits_Type is DecimalVector_Pkg.Vector;
0017 | 
0018 |    package HexadecimalVector_Pkg is new Ada.Containers.Vectors
0019 |      (Natural, HexadecimalDigit_Type);
0020 |    subtype HexadecimalDigits_Type is HexadecimalVector_Pkg.Vector;

As shown above we define 3 custom data types - all numerics but with different range specifications. Each then requires an instantiation of the basic Containers.Vectors. The created packages have the support for dynamic arrays of the appropriate data type. The HexadecimalVectors_Pkg supports vectors of HexadecimalDigit_Type elements.

~/bin/codemd ../../toolkit/adalib/src/numbers.ads -x Basics -l
0024 |    function Convert (value : Natural) return OctalVector_Pkg.Vector;
0025 |    function Convert (value : Natural) return DecimalVector_Pkg.Vector;
0026 |    function Convert (value : Natural) return HexadecimalVector_Pkg.Vector;
0027 |    function Value (digs : OctalVector_Pkg.Vector) return Natural;
0028 |    function Value (digs : DecimalVector_Pkg.Vector) return Natural;
0029 |    function Value (digs : HexadecimalVector_Pkg.Vector) return Natural;
0030 |    function Image (digs : OctalVector_Pkg.Vector) return String;
0031 |    function Image (digs : DecimalVector_Pkg.Vector) return String;
0032 |    function Image (digs : HexadecimalVector_Pkg.Vector) return String;

We add our helper functions built around the basic vectors as shown above.

~/bin/codemd ../../toolkit/adalib/src/numbers.adb -x Convert -l
0033 |    function Convert (value : Natural) return DecimalVector_Pkg.Vector is
0034 |       result : DecimalVector_Pkg.Vector;
0035 |       valnow : Natural := value;
0036 |    begin
0037 |       if valnow < 10 then
0038 |          result.Append (valnow);
0039 |          return result;
0040 |       end if;
0041 |       loop
0042 |          result.Prepend (valnow mod 10);
0043 |          valnow := valnow / 10;
0044 |          if valnow < 1 then
0045 |             exit;
0046 |          end if;
0047 |       end loop;
0048 |       return result;
0049 |    end Convert;

In the above example, a number is split into its decimal digits the result being a dynamically sized array. The reverse operation is performed by retrieving individual elements:

~/bin/codemd ../../toolkit/adalib/src/numbers.adb -x Value -l
0097 |    function Value (digs : DecimalVector_Pkg.Vector) return Natural is
0098 |       use DecimalVector_Pkg;
0099 |       result : Natural := 0;
0100 |       curs   : Cursor  := digs.First;
0101 |    begin
0102 |       loop
0103 |          result := result * 10 + Element (curs);
0104 |          if curs = Last (digs) then
0105 |             exit;
0106 |          end if;
0107 |          curs := Next (curs);
0108 |       end loop;
0109 |       return result;
0110 |    end Value;

Alternatively, the library and thus the instantiated version, supports a cleaner and more elegant way to iterate over the elements:

~/bin/codemd ../../toolkit/adalib/src/numbers.adb -x Iterate -l
0199 |    procedure ShowNumber (cursor : NumbersVector_Pkg.Cursor) is
0200 |    begin
0201 |       Put (NumbersVector_Pkg.Element (cursor)'Image);
0202 |       Put (", ");
0203 |    end ShowNumber;
0204 | 
0205 |    procedure Show (num : NumbersVector_Pkg.Vector) is
0206 |    begin
0207 |       num.Iterate (ShowNumber'Access);
0208 |    end Show;

4.4 Fixed Point Data type

We have so far dealt with variations of Integer and Float data types; neither of these are convenient for a notion such as abundance. Somewhat unique to Ada is the fixed point data type which can take on discrete values but in an arbitrary range including less than 1 and is a better fit.

~/bin/codemd ../../toolkit/adalib/src/numbers.ads -x Abundance -l
0046 |    -- Ref: https://mathworld.wolfram.com/Abundancy.html
0047 |    type AbundancyRatio is delta 0.01 digits 6;
0048 |    function Abundance (num : Natural) return AbundancyRatio;

This is fundamendal to identifying Multiperfect numbers for example.

4.5 Applications

Even a casual visit to a site like Wolframalpha inspires one to start excursions. With a foundational support for computing the list of divisors and factors many applications become far simpler.

~/bin/codemd ../../toolkit/adalib/src/numbers.adb -x Factors -l
0227 | 
0228 |    function DivisorSum (num : Natural) return Natural is
0229 |       use NumbersVector_Pkg;
0230 |       divs   : constant Vector  := Divisors (num);
0231 |       result : Natural := 0;
0232 |       procedure Summer (cur : Cursor) is
0233 |       begin
0234 |          result := result + Element (cur);
0235 |       end Summer;
0236 |    begin
0237 |       divs.Iterate (Summer'Access);
0238 |       return result;
0239 |    end DivisorSum;
0240 | 
0241 |    function Abundance (num : Natural) return AbundancyRatio is
0242 |       result : AbundancyRatio;
0243 |       divsum : constant Natural := DivisorSum (num);
0244 |    begin
0245 |       result := AbundancyRatio (Float (divsum) / Float (num));
0246 |       return result;
0247 |    end Abundance;
0248 | 
0249 |    function Factors (num : Natural) return NumbersVector_Pkg.Vector is
0250 |       result  : NumbersVector_Pkg.Vector;
0251 |       curfac  : Natural := 2;
0252 |       curnum  : Natural := num;
0253 |       sqrtnum : Natural;
0254 |    begin
0255 |       sqrtnum := 1 + Natural (Sqrt (Float (num)));
0256 |       if curfac > sqrtnum then
0257 |          result.Append (num);
0258 |          return result;
0259 |       end if;
0260 |       loop
0261 |          if curnum mod curfac = 0 then
0262 |             result.Append (curfac);
0263 |             curnum := curnum / curfac;
0264 |             if curnum <= 1 then
0265 |                exit;
0266 |             end if;
0267 |          else
0268 |             curfac := curfac + 1;
0269 |          end if;
0270 |       end loop;
0271 |       return result;
0272 |    end Factors;
~/bin/codemd ../../toolkit/adalib/src/numbers.adb -x Prime -l
0287 |    function IsPrime (num : Natural) return Boolean is
0288 |       use Ada.Containers, NumbersVector_Pkg;
0289 |       facs : constant Vector := Factors (num);
0290 |    begin
0291 |       if facs.Length > 1 then
0292 |          return False;
0293 |       end if;
0294 |       return True;
0295 |    end IsPrime;

4.5.1 Example usage

Unary algorithms

~/bin/num U 5749 8128 272
================= Number  5749
Octal       13165 Octal back conversion Ok
Decimal     5749 Decimal back conversion Ok
Hexadecimal 1675 HexaDecimal back conversion Ok
Divisors            1,  5749, 
DivisorSum          5750
Prime Factors       5749, 
Prime No           Yes
Perfect No         No
MultiPerfect No    No
Kaprekar No        No
================= Number  8128
Octal       17700 Octal back conversion Ok
Decimal     8128 Decimal back conversion Ok
Hexadecimal 1fc0 HexaDecimal back conversion Ok
Divisors            1,  2,  4,  8,  16,  32,  64,  127,  254,  508,  1016,  2032,  4064,  8128, 
DivisorSum          16256
Prime Factors       2,  2,  2,  2,  2,  2,  127, 
Prime No           No
Perfect No         Yes
MultiPerfect No    Yes
Kaprekar No        No
================= Number  272
Octal       420 Octal back conversion Ok
Decimal     272 Decimal back conversion Ok
Hexadecimal 110 HexaDecimal back conversion Ok
Divisors            1,  2,  4,  8,  16,  17,  34,  68,  136,  272, 
DivisorSum          558
Prime Factors       2,  2,  2,  2,  17, 
Prime No           No
Perfect No         No
MultiPerfect No    No
Kaprekar No        No

and the Binary algorithms

~/bin/num B 5749 8128 8128 272 5749 272 30 140 135 819 2480 6200 2480 30 
========================= 5749 &  8128
GCD                 1
Friendly           FALSE
========================= 8128 &  272
GCD                 16
Friendly           FALSE
========================= 5749 &  272
GCD                 1
Friendly           FALSE
========================= 30 &  140
GCD                 10
Friendly           TRUE
========================= 135 &  819
GCD                 9
Friendly           TRUE
========================= 2480 &  6200
GCD                 1240
Friendly           TRUE
========================= 2480 &  30
GCD                 10
Friendly           TRUE

4.6 Stretch

  • Distinctness - given a vector, develop an algorithm to determine if there are any duplicate members.

  • Basic statistical support including functions for mean, standard deviation, kurtosis, skewness for vectors and correlation coefficient for multiple vectors will be a great lightweight addition to the toolkit.

  • https://en.wikipedia.org/wiki/ISBN describes multiple forms of ISBN and the algorithms for calculating the checksum. A function that validates an ISBN - including the check digit is a reasonable exercise in extending the numbers package outlined in this projectlet.

4.7 Sample Implementation

Repository: toolkit
Directory: examples/num