Arrays & ArrayLists

Page 1

Arrays & ArrayLists


There will inevitably be a time in your programming career when keeping track of multiple variables will induce a migraine. Consider what happens if you were to program an electronic grade book to keep track of 25 test scores. You most certainly would not want to have the following declarations: int test1, test2, test3, . . ., test25;

Not only is that time consuming, not pretty to look at and inefficient, there is a large probability that you would make a mistake tracking all those variables.

ARRAY

Fortunately, there is a very elegant solution; it is called an array. An array is a data structure that is capable of holding multiple values of one type. Oftentimes, it is represented as follows:

83

89

92

76

35

100

99

Some neighborhoods have all the mailboxes together at the end of the street. This notion is similar to how an array Look at all those mailboxes! operates. Each mailbox can hold mail, and the contents of one mailbox are completely independent of the contents of surrounding mailboxes. Any mailbox can be accessed without visiting each one sequentially. However, typically the mail person will start from the left and visit each mail box in order. Similarly, each “box” in the array can hold one value. The value in any “box” is known as an element. You can access any element in an array whenever you want because each box has an index number associated with it. More on that later. Let’s talk about how you can create an array. There are two rules about arrays; first, you need to know how many things you are going to store in it. Second, you need to know what type of things you are going to store in it. Arrays do not accommodate different variable types, and you can’t change the size of an array once it has been created. Speaking of which, let’s look at the different ways you can create one:

Page 1


There are actually a few different ways to create an array. The following example illustrates an array that can hold 30 ints: int[] testScores = new int[30]; int testScores[] = new int[30]; int[] testScores; testScores = new int[30];

Arrays are objects, not primitive data types. Just like Strings, they can be reassigned memory locations. Therefore, assuming that testScores has been created, the following code is legal: testScores = new[50];

The array testScores will now point to a new memory location, therefore abandoning (or orphaning) the previous address (and, therefore, information). It is also possible to hardcode the data of an array into the array when it is being instantiated. Consider the following: int[] testScores = {83, 89, 92, 76, 35, 100, 99};

However you elect to create an array, keep in mind that the following diagram will be used throughout this book as a graphical representation:

DIAGRAM OF AN ARRAY

testScores 83

89

92

76

35

100

99

0

1

2

3

4

5

6

CREATING AN ARRAY

The actual code for this array instantiation would be: int testScores = new int[7];

Now, let’s get back to retrieving information from an array. To access the element in index 3 and store it in a variable, y, the following code could be used: int y = testScores[3];

This code will change the value of index 5: System.out.println(“What number do element 5 to? ”); testScores[5] = reader.readInt();

you

want

to

change

Page 2


However, a very common mistake when programming is to attempt to access information from an element that does not exist. Clearly, the length of testScores is 7, because it holds 7 items. So, it would be understandable if a programmer would try the following code to access the last element: System.out.println(“The last element is ” + testScores[7]);

ERRORS

This is commonly referred to as a fencepost error or off-by-one error. Check out the Wikipedia definition: In computer programming, a fencepost error is a computer bug involving the discrete equivalent of a boundary condition, often exhibited in programs by iterative loops. This can also occur in a mathematical context, but is not usually named. The following problem illustrates the error: "If you build a fence 100 feet long with posts 10 feet apart, how many posts do you need?" Many people will intuitively divide 100 by 10 and thus answer 10, but this is incorrect. The fence certainly has 10 sections, but there are 11 posts. The following diagram illustrates this:

Whenever

you

attempt

an

illegal access, you will incur an This will catastrophically terminate your program. Clearly, this is not a good thing, and should be avoided. Typically, this will happen when dealing with loops. A good programmer will be cognizant of this, and code accordingly. A great programmer will surround any access with a try-catch block to prevent program termination. But we will examine loops and arrays later. ArrayIndexOutOfBoundsException.

Every array has a length attribute, and that will return, as an integer, the length of the array. Consider the following code: System.out.println(“The length of array \“testScores\” is ” + testScores.length);

This will output: The length of array “testScores” is 7 Page 3


