🎉 Exercism Research is now launched. Help Exercism, help science and have some fun at research.exercism.io 🎉
Avatar of dgeiger

dgeiger's solution

to All Your Base in the Delphi Pascal Track

Published at Sep 04 2020 · 0 comments
Instructions
Test suite
Solution

Convert a number, represented as a sequence of digits in one base, to any other base.

Implement general base conversion. Given a number in base a, represented as a sequence of digits, convert it to base b.

Note

  • Try to implement the conversion yourself. Do not use something else to perform the conversion for you.

About Positional Notation

In positional notation, a number in base b can be understood as a linear combination of powers of b.

The number 42, in base 10, means:

(4 * 10^1) + (2 * 10^0)

The number 101010, in base 2, means:

(1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (0 * 2^2) + (1 * 2^1) + (0 * 2^0)

The number 1120, in base 3, means:

(1 * 3^3) + (1 * 3^2) + (2 * 3^1) + (0 * 3^0)

I think you got the idea!

Yes. Those three numbers above are exactly the same. Congratulations!

Testing

In order to run the tests for this track, you will need to install DUnitX. Please see the installation instructions for more information.

Loading Exercises into Delphi

If Delphi is properly installed, and *.dpr file types have been associated with Delphi, then double clicking the supplied *.dpr file will start Delphi and load the exercise/project. control + F9 is the keyboard shortcut to compile the project or pressing F9 will compile and run the project.

Alternatively you may opt to start Delphi and load your project via. the File drop down menu.

When Questions Come Up

We monitor the Pascal-Delphi support room on gitter.im to help you with any questions that might arise.

Submitting Exercises

Note that, when trying to submit an exercise, make sure the exercise file you're submitting is in the exercism/delphi/<exerciseName> directory.

For example, if you're submitting ubob.pas for the Bob exercise, the submit command would be something like exercism submit <path_to_exercism_dir>/delphi/bob/ubob.pas.

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you may request help from a mentor.

uAllYourBaseTests.pas

unit uAllYourBaseTests;

interface
uses
  DUnitX.TestFramework, System.SysUtils;

const
  CanonicalVersion = '2.3.0.1';

type
  [TestFixture]
  TAllYourBaseTest = class(TObject)
  private
    procedure CompareArrays(Array1, Array2: TArray<integer>);
  public
    [Test]
//    [Ignore('Comment the "[Ignore]" statement to run the test')]
    procedure single_bit_one_to_decimal;

    [Test]
    [Ignore]
    procedure binary_to_single_decimal;

    [Test]
    [Ignore]
    procedure single_decimal_to_binary;

    [Test]
    [Ignore]
    procedure binary_to_multiple_decimal;

    [Test]
    [Ignore]
    procedure decimal_to_binary;

    [Test]
    [Ignore]
    procedure trinary_to_hexadecimal;

    [Test]
    [Ignore]
    procedure hexadecimal_to_trinary;

    [Test]
    [Ignore]
    procedure integer_15_bit;

    [Test]
    [Ignore]
    procedure empty_list;

    [Test]
    [Ignore]
    procedure single_zero;

    [Test]
    [Ignore]
    procedure multiple_zeros;

    [Test]
    [Ignore]
    procedure leading_zeros;

    [Test]
    [Ignore]
    procedure input_base_is_one;

    [Test]
    [Ignore]
    procedure input_base_is_zero;

    [Test]
    [Ignore]
    procedure input_base_is_negative;

    [Test]
    [Ignore]
    procedure negative_digit;

    [Test]
    [Ignore]
    procedure invalid_positive_digit;

    [Test]
    [Ignore]
    procedure output_base_is_one;

    [Test]
    [Ignore]
    procedure output_base_is_zero;

    [Test]
    [Ignore]
    procedure output_base_is_negative;

    [Test]
    [Ignore]
    procedure both_bases_are_negative;
  end;

implementation
uses uAllYourBase;

procedure TAllYourBaseTest.binary_to_multiple_decimal;
begin
  CompareArrays([4, 2], TBase.Rebase(2, [1, 0, 1, 0, 1, 0], 10));
end;

procedure TAllYourBaseTest.binary_to_single_decimal;
begin
  CompareArrays([5], TBase.Rebase(2, [1, 0, 1], 10));
end;

procedure TAllYourBaseTest.both_bases_are_negative;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(-2, [1], -7);
    end, EArgumentOutOfRangeException, 'input base must be >= 2');
end;

procedure TAllYourBaseTest.CompareArrays(Array1, Array2: TArray<integer>);
var
  i: integer;
begin
  Assert.AreEqual(Length(Array1), Length(Array2), ' - Array lengths must be equal');
  for i := Low(Array1) to High(Array1) do
    Assert.AreEqual(Array1[i], Array2[i], format('Expecting element %d to = %d, Actual = %d',
      [i, Array1[i], Array2[i]]));
end;

procedure TAllYourBaseTest.decimal_to_binary;
begin
  CompareArrays([1, 0, 1, 0, 1, 0], TBase.Rebase(10, [4, 2], 2));
end;

procedure TAllYourBaseTest.empty_list;
begin
  CompareArrays([0], TBase.Rebase(2, [], 10));
end;

procedure TAllYourBaseTest.hexadecimal_to_trinary;
begin
  CompareArrays([1, 1, 2, 0], TBase.Rebase(16, [2, 10], 3));
end;

procedure TAllYourBaseTest.input_base_is_negative;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(-2, [1], 10);
    end, EArgumentOutOfRangeException, 'input base must be >= 2');
end;

procedure TAllYourBaseTest.input_base_is_one;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(1, [0], 10);
    end, EArgumentOutOfRangeException, 'input base must be >= 2');
