2

I have the following code for creating superscript versions of the digits '0' to '9' and the signs '+' and '-'

const
  Digits = ['0' .. '9'];
  Signs = ['+', '-'];
  DigitsAndSigns = Digits + Signs;

function SuperScript(c: Char): Char;
{ Returns the superscript version of the character c
  Only for the numbers 0..9 and the signs +, - }
const
  SuperDigits: array ['0' .. '9'] of Char = ('⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹');
begin
  if CharInSet(c, Digits) then
    Result := SuperDigits[c]
  else if c = '+' then
    Result := '⁺'
  else if c = '-' then
    Result := '⁻'
  else
    Result := c;
end;

This works, but is not very elegant. Ideally I would like to have something like

  SuperDigits: array [DigitsAndSigns] of Char = ('⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹', '⁺', '⁻');

but this does not even compile.

Is it somehow possible to create and preset an array element for every element in the set?

I am aware that I could use more heavy components like TDictionary, but (if possible) I would like to use sets or enumerations.

Matej
  • 442
  • 3
  • 9
  • I wonder why you won't continue with `else if CharInSet(c, Signs) then Result := SuperSigns[c]` but insist on single `if`s. Also performance wise a `case c of` would be best. – AmigoJack Feb 15 '23 at 22:14

2 Answers2

5

Actually there is a solution to achieve what you want, but perhaps not what you expected:

type
  SuperDigit = record
  private
    class function GetItem(const C: Char): Char; static;
  public
    class property Item[const C: Char]: Char read GetItem; default;
  end;

class function SuperDigit.GetItem(const C: Char): Char;
const
  cDigitsAndSigns = '0123456789+-';
  cSuperScripts   = '⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻';
begin
  Result := C;
  var idx := Pos(C, cDigitsAndSigns);
  if idx >= 0 then
    Result := cSuperScripts[idx];
end;

With this declaration your can write something like this:

procedure ToSuperScript(var S: string);
begin
  for var I := 1 to Length(S) do
    S[I] := SuperDigit[S[I]];
end;
Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • The advantage of this way is that `cDigitsAndSigns` could also be as flexible as `= Digits + Signs`, combining multiple strings at will. – AmigoJack Feb 15 '23 at 23:38
  • This gets very close to what I want. Performance-wise the solution by Andreas using case ... might be better, but for smaller sets this is a very elegant solution. – Matej Feb 16 '23 at 08:50
  • The case needs as many compares as the Pos. I doubt that the performance difference can be measured, let alone noticed. If performance is key you should consider assembler or a lookup table. – Uwe Raabe Feb 16 '23 at 10:05
2

Is it somehow possible to create and preset an array element for every element in the set?

No.

This is fundamentally impossible because the set is an unordered container.

In your case, Digits + Signs is exactly the same thing as Signs + Digits, so how could you possibly know in what order to enumerate the elements?

Also, it might be worth pointing out that the brackets in

const
  Digits = ['0' .. '9'];

are not of the same kind as the brackets in

array ['0' .. '9'] of Char 

The brackets in Digits really do make a set, but the static array syntax has nothing to do with sets. A static array is indexed by an ordinal type.

In theory, you could create an enumerated type with your characters, but then you need to convert an input character to your enumerated type, and then back to the mapped character. So this is not convenient.

In your particular case, you have a mapping Char → Char. The underlying Unicode code points aren't really nice enough to facilitate any clever tricks (like you can do with ASCII lower case -> upper case, for example). In fact, the superscript digits are not even contiguous! So you have no choice but to do a plain, data-based mapping of some sort.

I'd just use a case construct like in UnicodeSuperscript here:


function UnicodeSuperscript(const C: Char): Char;
begin
  case C of
    '0':
      Result := '⁰';
    '1':
      Result := '¹';
    '2':
      Result := '²';
    '3':
      Result := '³';
    '4':
      Result := '⁴';
    '5':
      Result := '⁵';
    '6':
      Result := '⁶';
    '7':
      Result := '⁷';
    '8':
      Result := '⁸';
    '9':
      Result := '⁹';
    '+':
      Result := '⁺';
    '-', '−':
      Result := '⁻';
  else
    Result := C;
  end;
end;

In terms of elegance, I guess you may want to separate data from logic. One (overkill and slower!) approach would be to store a constant array like in

function UnicodeSuperscript(const C: Char): Char;
const
  Chars: array[0..12] of
    record
      B,
      S: Char
    end
    =
    (
      (B: '0'; S: '⁰'),
      (B: '1'; S: '¹'),
      (B: '2'; S: '²'),
      (B: '3'; S: '³'),
      (B: '4'; S: '⁴'),
      (B: '5'; S: '⁵'),
      (B: '6'; S: '⁶'),
      (B: '7'; S: '⁷'),
      (B: '8'; S: '⁸'),
      (B: '9'; S: '⁹'),
      (B: '+'; S: '⁺'),
      (B: '-'; S: '⁻'),
      (B: '−'; S: '⁻')
    );
begin
  for var X in Chars do
    if C = X.B then
      Exit(X.S);
  Result := C;
end;
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384