You can also always access the last element of the array with the code: System.out.println(“The last element [testScores.length-1]);

is:

+

testScores

It is important to note that when you create an array, but before you populate it (give it information), every element will already be assigned a value. If the array is of type int or double, the value of every element will be 0. If it is of boolean, every element is false, and if it is an Object, the elements are given the value of null. This is important to know, because this information is called unreliable. For instance, if you have testScores, (of length 7) and you give the first 5 elements a value (but neglect to give the last two elements a value), and you take the average of all the elements in the array, you will get a wrong answer. Consequently, you need to be extremely careful with how you handle an array. Typically, it is referred to as walking through an array when you examine every element in the array. Whether you are just “peeking” at them or actually collecting the data for a reason (like, say, to compute the average of all the numbers), you would walk through the array and stop to look at every element. The following code is a garden-variety loop for walking through in array. In this case, it will echo all the values in the array: for (int i = 0; i < testScores.length; i++) System.out.print(testScores[i] + “ ”);

ERRORS

Given that testScores is modeled by the following diagram, the output would be: 83 89 92 76 35 100 99

testScores 83

89

92

76

35

100

99

0

1

2

3

4

5

6

There are two popular pitfalls in the control statement for the loop: 1.

The control variable should always start at 0; otherwise, you are ignoring the first element. for (int j = 1; j < testScores.length; j++) System.out.println(testScores[j] + “ ”);

will output: 89 92 76 35 100 99

clearly omitting testScores[0].

Page 4


2. The control variable should be less than the length of the array; otherwise, a fence-post error will occur. for (int k = 0; k <= testScores.length; k++) System.out.println(testScores[k] + “ ”);

will output: 83 89 92 76 35 100 99

but this code will crash when it attempts to access testScores[7].

Naturally, if you walk through an array that contains objects, they are subject to the same specifications as any objects are. Using System.out.println(array[4]) will automatically invoke the toString method of that object.. By the way, if you want to talk about arrays, the proper nomenclature for testScores[i] is, “test scores sub i”. Knowing this may prevent any embarrassing array communications.

INSERTION

Inserting elements into an array is not difficult, though it is a very tedious exercise, and should not happen frequently. If you find that you are constantly adding to an array, then perhaps you should consider another data structure (like an ArrayList, coming up later). Because arrays are objects, the code for copying an array is not as simple as: int[] x = {13, 52, 4}; int[] y = x;

All this does is assign the pointer for y to the memory address of x. Therefore, any modifications to x will affect y, and any alterations to y will affect x. Therefore, to insert an element into an array, another array needs to be created (one element larger), all the elements copied, and then the last element needs to be given the value of the new value: int[] x = {13, 52, 4}; int[] y = new int[x.length + 1]; for (int j = 0; j < x.length; j++) y[j] = x[j]; y[y.length-1] = 15;

This code copies all the values from x into y, and then appends 15 to the last spot in y. The code to delete an item is almost the same, and is left as an exercise for the reader. Page 5


There also exists the notion of a matrix, or a two-dimensional array. Imagine an array, where each element holds another array. The graphical representation of a matrix is:

MATRIX

ROWS

COLUMNS 0

1

2

3

4

0

1

1

2

0

0

1

1

3

4

9

1

2

2

5

6

0

1

3

3

7

8

6

5

4

5

9

10

7

7

5

8

11

12

8

9

The proper way to instantiate a matrix (like the one pictured above) is: int[][] matrix = new int[5][6];

The code to traverse through this matrix (typically you would look at the left-most column, go through that array, and then go through the second column, and so on) is: for (int i = 0; i <= matrix.length; i++){ for (int j = 0; j <= matrix[i].length; j++){ System.out.print(matrix[i][j] + “\t�); } System.out.println(); }

This code will work with a rectangular matrix, or one where the number of elements in each column is the same. If the number of elements is not the same, it is referred to as a ragged matrix, and the code will work, as well. This is intuitive because Java considers a matrix as an array of arrays. The use of a ragged array is not common, and it is not part of the AP curriculum.