end;

procedure TAllYourBaseTest.input_base_is_zero;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(0, [0], 10);
    end, EArgumentOutOfRangeException, 'input base must be >= 2');
end;

procedure TAllYourBaseTest.integer_15_bit;
begin
  CompareArrays([6, 10, 45], TBase.Rebase(97, [3, 46, 60], 73));
end;

procedure TAllYourBaseTest.invalid_positive_digit;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(2, [1, 2, 1, 0, 1, 0], 10);
    end, EArgumentOutOfRangeException, 'all digits must satisfy 0 <= d < input base');
end;

procedure TAllYourBaseTest.leading_zeros;
begin
  CompareArrays([4, 2], TBase.Rebase(7, [0, 6, 0], 10));
end;

procedure TAllYourBaseTest.multiple_zeros;
begin
  CompareArrays([0], TBase.Rebase(10, [0, 0, 0], 2));
end;

procedure TAllYourBaseTest.negative_digit;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(2, [1, -1, 1, 0, 1, 0], 10);
    end, EArgumentOutOfRangeException, 'all digits must satisfy 0 <= d < input base');
end;

procedure TAllYourBaseTest.output_base_is_negative;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(2, [1], -7);
    end, EArgumentOutOfRangeException, 'output base must be >= 2');
end;

procedure TAllYourBaseTest.output_base_is_one;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(2, [1, 0, 1, 0, 1, 0], 1);
    end, EArgumentOutOfRangeException, 'output base must be >= 2');
end;

procedure TAllYourBaseTest.output_base_is_zero;
begin
  Assert.WillRaise(procedure
    begin
      TBase.Rebase(10, [7], 0);
    end, EArgumentOutOfRangeException, 'output base must be >= 2');
end;

procedure TAllYourBaseTest.single_bit_one_to_decimal;
begin
  CompareArrays([1], TBase.Rebase(2, [1], 10));
end;

procedure TAllYourBaseTest.single_decimal_to_binary;
begin
  CompareArrays([1, 0, 1], TBase.Rebase(10, [5], 2));
end;

procedure TAllYourBaseTest.single_zero;
begin
  CompareArrays([0], TBase.Rebase(10, [0], 2));
end;

procedure TAllYourBaseTest.trinary_to_hexadecimal;
begin
  CompareArrays([2, 10], TBase.Rebase(3, [1, 1, 2, 0], 16));
end;

initialization
  TDUnitX.RegisterTestFixture(TAllYourBaseTest);
end.
unit uAllYourBase;

interface

uses
  System.Generics.Collections, System.SysUtils;

type
  TBase = class
    private
      class var FValue: Integer;
      class var FInBase: Integer;
      class var FDigits: TArray<Integer>;
      class var FOutBase: Integer;

      class procedure CalculateValue;
      class function GetValue: TArray<Integer>;

    public
      class function Rebase(InBase: Integer; Digits: TArray<Integer>; OutBase: Integer): TArray<Integer>;
  end;

implementation

{ TBase }

class procedure TBase.CalculateValue;
var
  Index: Integer;
  Digit: Integer;
begin
  // Start with the total being zero before converting to decimal
  FValue := 0;

  // We need to scan the digit array and process each digit
  for Index := 0 to Length(FDigits) - 1 do
    begin
      // Get the current digit
      Digit := FDigits[Index];

      // Is the digit valid for this base?
      if not (Digit in [0 .. FInBase - 1]) then
        // No, throw an exception
        raise EArgumentOutOfRangeException.Create('all digits must satisfy 0 <= d < input base');

      // We need to multiply the value by the base each time we process the
      // next digit ...
      FValue := FValue * FInBase;

      // and then add the digit to the value.
      FValue := FValue + Digit;
    end;
end;

class function TBase.GetValue: TArray<Integer>;
var
  OldValue: Integer;
  NewValue: Integer;
  Digits: TList<Integer>;
  Index: Integer;
begin
  // Create a list for the digits in the new base
  Digits := TList<Integer>.Create;

  // Get the decimal value we calculated earlier
  OldValue := FValue;

  // Start with the value in the new base being zero
  NewValue := 0;

  repeat
    // Get the low-order digit in the new base
    NewValue := OldValue mod FOutBase;

    // Add the digit to the list
    Digits.Add(NewValue);

    // Divide by the new base to get ready for the next digit
    OldValue := OldValue div FOutBase;

    // We do this until we have calculated all of the digits
  until OldValue = 0;

  // Since we added the digits starting with the low-order one, we
  // need to reverse the list before putting the digits in the Result array
  Digits.Reverse;

  // Set the size of the Result array
  SetLength(Result, Digits.Count);

  // For each digit, put it into the proper place in the Result array
  for Index := 0 to Digits.Count - 1 do
    Result[Index] := Digits.Items[Index];
end;

class function TBase.Rebase(InBase: Integer; Digits: TArray<Integer>;
  OutBase: Integer): TArray<Integer>;
begin
  // We need to make sure the base of the input digits is valid
  if InBase < 2 then
    // It isn't so throw an exception
    raise EArgumentOutOfRangeException.Create('input base must be >= 2');

  // We need to make sure the base of the output digits is valid
  if OutBase < 2 then
    // It isn't so throw an exception
    raise EArgumentOutOfRangeException.Create('output base must be >= 2');

  // Save the passed values into our object
  FInBase := InBase;
  FDigits := Digits;
  FOutBase := OutBase;

  // Convert the digits to a decimal value
  CalculateValue;

  // Return the decimal value converted to the new base
  Result := GetValue;
end;

end.

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?