Turbo Pascal for DOS Tutorial by Glenn Grotzinger Part 12: Stacks and Queues copyright(c) 1995-96 by Glenn Grotzinger Solution to part 11's problem.... program part11; uses dos; { Lists files and data in a ZIP file. } type ziplocfileheader = record zipver: integer; bitfield: word; compmethod: integer; packtime: longint; crc32: longint; compsize: longint; uncompsize: longint; filelen: integer; xtralen: integer; end; zipcds = record ver: byte; hostos: byte; verxtr: byte; targos: byte; bitfield: word; compmethod: integer; packtime: longint; crc32: longint; compsize: longint; uncompsize: longint; filelen: integer; xtralen: integer; comtlen: integer; disknum: integer; intattr: integer; extattr: longint; roffset: longint; end; endcdsheader = record numdisk: integer; numdiska: integer; numfiles: integer; entries: integer; size: longint; ofs: longint; comtlength: integer; end; var zipfile: file; outfile: text; locfile: ziplocfileheader; dirstr: zipcds; endcds: endcdsheader; filechar: char; filelength: array[1..20] of char; id: array[1..4] of char; i: integer; totsize, compsize, uncompsize: longint; param1, param2: string; { Example structure of a ZIP file. localheader1 loccompresseddata1 localheader2 loccompresseddata2 centraldirectorystructure1 centraldirectorystructure2 endofcentraldirectorystructure } procedure readzip; { ZIP is a random data binary file. We read the ID to check it, then differentiate it from there. We also devise a test of whether the "ZIP file" is a valid ZIP file here. } begin blockread(zipfile, id, sizeof(id)); totsize := totsize + 4; { totsize is a seek index } if (id[1] <> 'P') and (id[2] <> 'K') then begin writeln('Corrupted ZIP file, or not a ZIP file.'); halt(1); end; case id[3] of #05: begin blockread(zipfile, endcds, sizeof(endcds)); totsize := totsize + sizeof(endcds); end; #03: begin blockread(zipfile, locfile, sizeof(locfile)); totsize := totsize + sizeof(locfile); end; #01: begin blockread(zipfile, dirstr, sizeof(dirstr)); totsize := totsize + sizeof(dirstr); end; end; end; function zero(int: integer):string; {places zero on the beginning of a number if it's < 10 } var strng: string; numerr: integer; begin str(int, strng); if int < 10 then strng := '0' + strng; zero := strng; end; function strip(str: string):string; { removes null characters from random-read filename } var tstr: string; i: integer; begin tstr := ''; for i := 1 to length(str) do if str[i] <> #0 then tstr := tstr + str[i]; strip := tstr; end; function exist(filename: string):boolean; { file existence test } var thefile: text; begin assign(thefile, filename); {$I-}reset(thefile);{$I+} if IOResult <> 0 then exist := false else begin exist := true; close(thefile); end; end; procedure header(filename: string); { writes header of data } var s: string; begin writeln(outfile, 'Files contained in ':41, filename); writeln(outfile); writeln(outfile, 'NAME', 'COMP-SIZE':21, 'DATE':9,'TIME':12, 'UNCOMP-SIZE':15, 'COMP-METHOD':13); fillchar(s, 75, '-'); { fillchar fills a set of contiguous bytes of any type defined by the position s is in, for x number of spaces (75), with a char- acter represented by - } s[0] := #74; { set length byte } writeln(outfile, s); end; procedure footer(compsize, uncompsize: longint); { write footer of data } var s: string; begin fillchar(s, 75, '-'); s[0] := #74; writeln(outfile, s); writeln(outfile, compsize:22, uncompsize:34); writeln(outfile); writeln(outfile, endcds.numfiles, ' files; Effective compression', ' ratio: ', (compsize / uncompsize)*100 :0:1, '%'); end; procedure writehelp; { help for incorrect usage } begin writeln('ZIPLIST '); halt(1); end; procedure writedata(locfile: ziplocfileheader); var dt: datetime; begin unpacktime(locfile.packtime, dt); { this handles the MS-DOS time format } writeln(outfile, strip(filelength):12, locfile.compsize:10, zero(dt.month):7,'-',zero(dt.day),'-',dt.year, zero(dt.hour):6,':', zero(dt.min), locfile.uncompsize:10, locfile.compmethod:12); end; begin if paramcount <> 2 then { incorrect parameters? } writehelp; totsize := 0;compsize := 0;uncompsize := 0; param1 := paramstr(1); param2 := paramstr(2); assign(zipfile, param1); if exist(param1) = false then begin writeln(param1, ' does not exist!'); halt(1); end; reset(zipfile, 1); assign(outfile, param2); rewrite(outfile); header(param1); readzip; while id[3] = #03 do { process local file headers and compressed data } begin blockread(zipfile, filelength, locfile.filelen); compsize := compsize + locfile.compsize; uncompsize := uncompsize + locfile.uncompsize; writedata(locfile); totsize := totsize + locfile.filelen; totsize := totsize + locfile.xtralen; fillchar(filelength, sizeof(filelength),#0); seek(zipfile, totsize); totsize := totsize + locfile.compsize; seek(zipfile, totsize); readzip; end; while id[3] = #01 do { process central directory structure } begin totsize := totsize + dirstr.filelen; seek(zipfile, totsize); totsize := totsize + dirstr.xtralen; seek(zipfile, totsize); readzip; end; footer(compsize, uncompsize); writeln(outfile); { handle ZIP comment } if endcds.comtlength = 0 then writeln(outfile, 'No comment in ZIP file.') else for i := 1 to endcds.comtlength do begin blockread(zipfile, filechar, sizeof(filechar)); write(outfile, filechar); end; close(zipfile); close(outfile); end. Hello. Basically, we are dealing in this part with specialized types of data structures called stacks and queues. Stacks ====== A stack is best analagous to a stack of papers on a desk, only able to either place a sheet on the top of the stack, or take a sheet off the top of the stack. It is generally defined as a record format, which can take on the following: stack = record elements: array[1..maxstacksize] of ; capacity: integer; {range of 0..maxstacksize} end; It is best described as a last in/ first out data structure, much like the sheet of paper analogy. The elements array holds whatever information we need, while capacity tells us how many elements are still in the array. The best description I can use for this and queues is that it's a limited defined access structure. I will now describe the basic actions of working with stacks. The best way to think about coding this is to use the stack of paper as a view- point, and using the sample stack record above. To initialize or effectively kill a stack currently in usage is to set capacity to 0. We can do this, because all work with a stack ultimately comes down to usage of capacity as an index. If we have 0 sheets of paper on an imaginary stack, essentially, the stack is empty. Now, let's add a sheet of paper to our imaginary stack. We recognize that there is one more sheet of paper on the stack now. Then we have to place that sheet on the stack...also, we have to keep in mind if we have hit our arbitrarily set limit of sheets on the stack, and notify of such thing. What now, if we have to remove the sheet of paper from our imaginary stack of paper? We would need to check if we had a sheet of paper to remove. Then after that, we would need to pull the sheet off of the stack, then recognize that we removed the sheet. How do we recognize if we have a full stack or an empty stack? Look at the value of capacity. If it's 0, then it's empty. If it's the value for the maximum capacity of the stack we decide, then we know it's full. Basically, in the last four paragraphs, I have, in discussing the actions of working with stacks, have given you all the pseudocode you need to be able to come up with the code to work with a stack. It is basically good to develop all of these components as procedures. If you come across a problem where a last in, first out solution is needed, a stack may be the proper way to go. Queues ====== A queue is set up much like a line at the local shop to pay the merchant at the cash register. The first element in is the first element out. Much like a stack, a queue may be defined as a record format. queue = record elements: array[1..maxqueuesize] of ; front, back: integer; count: integer; end; Notice that the differences we see here, are that to take from the front of the so called line we need to know where the front is in the order. The same idea occurs with the back. We need to know where the back is in the array. Count just helps us in knowing how many values we currently have in the queue. To start up a queue, basically, we need to start the front at 1, the rear at the maximum size, and count at zero. To check whether it's full or empty is basically set through count. Check count to be zero for a queue to be empty and maxsize for a queue to be full. One sticky mess comes up when we try to add an element to the back of the line. How do we keep track of things? We increment the back unless it's maxqueuesize, then we set it to 1, essentially, to continue reusing the array storage as a loop.... To add to the back of the line, we do like we did with the stack. We add the element to the back, then we increment the back count as described above. To take from the front of the line, we pull the element from the front of the queue, and then increment the front count as described above. Basically, the effect we are getting is much like a message scrolling across a surface, like you may have seen on your television, and at Times Square in NY (on several movies). Conclusion ========== Both of these data storage schemes work beautifully, when you have a lot of data to deal with that can be passed through quickly, but needs to be released at other times...beyond that, at this point, I can't think of anything that can be done by only using a stack or a queue, so my suggestion for practice is to attempt coding each of these specialized data types to do simple things. Practice Programming Problem(s) #12 =================================== Here are a couple of things for this one. 1) Reverse a string of text inputted from the screen (not necessarily a string) of a maximum of 500 characters, and output the text back to the screen. Sample ------ Enter a string: AbCdE Your string reversed is: EdCbA 2) Use a queue to store a maximum set of numbers that is 50 numbers long. These numbers will come from a series of randomly generated numbers by your program. The random numbers you will generate are from 1..1000, and the ones that are from 1..50 will be written out to the screen when the queue is filled up with those numbers. Write the numbers in the queue out in 5 rows of 10, and at the bottom, write out the total number of random numbers you had to go through to get those 50 numbers in the proper range. Sample ------ 23 23 11 22 33 1 9 12 23 11 23 3 11 22 33 11 9 12 23 11 23 23 11 22 33 11 9 12 23 11 23 29 11 2 33 11 9 12 23 11 23 23 11 22 33 11 9 50 23 11 987 numbers were generated from 1-1000 to get the 50 numbers from 1-50. Next Time ========= Recursion. (n) See Recursion. :> Any comments? Write ggrotz@2sprint.net.