0

1

2

3

4

0

1

1

2

0

0

1

1

3

4

9

1

2

2

5

6

1

3

3

7

8

5

4

5

10

7

5

8

12

6

13

RAGGED MATRIX

Page 6


ARRAYLIST

There is another type of data structure that is very similar to arrays. This class is known as an ArrayList. An ArrayList is similar to an array, though it has a few notable exceptions. First and foremost, it can change size. Secondly, it is not bound to containing any one type of object. You can store multiple classes all willy-nilly. However, with great power comes great responsibility. With these conveniences, you will sacrifice ease-ofuse. When retrieving information from an ArrayList, any element is returned as an Object. This means that you must cast any element to the proper class. This also means that primitive data types cannot be stored in an ArrayList without modification. More on that in a minute. As a convention, diagrams for ArrayLists will be slightly tailored so that they can be easily differentiated from an array. Consider the following diagram: list “Dave”

“John”

“Beth”

“Mark”

“Vikki”

“Katie”

“Liz”

0

1

2

3

4

5

6

Naturally, the ArrayList has a starting index of 0, as well as a name. The length of the ArrayList is accessed by list.size().

CREATING

Typically, when an ArrayList is constructed, it is created using the default constructor (no parameters, therefore no size). This is acceptable because anytime you want to add something to it, the class will automatically increase the size and append that object to the list. To replicate the above example of list, the code would look like this: ArrayList list = new ArrayList(); list.add(“Dave”); list.add(“John”); list.add(“Beth”); list.add(“Mark”); list.add(“Vikki”)’ list.add(“Katie”); list.add(“Liz”);

Unlike an array, adding and deleting elements are a breeze; there is no need to shift elements because one of the methods in the class will automatically do it. It is important to note that you will need to import the ArrayList class from its appropriate package in every program you wish to utilize an ArrayList in. Use this code: import java.util.ArrayList; Page 7


The methods that you are responsible for on the AP exam are:

METHODS

class java.util.ArrayList int size() // returns the size of the ArrayList boolean add(Object x) // appends x to the end of list; returns true Object get(int index) // returns the element at the specified position Object set(int index, Object x) // replaces the element at index with x // returns the element formerly at the specified position void add(int index, Object x) // inserts x at position index, sliding elements // at position index and higher to the right // (adds 1 to their indices) and adjusts size Object remove(int index) // removes element from position index, sliding elements // at position index + 1 and higher to the left // (subtracts 1 from their indices) and adjusts size // returns the element formerly at the specified position

However, before we get started with all these methods, let’s take a look at how we can add primitive data types to an ArrayList. As we already learned, primitive data types need to be “modified” before they can go into an ArrayList. The modification is a wrapper class (at the time of publication, Java 1.5 has been released, and contains “auto-boxing” on primitive data types, which allows for primitive data types to be stored without a wrapper class. However, to be aligned with the Advanced Placement curriculum, we will discuss wrapper classes).

WRAPPER CLASSES

Since ArrayLists can only hold objects, a class known as Integer, as well as one known as Double, have been designed. Actually, there is a wrapper class for all primitive data types, but we only need to really worry about Integer and Double. Besides, they all work the same. The main intention of these wrapper classes is to “wrap” the primitive data type into a class. For instance, to create an Integer with the value of 70, and add it to ArrayList list, the following code would be appropriate: Integer x = new Integer(70); list.add(x);

Or, the object can be instantiated and added on the fly: list.add(new Integer(70));

Page 8


Let’s look at wrapper classes briefly. You will need to know these fundamental methods: class java.lang.Integer implements java.lang.Comparable Integer(int value) // constructor int intValue() // returns the numerical value of the object class java.lang.Double implements java.lang.Comparable Double(double value) // constructor double doubleValue() // returns the numerical value of the object

RETRIEVAL

Retrieving a value from an ArrayList is not a difficult chore, however care must be given because anytime you look into an ArrayList and inquire about an element, it will be returned as an Object. Therefore, you must properly cast your object. Consider the following ArrayList: list

