I am trying to write an Ada equivalent to the split() method in Java or C++. I am to intake a string and an integer and output two seperate string values. For example:
split of "hello" and 2 would return:
"The first part is he
and the second part is llo"
The code I have is as follows:
-- split.adb splits an input string about a specified position.
--
-- Input: Astring, a string,
-- Pos, an integer.
-- Precondition: pos is in Astring'Range.
-- Output: The substrings Astring(Astring'First..Pos) and
-- Astring(Pos+1..Astring'Last).
--------------------------------------------------------------
with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Strings.Fixed;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada.Strings.Fixed;
procedure Split is
EMPTY_STRING : String := " ";
Astring, Part1, Part2 : String := EMPTY_STRING;
Pos, Chars_Read : Natural;
------------------------------------------------
-- Split() splits a string in two.
-- Receive: The_String, the string to be split,
-- Position, the split index.
-- PRE: 0 < Position <= The_String.length().
-- (Ada arrays are 1-relative by default)
-- Passback: First_Part - the first substring,
-- Last_Part - the second substring.
------------------------------------------------
function Split(TheString : in String ; Pos : in Integer; Part1 : out String ; Part2 : out String) return String is
begin
Move(TheString(TheString'First .. Pos), Part1);
Move(TheString(Pos .. TheString'Last), Part2);
return Part1, Part2;
end Split;
begin -- Prompt for input
Put("To split a string, enter the string: ");
Get_Line(Astring, Chars_Read);
Put("Enter the split position: ");
Get(Pos);
Split(Astring, Pos, Part1, Part2);
Put("The first part is ");
Put_Line(Part1);
Put(" and the second part is ");
Put_Line(Part2);
end Split;
The main part I am having trouble with is returning the two separate string values and in general the whole split() function. Any pointers or help is appreciated. Thank you
Instead of a function, consider making Split a procedure having two out parameters, as you've shown. Then decide if Pos is the last index of Part1 or the first index of Part2; I've chosen the latter.
procedure Split(
TheString : in String; Pos : in Integer;
Part1 : out String; Part2 : out String) is
begin
Move(TheString(TheString'First .. Pos - 1), Part1);
Move(TheString(Pos .. TheString'Last), Part2);
end Split;
Note that String indexes are Positive:
type String is array(Positive range <>) of Character;
subtype Positive is Integer range 1 .. Integer'Last;
Doing this is so trivial, I'm not sure why you'd bother making a routine for it. Just about any routine you could come up with is going to be much harder to use anyway.
Front_Half : constant String := Original(Original'first..Index);
Back_Half : constant String := Original(Index+1..Original'last);
Done.
Note that static Ada strings are very different than strings in other languages like C or Java. Due to their static nature, they are best built either inline like I've done above, or as return values from functions. Since functions cannot return more than one value, a single unified "split" routine is just plain not a good fit for static Ada string handling. Instead, you should either do what I did above, call the corresponding routines from Ada.Strings.Fixed (Head and Tail), or switch to using Ada.Strings.Unbounded.Unbounded_String instead of String.
The latter is probably the easiest option, if you want to keep your Java mindset about string handling. If you want to really learn Ada though, I'd highly suggest you learn to deal with static fixed Strings the Ada way.
From looking over your code you really need to read up in general on the String type, because you're dragging in a lot of expectations in from other languages on how to work with them--which aren't going to work with them. Ada's String type is not one of its more flexible features, in that they are always fixed length. While there are ways of working around the limitations in a situation such as you're describing, it would be much easier to simply use Unbounded_Strings.
The input String to your function could remain of type String, which will adjust to the length of the string that you provide to it. The two output Unbounded_Strings then are simply set to the sliced string components after invoking To_Unbounded_String() on each of them.
Given the constraints of your main program, with all strings bounded by the size of EMPTY_STRING. the procedure with out parameters is the correct approach, with the out parameter storage allocated by the caller (on the stack as it happens)
That is not always the case, so it is worth knowing another way. The problem is how to deal with data whose size is unknown until runtime.
Some languages can only offer runtime allocation on the heap (via "new" or "malloc") and can only access the data via pointers, leaving a variety of messy problems including accesses off the end of the data (buffer overruns) or releasing the storage correctly (memory leaks, accessing freed pointers etc)
Ada will allow this method too, but it is usually unnecessary and strongly discouraged. Unbounded_String is a wrapper over this method, while Bounded_String avoids heap allocation where you can accept an upper bound on the string length.
But also, Ada allows variable sized data structures to be created on the stack; the technique just involves creating a new stack frame and declaring new variables where you need to, with "declare". The new variables can be initialised with function calls.
Each function can only return one object, but that object's size can be determined at runtime. So either "Split" can be implemented as 2 functions, returning Part1 or Part2, or it can return a record containing both strings. It would be a record with two size discriminants, so I have chosen the simpler option here. The function results are usually built in place (avoids copying).
The flow in your example would require two nested Declare blocks; if "Pos" could be identified first, they could be collapsed into one...
procedure Split is
function StringBefore( Input : String; Pos : Natural) return String is
begin
return Input(1 .. Pos-1);
end StringBefore;
function StringFrom ...
begin
Put("To split a string, enter the string: ");
declare
AString : String := Get_Line;
Pos : Natural;
begin
Put("Enter the split position: ");
Get(Pos);
declare
Part1 : String := StringBefore(AString, Pos);
Part2 : String := StringFrom(AString, Pos);
begin
Put("The first part is ");
Put_Line(Part1);
Put(" and the second part is ");
Put_Line(Part2);
end; -- Part1 and Part2 are now out of scope
end; -- AString is now out of scope
end Split;
This can obviously be wrapped in a loop, with different size strings each time, with no memory management issues.
Look at the Head and Tail functions in Ada.Strings.Fixed.
function Head (Source : in String; Count : in Natural; Pad : in Character := Space) return String;
function Tail (Source : in String; Count : in Natural; Pad : in Character := Space)
return String;
Here's an approach that just uses slices of the string.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
procedure Main is
str : String := "one,two,three,four,five,six,seven,eight";
pattern : String := ",";
idx, b_idx : Integer;
begin
b_idx := 1;
for i in 1..Ada.Strings.Fixed.Count ( Source => str, Pattern => pattern ) loop
idx := Ada.Strings.Fixed.Index( Source => str(b_idx..str'Last), Pattern => pattern);
Put_Line(str(b_idx..idx-1)); -- process string slice in any way
b_idx := idx + pattern'Length;
end loop;
-- process last string
Put_Line(str(b_idx..str'Last));
end Main;
Related
If I create a subprogram of type function that for instance orders you to type a string of a particular length and you type Overflow, it's supposed to type the last half of the string, so in this case it would be flow. But on the other end if I type an odd number of characters like Stack it's supposed to type the last half of the string + the middle letter, so in this case it would be "ack".
Let me make it clearer (text in bold is user input):
Type a string that's not longer than 7 characters: Candy
The other half of the string is: ndy
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
function Split_String (S : in String) return String is
begin
Mid := 1 + (S'Length / 2);
return S(Mid .. S'Last);
end Split_String;
S : String(1 .. 7);
I : Integer;
begin
Put("Type a string that's no longer than 7 characters: ");
Get_Line(S, I);
Put(Split_String(S));
end Split;
Let me tell you how I've been thinking. So I do a Get_Line to see how many characters the string contains. I then put I in my subprogram to determine if its evenly dividable by two or not. If it's dividable by two, the rest should be 0, thus it'll mean that typing out the other half of the string + THE MIDDLE CHARACTER is not needed. If in all the other cases, it's not dividable by two I have to type out the other half of the string + the middle character. But now I stumbled upon a big problem in my main program. I don't know how type out the other half of a string. If a string contains 4 words I can just type out Put(S(3 .. 4); but the thing is that I don't know a general formula for this. Help is appreciated! :) Have a good day!
You need a more general approach to your problem. Also, try to understand how Get_Line works for you.
For example, if you declare an input string with a large size such as
Input : String (1..1024);
You will have a string large enough to work with any likely input values.
Next, you need a variable to indicate how many characters were actually read by Get_Line.
Length : Natural;
The data returned by Get_Line will then be in the slice of the input string designated as
Input (1 .. Length);
Pass that slice to your function to return the second half of the string.
function last_half(S : string) return string;
last_half(Input(1..Length));
Now all you need is to calculate the last half of the string passed to the function last_half. The function will output a slice of the string passed to it. To find the first index of the last half of the input string you must perform the calculation
mid : Positive := 1 + (S'length / 2);
Then simply return the string S(mid .. S'Last).
It appears that the goal of this exercise is to learn how to use array slices. Concentrate on how slices work for you in the problem and the solution will be very simple.
One possible solution is
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
Input : String (1 .. 1_024);
Length : Natural;
function last_half (S : in String) return String is
Mid : Positive := 1 + (S'Length / 2);
begin
return S (Mid .. S'Last);
end last_half;
begin
Put ("Enter a string: ");
Get_Line (Input, Length);
Put_Line (Input (1 .. Length) & " : " & last_half (Input (1 .. Length)));
end Main;
Study how the solution uses array slices on the return value of Get_Line and on the parameter for the function last_half and on its return statement. It is also important to remember that the type String is defined as an unbounded array of character. This means that every slice of a string is also a string.
type String is array ( Positive range <> ) of Character;
Aside from being an untidy mess, your latest code edit (as of 20:11 GMT on 15 Nov 2021) doesn’t even compile. Please don’t show us code like this! (unless, of course, that’s the problem).
I’d like to strongly suggest this alternate way of inputting strings:
declare
S : constant String := Get_Line;
begin
-- do things with S, which is exactly as long as
-- the input you typed: no undefined characters at
-- the end to confuse the result, no need to worry
-- about overrunning an input buffer
end;
With this change, and obvious syntactic changes, your current code will do what you want.
Given a field/string like 'G,H,1,AA,T,AAA,1,E,A,H,....'. The characters can be in any combination/order.
How do I search that string and return True when searching for just 'A' or 'AA'?
i.e. If doing a search for say 'A', it should only find the 'A' between the E & H.
Regards & TIA,
Ian
You can simply split your string into an array by your delimiter and search in that array, e.g.
function FindItem(const List, Item: string): Boolean;
var
SArr: TArray<string>;
S: string;
begin
Result := False;
//Separators could also be a parameter
SArr := List.Split([',']);
for S in SArr do
begin
//use S.Trim if needed
//use AnsiSameText(S, Item) for case insensitive check
if Item = S then
Exit(True);
end;
end;
If you need to search for multiple items in your data, you might want to sort the array and use a binary search.
TArray.Sort<string>(SArr);
Result := TArray.BinarySearch(SArr, Item, Tmp);
Another approach would be using regular expressions with word boundaries to search for whole words only
Result := TRegex.IsMatch(List, '\bA\b');
Split this string into a list, for example with TStringList.CommaText (alternatively, into an array with StrUtils.SplitString()).
Then, just walk through the list and check every string (or use TStrings.IndexOf() - note: it uses CaseSensitive property, as Remy mentioned in comments).
If you are going to make many queries for the same list - sort it and use an effective binary search (TStringList.Find()).
(Translated from German to Englisch)
I need help in this exercise :
Thread: String processing The user can make simple changes to an input sentence.
conditions
The program displays a menu for the user to select the following action. This is also displayed again after the action has been completed until the user terminates the program (a loop is therefore required).
The menu contains the following items, which should be executed when the specified letter is entered:
A. Enter the sentence
B. Determine the number of words
C. Determine the number of characters that are less than their sequence character
D. Replace all the words in the sentence with their uppercase initials
X. end
If the user enters a different letter, nothing happens or the menu is output again.
If the menu item A is selected, a prompt is issued to enter a set which is read into a string variable. This variable can not be changed by the actions of menu items B, C and D! Possibly. A copy of the set has to be prepared beforehand in another string variable.
In menu point B the number of all words in the block is to be counted. For simplicity, you can assume that there is always one space between two words. At the beginning and end of the sentence there are no spaces. The number of words is output after the calculation (e.g., "The set is 4 words").
If the user executes menu item C, the set is traversed character-by-character, and for each character it is checked whether it is smaller than its trailing character. Here is a simple character comparison (you can also write directly something like '1' <'d'). The number of characters so found is then output (e.g., "13 characters found in the sentence less than the trailing character").
In menu item D, the sentence is traversed and every word contained in it is replaced by its upper-case initial character. The capitalization is of course only made if the first character is a letter, otherwise the character remains unchanged. You can assume that the sentence never starts or ends with a space. Between two words there is always exactly one space and so it should be between the initial letters. For example, from "123 good mood" becomes "1 G L".
It is not permissible here to build up a completely new string piece by piece! Instead, you should work in a loop on a copy of the original sentence with pos, copy, length, delete and insert! It is also forbidden to "gather" the initial characters all at the beginning or end of the string; These should be inserted directly into the string at the position of the corresponding word!
Furthermore, a string can not be accessed at menu point D, because the work with string routines is to be practised explicitly here. Menu items B, C and D may only be selectable if a record has already been entered. Otherwise nothing happens or a fault message is entered when entering B, C or D in the menu and the menu is output again.
Each call to the menu items B, C or D will always work on the original set entered by the user and not on a set that has already been altered by previously executed menu items!
By entering the menu item A again, the entered block can be overwritten by a new one.
With an 'X' the user can terminate the program.
Use wherever it is the predefined string functions and do not write it yourself with difficulty loops, etc.! However, the use of the strreplace or reverseString functions is forbidden!
Here's my work till now, I only have problems with part D:
program Project2;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
const
lz = ' ';
var
Satz: string;
Buchstabe: char;
i, p, j, zaehler2, index, count: integer;
writeln('A) Write Sentence');
readln(Satz);
'D':
begin
index := 2;
insert(lz, Satz, length(Satz)+1);
count := (pos(lz,Satz));
repeat
delete(Satz, index,(count - index));
index := index + 2;
count := pos(lz,copy(Satz,index,(length(Satz)-index)))+index-1;
until ;
writeln(uppercase(Satz));
end
I'm glad you've found your own solution, well done!
While you've been doing that, I've been writing the answer below and, as I
have finished it, I thought I'd post it here to show you another way
to go about the problem of extracting words from a string. There are
dozens of ways of doing that task, but I hope the one I've used is fairly
easy to follow.
Maybe the reason you were having a problem with this is that your string-indexing
expressions are a bit too complicated. I bet if you come back to your code in 6 months
it will take you a while to figure out what it is supposed to be doing and longer
to tell whether it is actually doing it. The key to avoiding problems like that
is to break your code up into chucks which are easier to follow and easier
to test. So instead of just telling you what your repeat condition should be,
I'll show you a way which is easier to follow.
The first thing to do is to extract a singe word from the input. So, first thing
I've written is a function, ExtractFirstWord which returns the first word in
the input string, whether or not the input includes spaces, and also returns
a Remainder string which is what is left or the input string after the first
word (and any spaces immediately following it have been removed). This is done
using some simple while loops which are coded to skip over the leading spaces
and then build a string from the non-space characters which follow.
Code
const
lz = ' ';
var
Satz: string;
FirstWord : String;
Remainder : String;
function ExtractFirstWord(const InputStr : String; var Remainder : String) : String;
var
P : Integer;
WordStart : Integer;
begin
Result := '';
P := 1;
// The following skips over any spaces at the start of InputStr
while (P <= Length(InputStr)) and (InputStr[P] = lz) do
Inc(P);
// Now we know where the first word starts
WordStart := P;
// Now we can get the first word, if there is one
while (P <= Length(InputStr)) and (InputStr[P] <> lz) do
Inc(P);
Result := Copy(InputStr, WordStart, P - WordStart);
Remainder := Copy(InputStr, P, Length(InputStr));
// the following is one way to remove spaces at the start of Remainder
while (Length(Remainder) > 0) and (Remainder[1] = lz) do
Delete(Remainder, 1, Length(lz));
// instead you could do something simlar to the first `while` loop above
end;
begin
Satz := ' cat dog ';
repeat
FirstWord := ExtractFirstWord(Satz, Remainder);
FirstWord := UpperCase(FirstWord);
Satz := Remainder;
writeln('First word: ', FirstWord, ' remainder: ', Remainder);
until Remainder = '';
readln;
end.
This particular way of doing it is not an ideal fit with the other requirements
specified in your task but should be easily adaptable to them. E.g, the upper-casing of words could be done "in place" on the input string by upper-casing the current character of it in the second While loop.
Btw, if you are using Delphi or Free Pascal/Lazarus, there is a much simpler
way of extracting the words from a string. It uses a TStringList. Try
looking it up in the online help and have a thing about how you might use it
to do the task.
I am trying to finish up a homework assignment, and am down to the last part. First, I'll show you the type that I am dealing with:
TYPE Book_Collection IS
RECORD
Books : Book_Collection_Array;
Max_Size : Integer;
Size : Integer;
END RECORD;
TYPE Book_Type IS
RECORD
Title,
Author,
Publisher : Title_Str;
Year : Year_Type;
Edition : Natural;
Isbn : Isbn_Type;
Price : Dollars;
Stock : Natural;
Format : Format_Type;
END RECORD;
Book_Collection_Array is an array of book_type. These are private types, so the array is bounded (1..200).
There is a function called ToString in a separate package that was provided to us, that takes a book_type as input, and returns a string of all the elements of book_type. What I need to create is a function that takes book_collection is a parameter, and returns a string concatenating all of the strings that are returned by the ToString function that was provided, for the book_types that exist in that book_collection. I have made multiple attempts, but am constantly getting range check failures. Can anyone point me in the right direction?
*Edit:
Thank you to both of you for your help. I went the route of using an unbounded string, and appending each string to it, then declaring an output string and setting it as a constant string equal to the the To_String of the unbounded_string.*
I'll give you a hint.
Ada strings ideally aren't treated or handled much like C or Java strings at all. C strings count on a trailing nul (0) character to designate the end of data in a buffer. Java strings keep track of their own length, and will dynamically reallocate themselves to keep to the proper length if need be. So typical string-handling idioms in those languages think nothing of progressively modifying a string variable.
Ada strings instead are expected to be perfectly-sized when created. Most routines will assume that every element in a string array contains valid character data, and any destination string you assign data into will be perfectly sized to hold it. If that isn't the case, usually an exception is raised (and most likely your program crashes).
There are several ways to deal with this when you are building a string. One way is to create a really big string object as a buffer, and keep a separate length variable to tell your code how much data is really in there at all times. Then when you call Ada string routines you can feed them just the slice of data from the string that is valid. eg: Put_line (My_New_String(1..My_String_Length));
A better way is to just deal with perfectly-sized constant strings. For example, if you want to tack String1 and String2 together, the safe Ada way to do this is:
My_New_String : constant String := String1 & String2;
Then if you later want a string that is this string with String3 tacked on:
My_New_New_String : constant String := My_New_String & String3;
For more info on this, I suggest you look at some of the links over on the right side of this browser window under the heading "Related". I see a lot of good stuff in there.
I am new to Ada and currently trying to write a simple program involving an if-else if statement. The code is as follows:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Year_Codes is
Year : String(1..9) := " ";
CharsRead : Natural;
function YearCode(Name : in String) return Integer is
begin
if(Name = "freshman")then
return 1;
elsif(Name = "sophomore")then
return 2;
elsif(Name = "junior")then
return 3;
elsif(Name = "senior")then
return 4;
else
return 0;
end if;
end YearCode;
begin
Put("Enter your academic year: "); -- Prompt for input
Get_Line(Year, CharsRead); -- Input
Put( YearCode(Year) ); -- Convert and output
New_Line;
end Year_Codes;
I am getting 0 for every answer. Any input on what I am doing wrong?
The "=" operation on strings compares the entire strings. If the user's input is "freshman", the value of Name will be "freshman ", not "freshman". Read the documentation for the Get_Line procedure.
You should probably pass YearCode a slice of the Year string, not the entire string; CharsRead tells you what that slice should be.
Specifically, the call should be:
Put( YearCode(Year(Year'First..CharsRead)) );
Here's a case-insensitive version using attributes:
function YearCode(Name : in String) return Integer is
Type Class is (Freshman, Sophmore, Junior, Senior);
begin
Return 1 + Class'Pos(Class'Value(Name));
exception
When CONSTRAINT_ERROR => Return 0;
end YearCode;
With that extra character in your buffer, it looks to me like you are thinking of strings in C terms. You need to stop that. Of everything in the language, string handling is the most different between Ada and C.
While C strings are null terminated, Ada strings are not. Instead, an Ada string is assumed to be the size of the string array object. Its a simple difference, but it has enormous consequences in how you handle strings.
I go into this a bit in my answer to How to I build a string from other strings in Ada? The basic gist is that in Ada you always try to build perfectly-sized string objects on the fly.
Sadly, Text_IO input is one place that has traditionally made that really hard, due to its string buffer-based input. In that case, you are forced to use an overly large string object as a buffer, and use the returned value as the end of the defined area of the buffer, as Keith showed.
However, if you have a new version of the compiler, you can use the function version of Get_Line to fix that. Simply change your middle two lines to:
Put( YearCode(Get_Line) );