Skip to main content


By May 15, 2020留学咨询

COMP20003 Algorithms and Data StructuresSecond (Spring) Semester 2019[Assignment 1]Taxi & For-Hire Vehicle Trip DatasetInformation Retrieval using Binary Search TreesHanded out: Friday, 23 of AugustDue: 8:00 AM, Monday, 9 of SeptemberPurposeThe purpose of this assignment is for you to:• Increase your proficiency in C programming, your dexterity with dynamic memory allocationand your understanding of linked data structures, through programming a dictionary.• Increase your understanding of how computational complexity can affect the performance of analgorithm by conducting orderly experiments with your program and comparing the results ofyour experimentation with theory.• Increase your proficiency in using UNIX utilities.BackgroundA dictionary is an abstract data type that stores and supports lookup of key, value pairs. For example,in a telephone directory, the (string) key is a person or company name, and the value is the phonenumber. In a student record lookup, the key would be a student ID number and the value would be acomplex structure containing all the other information about the student.A dictionary can be implemented in C using a number of underlying data structures. Any implemen-tation must support the operations: makedict a new dictionary; insert a new item (key, valuepair) into a dictionary; search for a key in the dictionary, and return the associated value. Mostdictionaries will also support the operation delete an item.Your taskIn this assignment, you will create a simple instance of a dictionary, and we’ll use it to look up infor-mation about for-hire vehicle trips in New York City.There are two stages in this project. In the first stage you will code a dictionary in the C programminglanguage, using a binary search tree as the underlying data structure. You will insert records into thisdictionary from a file, and look up records by key. In the second stage, you will code additional func-tions to retrieve information from this dictionary. You will use a Makefile to direct the compilationof two separate executable programs, one for Stage 1 and one for Stage 2.In both stages of the assignment, you will report on the number of key comparisons used for searchand analyse what would have been expected theoretically. The report should cover each file used toinitialize the dictionary.You are not required to implement the delete functionality.1Stage 1 (7 marks)In Stage 1 of this assignment, your Makefile will direct the compilation to produce an executableprogram called dict1. The program dict1 takes two command line arguments: the first argument isthe name of the data file used to build the dictionary; the second argument is the name of the outputfile, containing the data located in the searches. The data file consists of an unspecified number ofrecords, one per line, with the following information:VendorID – Code to indicate the vendor which produced the recordpassenger count – Number of passengerstrip distance – Length of the trip in milesRatecodeID – Code to represent the fare rate for the tripstore and fwd flag – Indicates whether trip records were stored locallyPULocationID – TLC Taxi Zone where passengers were picked upDOLocationID – TLC Taxi Zone where passengers were dropped offpayment type – Code to indicate payment type (e.g., cash)fare amount – Fare for the trip in USDextra – Extra charges in USDmta tax – MTA tax in USDtip amount – Tip in USDtolls amount – Tolls in USDimprovement surcharge – Improvement surcharge in USDtotal amount – Total cost of the trip in USDPUdatetime – Date/time passengers were picked upDOdatetime – Date/time passengers were dropped offtrip duration – Duration of the trip in minutesThis data comes from a publicly-available dataset released by the New York City Taxi & LimousineCommission. More information about the dataset can be found at: field is an alphabetic string representing the date and time of the taxi trip inthe format YYYY-MM-DD HH:mm:ss (year-month-day hour:minute:second). The other columns can betreated simply as the associated field. Build a data structure of strings to save the associateddata collected about each taxi trip. The maximum size that any string can be is 128 characters. Eachstring is separated by a comma “,”. This is a standard csv format where the delimiter used is a comma.The field will serve as the dictionary key, so the records will be sorted in temporalorder. Note that because the datetime information is stored in lexicographical order, the values can becompared as strings (e.g., with strcmp()) to determine which trip is earlier/later. The is theinformation sought during lookup.In this assignment the search keys are not guaranteed to be unique – there are instances where mul-tiple taxis pick up passengers at exactly the same day and time. You should handle duplicates byimplementing a linked list for items with same key.For the purposes of this assignment, you may assume that the input data is well-formatted, that theinput file is not empty, and that the maximum length of an input record (a single full line of the csvfile) is 256 characters. This number could help you to determine the buffer size to use when readingthe file.In this first stage of the assignment, you will:• Construct a binary search tree to store the information contained in the file specified in the2command line argument. Each record should be stored in a separate Node.• Search the binary search tree for records, based on their keys. The keys are read in from stdin,i.e. from the screen.For testing, it is often convenient to create a file of keys to be searched, one per line, and redirectthe input from this file. Use the UNIX operator < to redirect input from a file.• Examples of use:– dict1 datafile outputfile then type in keys; or– dict1 datafile outputfile < keyfile• Your program will look up each key and output the information (the data found) to the outputfile specified by the second command line parameter. If the key is not found in the tree, youmust output the word NOTFOUND.The number of key comparisons performed during both successful and unsuccessful lookupsshould be written to stdout.• Remember that the entries in the file do not necessarily have unique keys. Your search mustlocate and output all the data found for a matching key.• Example output:– output file (information):2018-12-15 01:49:13 --> VendorID: 1 || passenger count: 1 || trip distance: 1.9|| RatecodeID: 1.0 || store and fwd flag: 0 || PULocationID: 79 || DOLocationID: 234 ||payment type: 1 || fare amount: 9.5 || extra: 0.5 || mta tax: 0.5 || tip amount: 2.15|| tolls amount: 0.0 || improvement surcharge: 0.3 || total amount: 12.95 || DOdatetime:2018-12-15 02:00:00 || trip duration: 10 ||2018-12-15 01:49:13 –> VendorID: 1 || passenger count: 1 || trip distance: 0.6|| RatecodeID: 1.0 || store and fwd flag: 0 || PULocationID: 79 || DOLocationID: 114 ||payment type: 1 || fare amount: 5.0 || extra: 0.5 || mta tax: 0.5 || tip amount: 1.00|| tolls amount: 0.0 || improvement surcharge: 0.3 || total amount: 7.35 || DOdatetime:2018-12-02 01:53:38 || trip duration: 4 ||1901-11-06 12:03:14 –> NOTFOUND– stdout (comparisons):2018-12-15 01:49:13 –> 4231901-11-06 12:03:14 –> 401Note that the key is output to both the file and to stdout, for identification purposes. Also note thatthe number of comparisons is only output at the end of the search, so there is only one number forkey comparisons per key, even when multiple records have been located for that key.The format need not be exactly as above. Variations in whitespace/tabs are permitted. The number ofcomparisons shown above was made up; do not take it as an example of a correct result.3Stage 2 (2 marks)In Stage 2, you will code a function which takes a taxi zone ID number as as input and returns to theoutput file all of the keys from records which match the , usingin-order tree traversal. The keys should be output in sorted temporal order (that is, earlier recordsshould be printed first). If no records with the requested exist in the database,this function sho
uld write the string NOTFOUND to the output file. As in Stage 1, the the number ofcomparisons made during the search should be written to stdout.The is an unsigned integer between 1 and 265 which indicates where the taxipicked up passengers. You can find maps of the zones at the dataset website linked above, but youdo not need these maps for the assignment – you can treat the zone as simply an integer. You maystore the as a separate field in your struct, or you can check for the matching inside the field. As in Stage 1, you should handle duplicate keys by im-plementing a linked list for items with same key. Note that this means there may be more than onematching for a single key. If this is the case, the key should be output multipletimes to reflect the number of matches.You should compile your code using a Makefile to produce an executable program called dict2.The program dict2 takes two command line arguments: the first argument is the name of the datafile used to build the dictionary; the second argument is the name of the output file, containing thedata located in the searches. You may reuse your record insertion code from Stage 1 to build thedictionary from the datafile in Stage 2.• Examples of use:– dict2 datafile outputfile then type in location IDs; or– dict2 datafile outputfile < idsfile• Example output:– output file (information):79 --> 2018-12-08 19:36:5779 –> 2018-12-08 21:22:0879 –> 2018-12-15 01:49:1379 –> 2018-12-15 01:49:1379 –> 2018-12-23 17:26:42– stdout (comparisons):79 –> 1528The number of comparisons shown above was made up; do not take it as an example of a correct result.Experimentation (4 marks)You will run various files through your program to test its accuracy and also to examine the number ofkey comparisons used when searching different files. You will report on the key comparisons used byyour Stage 1 dictionary dict1 for various data inputs and the key comparisons used by your Stage 2dictionary dict2 for various data inputs too. You will compare these results with what you expectedbased on theory (big-O) for these algorithms and data structure.4Your experimentation should be systematic, varying the size and characteristics of the dataset files youuse (e.g. sorted or random), and observing how the number of key comparisons varies. Repeating atest case with different keys and taking the average can be useful.Some useful UNIX commands for creating test files with different characteristics include sort, sort-R (man sort for more information on the -R option), and shuf. You can randomize your inputdata and pick the first x keys as the lookup keywords.If you use only keyboard input for searches, it is unlikely that you will be able to generate enoughdata to analyze your results. You should familiarize yourself with the powerful UNIX facilities forredirecting standard input (stdin) and standard output (stdout). You might also find it useful tofamiliarize yourself with UNIX pipes ‘|’ and possibly also the UNIX program awk for processingstructured output. For example, if you pipe your output into echo ‘‘abc:def’’ | awk -F ’:’’{print $1}’, you will output only the first column (abc). In the example, -F specifies the de-limiter. Instead of using echo you can use cat filename.csv | awk -F ’;’ ’{print $1}’which will print only the first column of the filename.csv file. You can build up a file of numbers ofkey comparisons using the shell append operator >>, e.x. your command >> file to append to.You will write up your findings and submit your results separately through the Turnitin system. You willdescribe your results from each stage and also compare these to what you know about the theory ofbinary search trees.Tables and graphs are useful presentation methods. Select only informative data; more is not alwaysbetter.You should present your findings clearly, in light of what you know about the data structures used inyour programs and in light of their known computational complexity. You may find that your resultsare what you expected, based on theory. Alternatively, you may find your results do not agree withtheory. In either case, you should state what you expected from the theory, and if there is a discrep-ancy you should suggest possible reasons. You might want to discuss space-time trade-offs, if this isappropriate to your code and data.You are not constrained to any particular structure in this report, but a useful way to present yourfindings might be:• Introduction: Summary of data structures and inputs.• Stage 1:– Data (number of key comparisons)– Comparison with theory• Stage 2:– Data (number of key comparisons)– Comparison with theory• DiscussionImplementation RequirementsThe following implementation requirements must be adhered to:• You must code your dictionary in the C programming language.5• You must code your dictionary in a modular way, so that your dictionary implementation could beused in another program without extensive rewriting or copying. This means that the dictionaryoperations are kept together in a separate .c file, with its own header (.h) file, separate from themain program.• Your code should be easily extensible to allow for multiple dictionaries. This means that the func-tions for insertion, search, and deletion take as arguments not only the item being inserted or akey for searching and deleting, but also a pointer to a particular dictionary, e.g. insert(dict,item).• Your program should store strings in a space-efficient manner. If you are using malloc() tocreate the space for a string, remember to allow space for the final end of string ‘\0’ (NULL).• A Makefile is not provided for you. The Makefile should direct the compilation of twoseparate programs: dict1 and dict2. To use the Makefile, make sure it is in the same directoryof your code, and type make dict1 to make the dictionary for Stage 1 and make dict2 tomake the dictionary for Stage 2. You must submit your makefile with your assignment. Hint: Ifyou havent used make before, try it on simple programs first. If it doesn’t work, read the errormessages carefully. A common problem in compiling multifile executables is in the includedheader files. Note also that the whitespace before the command is a tab, and not multiplespaces. It is not a good idea to code your program as a single file and then try to break it downinto multiple files. Start by using multiple files, with minimal content, and make sure they arecommunicating with each other before starting more serious coding.DataThe data files are provided at /home/shared/assg1/datafiles/ on JupyterHub. The data for-mat is as specified above in Stage 1.No attempt has been made to remove or prevent duplicate keys in the original files, so you shouldexpect duplicate keys. Our script only formatted the data correctly making sure it complies with a csvstandard specification.Resources: Programming Style (2 Marks)Two locally-written papers containing useful guidelines on coding style and structure can be foundon the LMS Resources→ Project Coding Guidelines, by Peter Schachte, and below and adapted versionof the LMS Resources → C Programming Style, written for Engineering Computation COMP20005 byAidan Nagorcka-Smith. Be aware that your programming style will be judged with 2 marks.1 /** ***********************2 * C Programming S t y l e f o r Engineer ing Computation3 * Created by Aidan Nagorcka−Smith ( [email protected] . unimelb . edu . au) 13/03/20114 * D e f i n i t i o n s and inc ludes5 * D e f i n i t i o n s are in UPPER CASE6 * Inc ludes go before d e f i n i t i o n s7 * Space between inc ludes , d e f i n i t i o n s and the main func t ion .8 * Use d e f i n i t i o n s f o r any cons tan t s in your program , do not j u s t wr i te them9 * in .10 *11 * Tabs may be s e t to 4−spaces or 8−spaces , depending on your e d i t o r . The code12 * Below i s “gnu ‘ ‘ s t y l e . I f your e d i t o r has “bsd ‘ ‘ i t w i l l fo l low the 8−space13 * s t y l e . Both are very standard .14 */15616 /**17 * GOOD:18 */1920 #inc lude 21 #inc lude 22 #def ine MAX STRING SIZE 100023 #def ine DEBUG 024 i n t main
( i n t argc , char **argv) {25 . . .2627 /**28 * BAD:29 */3031 /* D e f i n i t i o n s and inc ludes are mixed up */32 #inc lude 33 #def ine MAX STING SIZE 100034 /* D e f i n i t i o n s are given names l i k e v a r i a b l e s */35 #def ine debug 036 #inc lude 37 /* No spac ing between inc ludes , d e f i n i t i o n s and main func t ion */38 i n t main( i n t argc , char **argv) {39 . . .4041 /** *****************************42 * Va r i ab l e s43 * Give them use fu l lower case names or camelCase . E i t he r i s f ine ,44 * as long as you are c o n s i s t e n t and apply always the same s t y l e .45 * I n i t i a l i s e them to something tha t makes sense .46 */4748 /**49 * GOOD: lower case50 */5152 i n t main( i n t argc , char **argv) {5354 i n t i = 0;55 i n t num_fifties = 0;56 i n t num_twenties = 0;57 i n t num_tens = 0;5859 . . .60 /**61 * GOOD: camelCase62 */6364 i n t main( i n t argc , char **argv) {6566 i n t i = 0;67 i n t numFifties = 0;68 i n t numTwenties = 0;69 i n t numTens = 0;7071 . . .72 /**73 * BAD:74 */7576 i n t main( i n t argc , char **argv) {7778 /* Var i ab l e not i n i t i a l i s e d − causes a bug because we didn ‘ t remember to79 * s e t i t be fore the loop */80 i n t i ;81 /* Var i ab l e in a l l caps − we ‘ l l get confused between t h i s and cons tan t s782 */83 i n t NUM_FIFTIES = 0;84 /* Overly abbrev ia ted v a r i a b l e names make th ings hard . */85 i n t nt = 08687 while (i < 10) {88 . . .89 i++;90 }9192 . . .9394 /** ********************95 * Spacing :96 * Space i n t e l l i g e n t l y , v e r t i c a l l y to group b locks of code tha t are doing a97 * s p e c i f i c operat ion , or to separa te v a r i a b l e d e c l a r a t i o n s from other code .98 * One tab of inden ta t ion within e i t h e r a func t ion or a loop .99 * Spaces a f t e r commas .100 * Space between ) and { .101 * No space between the ** and the argv in the d e f i n i t i o n of the main102 * func t ion .103 * When dec l a r ing a po in te r v a r i a b l e or argument , you may place the a s t e r i s k104 * ad jacent to e i t h e r the type or to the v a r i a b l e name .105 * L ines at most 80 c h a r a c t e r s long .106 * Clos ing brace goes on i t s own l i n e107 */108109 /**110 * GOOD:111 */112113 i n t main( i n t argc , char **argv) {114115 i n t i = 0;116117 f o r (i = 100; i >= 0; i−−) {118 i f (i > 0) {119 printf( ”%d b o t t l e s of beer , take one down and pass i t around , ”120 ” %d b o t t l e s of beer .\n” , i , i − 1) ;121 } e l s e {122 printf( ”%d b o t t l e s of beer , take one down and pass i t around . ”123 ” We ‘ re empty .\n” , i) ;124 }125 }126127 re turn 0;128 }129130 /**131 * BAD:132 */133134 /* No space a f t e r commas135 * Space between the ** and argv in the main func t ion d e f i n i t i o n136 * No space between the ) and { at the s t a r t of a func t ion */137 i n t main( i n t argc , char ** argv){138 i n t i = 0;139 /* No space between v a r i a b l e d e c l a r a t i o n s and the r e s t of the func t ion .140 * No spaces around the boolean opera tor s */141 f o r (i=100;i>=0;i−−) {142 /* No indenta t ion */143 i f (i > 0) {144 /* Line too long */145 printf( ”%d b o t t l e s of beer , take one down and pass i t around , %d146 b o t t l e s of beer .\n” , i , i − 1) ;147 } e l s e {8148 /* Spacing f o r no good reason . */149150 printf( ”%d b o t t l e s of beer , take one down and pass i t around . ”151 ” We ‘ re empty .\n” , i) ;152153 }154 }155 /* Clos ing brace not on i t s own l i n e */156 re turn 0;}157158 /** ****************159 * Braces :160 * Opening braces go on the same l i n e as the loop or func t ion name161 * Clos ing braces go on t h e i r own l i n e162 * Clos ing braces go at the same indenta t ion l e v e l as the th ing they are163 * c l o s i n g164 */165166 /**167 * GOOD:168 */169170 i n t main( i n t argc , char **argv) {171172 . . .173174 f o r ( . . . ) {175 . . .176 }177178 re turn 0;179 }180181 /**182 * BAD:183 */184185 i n t main( i n t argc , char **argv) {186187 . . .188189 /* Opening brace on a d i f f e r e n t l i n e to the fo r loop open */190 f o r ( . . . )191 {192 . . .193 /* Clos ing brace at a d i f f e r e n t indenta t ion to the th ing i t ‘ s194 c l o s i n g195 */196 }197198 /* Clos ing brace not on i t s own l i n e . */199 re turn 0;}200201 /** **************202 * Commenting :203 * Each program should have a comment exp la in ing what i t does and who created204 * i t .205 * Also comment how to run the program , inc lud ing op t iona l command l i n e206 * parameters .207 * Any i n t e r e s t i n g code should have a comment to exp la in i t s e l f .208 * We should not comment obvious th ings − wri te code tha t documents i t s e l f209 */210211 /**212 * GOOD:213 */9214215 /* change . c216 *217 * Created by Aidan Nagorcka−Smith ( [email protected] . unimelb . edu . au)218 13/03/2011219 *220 * P r i n t the number of each coin tha t would be needed to make up some221 change222 * tha t i s input by the user223 *224 * To run the program type :225 * . / co ins −−num coins 5 −−shape co ins t rapezo id −−output b l ab l a . t x t226 *227 * To see a l l the input parameters , type :228 * . / co ins −−help229 * Options : :230 * −−help Show help message231 * −−num coins arg Input number of co ins232 * −−shape co ins arg Input co ins shape233 * −−bound arg (=1) Max bound on xxx , d e f a u l t value 1234 * −−output arg Output s o l u t i o n f i l e235 *236 */237238 i n t main( i n t argc , char **argv) {239240 i n t input_change = 0;241242 printf( ” P lease input the value of the change (0−99 cent s243 i n c l u s i v e ) :\n” ) ;244 scanf( ”%d” , &input_change) ;245 printf( ”\n” ) ;246247 // Va l id change va lues are 0−99 i n c l u s i v e .248 i f (input_change < 0 | | input_change > 99) {249 printf( ” Input not in the range 0−99.\n” )250 }251252 . . .253254 /**255 * BAD:256 */257258 /* No explanat ion of what the program i s doing */259 i n t main( i n t argc , char **argv) {260261 /* Commenting obvious th ings */262 /* Create a i n t v a r i a b l e c a l l e d input change to s t o r e the input from263 the264 * user . */265 i n t input_change ;266267 . . .268269 /** ****************270 * Code s t r u c t u r e :271 * F a i l f a s t − input checks should happen f i r s t , then do the computation .272 * S t ruc tu re the code so tha t a l l e r ro r handl ing happens in an easy to read273 * l o c a t i o n274 */275276 /**277 * GOOD:278 */279 i f (input_is_bad) {10280 printf( ” Er ror : Input was not v a l i d . E x i t i n g .\n” ) ;281 exit(EXIT_FAILURE) ;282 }283284 /* Do computations here */285 . . .286287 /**288 * BAD:289 */290291 i f (input_is_good) {292 /* l o t s of computation here , pushing the e l s e par t o f f the screen .293 */294 . . .295 } e l s e {296 fprintf(stderr , ” Er ror : Input was not v a l i d . E x i t i n g .\n” ) ;297 exit(EXIT_FAILURE) ;298 }Additional SupportYour tutors will be available to help with your assignment during the scheduled workshop times. Ques-tions related to the assignment may be posted on the Piazza forum, using the folder tag assignment1for new posts. You should feel free to answer other students’ questions if you are confident of yourskills.A tutor will check the Discussion Forum regularly, and answer some questions, but be aware that forsome questions you will just need to use your judgment and document your thinking. For example, aquestion like, How much data should I use for the experiments?, will not be answered; you must tryout different data and see what makes sense.In this subject, we’ll be supporting the shared JupyterHub system, its terminal and file editor. Yourfinal program must compile and run on the shared JupyterHub instance.SubmissionYou will need to make two submissions for this assignment:• Your C code files (including your Makefile) will be submitted through the LMS page for thissubject: Assignments→ Assignment 1→ Assignment 1: Code.• Your experiments report file will be submitted through the LMS page for this subjec
t: Assignments→ Assignment 1→ Assignment 1: Experimentation. This file can be of any format, e.g. .pdf, textor other.Program files submitted (Code)Submit the program files for your assignment and your Makefile.If you wish to submit any scripts or code used to generate input data, you may, although this is notrequired. Just be sure to submit all your files at the same time.Your programs must compile and run correctly on the shared JupyterHub instance. You may have de-veloped your program in another environment, but it still must run on the JupyterHub at submission11time. For this reason, and because there are often small, but significant, differences between compil-ers, it is suggested that if you are working in a different environment, you upload and test your codeon the shared JupyterHub instance at reasonably frequent intervals.A common reason for programs not to compile is that a file has been inadvertently omitted from thesubmission. Please check your submission, and resubmit all files if necessary.Experiment file submitted using TurnitinAs noted above, your experimental work will be submitted through the LMS, via the Turnitin system.Go to the LMS page for this subject: Assignments → Assignment 1 → Assignment 1: Experimentationand follow the prompts.Your file can be in any format. Plain text or .pdf are recommended, but other formats will be ac-cepted. It is expected that your experimental work will be in a single file, but multiple files can beaccepted. Add your username to the top of your experiments file.Please do not submit large data files. There is no need to query every key on the dictionary.AssessmentThere are a total of 15 marks given for this assignment, 7 marks for Stage 1, 2 marks for Stage 2, and4 marks for the separately submitted Experimentation Stage. 2 marks will be given based on yourC programming style.Your C program will be marked on the basis of accuracy, readability, and good C programming struc-ture, safety and style, including documentation. Safety refers to checking whether opening a filereturns something, whether mallocs do their job, etc. The documentation should explain all majordesign decisions, and should be formatted so that it does not interfere with reading the code. As muchas possible, try to make your code self-documenting, by choosing descriptive variable names.Your experimentation will be marked on the basis of orderliness and thoroughness of experimentation,comparison of your results with theory, and thoughtful discussion.PlagiarismThis is an individual assignment. The work must be your own.While you may discuss your program development, coding problems and experimentation with yourclassmates, you must not share files, as this is considered plagiarism.If you refer to published work in the discussion of your experiments, be sure to include a citation tothe publication or the web link.Borrowing of someone elses code without acknowledgment is plagiarism. Plagiarism is considereda serious offense at the University of Melbourne. You should read the University code on Academichonesty and details on plagiarism. Make sure you are not plagiarizing, intentionally or unintentionally.You are also advised that there will be a C programming component (on paper, not on a computer) inthe final examination. Students who do not program their own assignments will be at a disadvantagefor this part of the examination.12Administrative issuesWhen is late? What do I do if I am late? The due date and time are printed on the front of thisdocument. The lateness policy is on the handout provided at the first lecture and also available on thesubject LMS page. If you decide to make a late submission, you should send an email directly to thelecturer as soon as possible and he will provide instructions for making a late submission.What are the marks and the marking criteria Recall that this project is worth 15% of your finalscore. There is also a hurdle requirement: you must earn at least 15 marks out of a subtotal of 30 forthe projects to pass this subject.Finally Despite all these stern words, we are here to help! There is information about getting help inthis subject on the LMS pages. Frequently asked questions about the project will be answered in theLMS discussion group.13


Author admin

More posts by admin

Leave a Reply