CASTING

0

6

28

496

8128

0

1

2

3

4

The code to “peek” at element 2 is: int num = ((Integer)(list.get(2))).intValue();

This will get the object from list, cast it as an Integer object, and then return the integer equivalent of it via IntValue(). The only time it is not necessary to cast an object is when you will be invoking a method that is from Object class. For example, all Integers have a toString method inherited from Object (which is automatically invoked in a print() method). Therefore, the following code will not throw a ClassCastException: System.out.println(list.get(3));

Instead, it will execute and output “496”.

Page 9


The code below shows common usages of these methods:

METHODS

ArrayList list = new ArrayList(); for (int i = 0; i < 10; i++) list.add(new Integer(Math.pow(i, 2))); list.add(new Integer(196)); list.add(3, new Integer(225)); list.remove(4); list.set(0, 900); for (int i = 0; i <= list.size(); i++) System.out.println(list.get(i) + “ �);

The output of this code is: 900 1 4 225 16 25 36 49 64 81 196

Using ArrayLists is not difficult, but if you are storing multiple objects in an ArrayList, you must be careful with their retrieval or extraction.

ERRORS

When extracting (or removing) an object from an ArrayList, realize that all objects that appear after the removed element will be re-indexed. Consider this example. Keep in mind that remove(int x) will remove an object, and return an Object. ArrayList items = new ArrayList(); items.add("RED"); items.add("WHITE"); items.add("BLUE"); items.add("GREEN"); for (int i = 0; i < items.size(); i++) System.out.print(items.remove(i) + " "); System.out.println();

The output of this code is: RED BLUE

Page 10


EXERCISES 1. Write a program that asks the user how many numbers they would like to input (n). Create an array that is n long, and have the user populate it. Your program should then echo the elements forward and backward. Sample output should look like this: How long would you like the array? 5 Enter number 1: 10 Enter number 2: 53 Enter number 3: 21 Enter number 4: -4 Enter number 5: 0 The numbers are: 10 53 21 –4 0

2. Write a program that will ask a user how much data they would like to input. After populating the array, your program should output a statistical report that mirrors the following: How much data do you have? 8 Enter the data: 15 20 21 20 36 15 25 15 The The The The The The The The

maximum is 36.0000 minimum is 15.0000 mean is 20.8750 median is 20.0000 mode is 15.0000 standard deviation is 7.0799 variance is 50.1250 range is 21.0000

Pay attention to the input String that needs to be parsed, as well as the DecimalFormatter that will need to be instantiated. Use the following formulas for standard deviation s. n

s

( X i 1

i

 X )2

(n  1)

3. At Liverpool High School, there are about 5000 students. The school is divided into “houses” (House I, House II, House III, House IV and House V). There are approximately 1000 students in each house. Geographically, the “houses” are separated. Each student in House I has a locker on the first floor. The unique layout provides a setup where all 1000 lockers are right next to each other.

Page 11

During lunch one day, Krista gets to talking with Nadia. Krista claims that one day, over the summer, she entered the school building and opened every locker in House I (starting at 1 and ending at 1000).


Nadia’s eyes brightened up and said that she was in the building that day, and closed (reversed) every other locker (starting at 2, 4, 6, ‌). Just then, Jeff piped in to say that he, too, was in the building, and went to every third locker, and opened it if it was closed, and closed it if it was open. Holly added that she, also, coincidentally enough, was in the building that day, and added to the fun. She reversed every fourth locker. By the end of lunch, it was discovered that 1000 people walked down the hall that day. The ith person reversed the position of locker numbers that are multiples of i. For instance, the 60 th person reversed lockers 60, 120, 180, etc. The challenge is to write a program that identifies how many lockers are opened after 1000 people go through with this pattern. 4. Adding and deleting elements from an array is a fairly common task. Write a program which demonstrates this by randomly populating an array (either use Math.random() or the Random class). Display the array, and give the user a choice to add and element, delete and element, or view the array. If add is chosen, then the user should be able to decide what the new element should be, and where it should go. After the successful addition, the array should be displayed. Similarly, if the user chooses to delete an element, they should choose which one to erase, and the array should be displayed. The program should loop until the user decides to quit. Use of static methods should be implemented: static int[] add(int[] array, int value, int position) // Precondition: Takes in an array with length > 0, a valid // integer, and a value for position // Postcondition: Returns an array with new element added at // the position; if the position argument is invalid, the // original array should be returned and an error message // should be displayed. static int[] delete(int[] array, int position) // Precondition: Takes in an array with length > 0 and a / // position // Postcondition: An array is returned with the given element // with an index of position removed. If position is // illegal, then the original array should be returned, and // an error message should be displayed. static void displayArray(int[] array) // Precondition: The array has a length > 0 // Postcondition: The array has been displayed

