Hi, I’m writing a “Rotate_right” routine and I can’t get the cicling right. So if an increment is 10 and the bounds are 2…8, I must arrive at 4.
I’m stuck at this:
pragma Overflow_Mode (minimized);
pragma Ada_2022;
with Ada.Text_IO; use Ada.Text_IO;
procedure main with Spark_Mode is
type Buffer_type is array (Positive range <>) of Character;
function Rotate_Right (Buffer: Buffer_Type; D: Natural) return Buffer_type is
((for I in Buffer'Range =>
Buffer ((if I + D <= Buffer'Last then I + D else
Buffer'First + (D - Buffer'Last + I + 1) mod (Buffer'Length - 1)))));
begin
for I in 0..20 loop
put_line ("I " & " : " & Character'Val (9) & String (Rotate_Right ("je ne sais pas", I)));
end loop;
end main;
it eats letters. I could get a version where it works fine… until D = Buffer’Length, where an index gets out of boud, but I don’t get which one.
Can you help me ?
return Buffer_Type is
(for I in Buffer'Range =>
Buffer (if I + D <= Buffer'Last then I + D else
Buffer'First + (I + D - Buffer'First) mod Buffer'Length));
(a loop or conditional expression doesn’t need an extra paren if it’s immediately inside one already)
x mod Buffer’Length, when Buffer’Length is 14, returns values in 0 … 13, which is correct for adding to Buffer’First.
I like to keep the common parts of expressions like these in the same order (i.e. the I + D).
By the way, GNATpp (v24, v25) can’t handle this source:
Holy mother of bloody Christ.
Why can I waste hours on this trying all kinds of formulas while it was so simple ? What is it, just experience ? All those expressions feel like my early days of fighting the compiler when I didn’t know the grammar at all: I want to get somewhere, but can’t quite find the simplest way, which is usually not the one my mind comes up with.
You need not. In Ada arrays of same type are congruent if they have same length. It is also OK to overlap, the compiler chooses the direction of copying that does not corrupt the source.
declare
S : String := "1234567890";
begin
S (2..5) := S (6..9);
Put_Line (S);
S (7..9) := S (8..10);
Put_Line (S);
S (1..3) := S (2..4);
Put_Line (S);
end;
type Arr is array (Positive range <>) of ...;
subtype Arr_15_19 is Arr (15 .. 19);
X: Arr (1 .. 80);
Arr_15_19 (X (6 .. 10)) -- this is not a copy, but behaves like a constant
This is the best way to change the bounds of an array (or part of an array) in Ada.
It throws constraint_error when you set the subtype’s 'First to 0 (to apply modular arithmetic) so not usable for my purpose:
pragma Ada_2022;
procedure main is
function rotate (A: String; B: Natural) return String is
subtype String_from_zero is String (0 .. A'Length-1);
S renames String_from_zero (A);
begin
return (for I in S'Range => S ((I + B) mod S'Length));
end rotate;
begin
null;
end main;
Dmitry’s proposition is better and more aesthetic.
As I said, use slices. It is much simpler to understand and more efficient. BTW, I suggest pragma Ada_2005. That would preclude you from using indigestible new stuff like if- and for-expressions etc.
Anyway:
function Rotate_Right (A: String; B: Natural) return String is
begin
if A'Length = 0 then -- Empty string, sanity check
return A;
else
declare
Offset : constant Natural := B mod A'Length;
begin
if Offset = 0 then
return A;
else
return A (A'Last - Offset + 1..A'Last) &
A (A'First..A'Last - Offset);
end if;
end;
end if;
end Rotate_Right;
Huh, always the reactionary ! I would have a hard time proving anything without these ! And I wouldn’t have the pleasure to pervert your holy code:
function Rotate_Right (A: String; B: Natural) return String is
(if A'Length = 0 then A else
(declare Offset: constant Natural := B mod A'Length;
begin (if Offset = 0 then A else A(A'Last - Offset + 1..A'Last) &
A (A'First..A'Last - Offset))));
Ahahah. Thanks, I was looking for this but I just couldn’t make the operation in my head. Makes sense now, no actual circling. And it’s more efficient, assuming I would need to entire pages at once or thousands of time per minute
There’s a couple reasons. First your subtype String_from_Zero is a subtype of String which only allows Positive ranges (so no 0 indexing). You would need to do a custom character array type if you want to force the 0 index. However, that won’t be enough on its own as: Second is that for whatever reason the compiler is trying to assign a 0 index to the result type (I don’t know if this is a compiler bug or a language rule).
You can mod that function to fix both issues:
function rotate (A: String; B: Natural) return String is
type String_from_zero is array(0 .. A'Length-1) of Character;
S renames String_from_zero (A);
begin
return [for I in 1 .. A'Length => S((I-1+B) mod A'Length)];
end rotate;
That said you can do it a bit more simply:
function Rotate(A : String; B : Natural) return String is
(for I in A'Range => A((I-A'First+B) mod A'Length + A'First));
The way it works is you subtract out the 'First offset from the I index since I can start with values other than 1, then add B, then do the mod math, then add back in the 'First offset to get it back to the same index range as A (the compiler is already trying to force this and it is causing a Constraint_Error otherwise). Note you don’t need to worry about 0 length strings here either since the for loop handles that
or if you want to force a starting index of 1 in the result:
function Rotate(A : String; B : Natural) return String is
(for I in 1..A'Length => A((I-1+B) mod A'Length + A'First));
If you want to do it through slices:
function Rotate(A : String; B : Natural) return String is
(if B = 0 or A'Length = 0 then
A
else
A(A'First + (B mod A'Length) .. A'Last)
& A(A'First .. A'First + (B mod A'Length) - 1));
Ah I finally see in my head what happens… Damn it took me time. I guess there’s nothing but practice to make those inner muscles, if as it happens, I don’t really have the mental imagination by default. Well I think not many people when it comes to modular arithmetic or combinatorics.
No problem. Just a note that I modded the slice version to handle empty strings and large B values as it didn’t have the logic to guard against those previously.
Ring buffers are fun. The really interesting stuff begins when you start to write variable length items into it (or rotate, considering your task, which I still believe is totally useless). Then you have a problem that the thing may not fit into the buffer tail. You must cut the buffer and wrap it leaving some empty space at the end of the buffer. Then when you overwrite only a part of an old thing in the buffer, you need to throw all that thing out. So yet another hole appears. Normally you would work with two indices: First_In and First_Out rotating them at the buffer length modulo. Keep in mind that First_In = First_Out is ambiguous meaning ether buffer full or buffer empty. You get the picture. The typical use case is writing stream representations of some objects into a ring buffer of stream elements array. A nice thing about modulo is that you can use very large indices, e.g. 64-bit. This allows you to order items in the buffer combining index and sequence number in a single unit and detecting if an item is not in the buffer anymore.