The menu should look like this: Your options are: 1. Add an element 2. Delete an element 3. View the array 4. Quit

Page 12


5. Use a matrix to create a N by N multiplication table. It should be able to be displayed nicely by a call to static method displayTable(). How large do you want the table? 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81 10 20 30 40 50 60 70 80 90

10 10 20 30 40 50 60 70 80 90 100

6. Matrix multiplication is a very important algorithm. Namely, there are cryptography algorithms that rely on the ability to have a key and a message to encrypt the cleartext. This particular form of cryptography is strong because it is polyalphabetic. For the purpose of this exercise, let us use a 3 by 3 key. The message will be parsed into separate characters, and then converted to numbers. Therefore, each letter must have a numeric equivalent. For simplicity, use a=1, b=2, ‌ z=26. Spaces will be represented by the number 27. The user should enter a message, and the program should encode it using the key. The output should be in a 3 by N matrix (where N depends on the length of the message. However, if there are empty cells in the code matrix, populate them with 27). The ciphertext is calculated by multiplying the key matrix by the message matrix. Consider the following example: Enter a message to encipher: prepare to attack The enciphering key is: -3 -3 -4 0

1

1

4

3

4

The numerical message is: 16 16 5 18 1 5

15 20 3

27 27 20 11

18 20 1

1

27

The enciphered message is: -122 -123 -176 -130 -124 -150

Page 14

23

19

47

28

21

38

138

139

181

145

144

153

1

2

5

6

1X5+2x7

1x6+2x8

3

4

7

8

3x5+4x7

3x6+4x8

For this lab, it will be helpful to know the algorithm for multiplying two matrices. Follow this example:


7. John Conway has contributed immensely to the mathematical and computer science world. Perhaps his best contribution has been the Game of Life, a modeling of population. In this game, there is a blank matrix. The user chooses which cells to “populate”. After the initial population, the game takes no more inputs; each progression is a repercussion of the previous stage. The rules are simple: For a space that is “populated”: Each cell with 1 or no neighbors dies (loneliness) Each cell with 4 or more neighbors dies (overpopulation) Each cell with two or three neighbors survives For a space that is “empty” or “unpopulated”: Each cell with 3 neighbors becomes populated Consider the following examples. Although in this graphical representation a solid cell will be “populated”, your simulation can have “X” represent a populated cell. Use “ ” for an empty cell. You must have a graphical representation of the playing grid that is displayed after each step. You should have a 20 by 20 grid, and allow the user to input coordinates for beginning points. Use a sentinel of –1 to terminate input. The simulation should wait for the user to press the ENTER key before it shows the next step. However, if the user enters –1, the simulation should stop. Typical output should be: Welcome to John Conway’s Game of Life! Please Please Please Please Please Please Please Please

enter enter enter enter enter enter enter enter

a a a a a a a a

starting starting starting starting starting starting starting starting

coordinate coordinate coordinate coordinate coordinate coordinate coordinate coordinate

(-1 (-1 (-1 (-1 (-1 (-1 (-1 (-1

to to to to to to to to

quit): quit): quit): quit): quit): quit): quit): quit):

5 3 4 4 6 4 4 5 5 5 6 5 5 6 -1

This is only a 10 by 10 grid. Page 14


8. For demonstration purposes, have an ArrayList populate itself with 20 different integers. Then, have it output the even numbers first, followed by the odd numbers. Sample output should look like: The even elements are: 10, 12, 98, 66, 48, 0, 10, 46 The odd elements are: 1, 23, 75, 9, 9, 77, 35, 39, 49, 99, 61, 7

9. This lab combines the power of ArrayList with the capabilities found in the Integer wrapper class. The code for the driver class follows, and the main method should not be modified. However, you will have to fill in the proper implementation for the codes for each method. Assume that all the elements are Integer objects.The specifications of the methods are as follows: static double average(ArrayList list) {} // Precondition: list is a valid ArrayList with size >= 0. // Postcondition: the average of all the ints is returned. static void displayMenu() {} // Postcondition: the menu, as shown in the example, is // displayed static void displayList(ArrayList list) {} // Precondition: list is a valid ArrayList with size >= 0. // Postcondition: the contents of list are displayed, // separated by tabs, with corresponding index numbers // under each element. Additionally, a statement of the // size of the array must be evident. static int search(ArrayList list, int target) {} // Precondition: list is a valid ArrayList and target is a // valid int. // Postcondition: either the method returns the position of // the item as an int, or –1 is returned.

For your convenience and better understanding, a copy of output has been included: Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 1 Enter a number to add: 123 123 0 There are 1 elements.

Page 15

Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit


Enter a selection: 1 Enter a number to add: 234 123 234 0 1 There are 2 elements. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 2 123 234 0 1 There are 2 elements. Enter an index number to remove: 345 Not a valid index. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 1 Enter a number to add: 345 123 234 345 0 1 2 There are 3 elements. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 1 Enter a number to add: 345 123 234 345 345 0 1 2 3 There are 4 elements. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 4 The average is: 261.75

Page 16


Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 3 Enter a number to look for: 456 Your number was not present. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 3 Enter a number to look for: 345 The number 345 was found at position 2. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 2 123 234 345 345 0 1 2 3 There are 4 elements. Enter an index number to remove: 6 Not a valid index. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 2 123 234 345 345 0 1 2 3 There are 4 elements. Enter an index number to remove: 1 123 345 345 0 1 2 There are 3 elements. Please choose from the following: 1. Add a number 2. Remove a number 3. Search for a number 4. Average 5. Quit Enter a selection: 5 Page 17

Thanks for playing!


Driver class code: import java.util.ArrayList; public class ArrayListIntegerStub { public static void main(String[] args) { KeyboardReader reader = new KeyboardReader(); ArrayList list = new ArrayList(); int choice = 0; while (choice != 5) { displayMenu(); choice = reader.readInt("Enter a selection: "); switch (choice) { case 1: int temp = reader.readInt("Enter a number to add: "); list.add(new Integer(temp)); displayList(list); break; case 2: displayList(list); temp = reader.readInt("Enter an index number to remove: "); if (temp < 0 || temp >= list.size()) System.out.println("Not a valid index."); else { list.remove(temp); displayList(list); } break; case 3: temp = reader.readInt("Enter a number to look for: "); int result = search(list, temp); System.out.println(); if (result >= 0) System.out.println("The number " + temp + " was found at position " + result + ". "); else System.out.println("Your number was not present."); System.out.println(); break; case 4: System.out.println(); System.out.println("The average is: " + average(list)); System.out.println(); break; } } System.out.println("Thanks for playing!"); } public static double average(ArrayList list) { System.out.println("average() has been called."); return -1; } public static void displayMenu() { System.out.println("displayMenu() has been called."); } public static void displayList(ArrayList list) { System.out.println("displayList() has been called."); } public static int search(ArrayList list, int target) { System.out.println("search() has been called."); return -1; } }

Page 18


This document was written for the Advanced Placement Computer Science A course by Dave Ghidiu, Honeoye Falls - Lima Central School District. Technical help was given by John Ghidiu, and the document was edited by Mark Dibble.

Š 2010 Dave Ghidiu. All rights reserved. This document cannot be reproduced in whole or in part without written permission from the author.


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.