Meta Topics

Software Technology: List of Lists

Assumed Knowledge
Learning Outcomes
  • Understand creation and operations on a list of lists.

Author: Gaurav Gupta

What are lists… again?

  1. Lists are collections of objects
  2. Lists themselves are objects

Together, 1. and 2. imply that we can have a collection of Lists in another List.

Creating a list of lists

1
2
3
4
5
6
7
8
9
10
11
12
13
ArrayList<Integer> a = new ArrayList<Integer>(Arrays.asList(1000,700,200,900));
ArrayList<Integer> b = new ArrayList<Integer>(Arrays.asList(500,800,300,400));
ArrayList<Integer> c = new ArrayList<Integer>(Arrays.asList(600,700,800,900));

ArrayList<ArrayList<Integer>> matrix = new ArrayList<ArrayList<Integer>>();
matrix.add(a); //matrix = [[1000,700,200,900]]
matrix.add(b); //matrix = [[1000,700,200,900], [500,800,300,400]]
matrix.add(c); //matrix = [[1000,700,200,900], [500,800,300,400], [600,700,800,900]]

System.out.println(matrix); //[[1000, 700, 200, 900]]
System.out.println(matrix.get(0)); //[1000, 700, 200, 900]
System.out.println(matrix.get(1)); //[500, 800, 300, 400]
System.out.println(matrix.get(2)); //[600, 700, 800, 900]

Note that when items are added to an ArrayList, it’s a reference copy of the added object that is created and added to the list. Here,

  • matrix.get(0) is a reference copy of a
  • matrix.get(1) is a reference copy of b
  • matrix.get(2) is a reference copy of c
1
2
3
a.set(0, -100);
System.out.println(matrix.get(0)); //[-100, 700, 200, 900]
System.out.println(b == matrix.get(1)); //reference copy check: true

Traversing a list of lists

You can see that we can display each sub-list, one at a time, using,

1
2
3
System.out.println(matrix.get(0)); //[1000, 700, 200, 900]
System.out.println(matrix.get(1)); //[500, 800, 300, 400]
System.out.println(matrix.get(2)); //[600, 700, 800, 900]

Basic abstraction taught in COMP1000 tells us that the only varying aspect is the counter, which in this case goes from 0 to 2, or 0 to less than 3. Here, 3 is the size of the list matrix.

1
2
3
4
5
6
7
8
9
for(int i=0; i < matrix.size(); i++) {
	System.out.println(matrix.get(i));
}
/*
output:
[1000,700,200,900]
[500,800,300,400]
[600,700,800,900]
*/

Now, if we copy matrix.get(i) in a local ArrayList object, we can traverse each item of that list one at a time as well:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for(int i=0; i < matrix.size(); i++) {
	ArrayList<Integer> current = matrix.get(i);
	for(int k=0; k < current.size(); k++) {
		System.out.println(current.get(k));
	}
	System.out.println("----");
}
/*
output:
1000
700
200
900
----
500
800
300
400
----
600
700
800
900
----
*/

Back-substituting matrix.get(i) in place of current gives us:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
for(int i=0; i < matrix.size(); i++) {
	for(int k=0; k < matrix.get(i).size(); k++) {
		System.out.println(matrix.get(i).get(k));
	}
	System.out.println("----");
}
/*
output:
1000
700
200
900
----
500
800
300
400
----
600
700
800
900
----
*/

Assuming that the size of each sub-list is the same, we can also access the first item of each sub-list, followed by the second item of each sub-list, and so on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for(int i=0; i < matrix.get(0).size(); i++) {
	for(int k=0; k < matrix.size(); k++) {
		System.out.println(matrix.get(k).get(i));
	}
	System.out.println("----");
}
/*
output:
1000
500
600
----
700
800
700
----
200
300
800
----
900
400
900
----
*/

Not all lists are born equal

The above code will, however fail, if the list varying is created as:

1
2
3
4
5
6
7
8
ArrayList<Integer> a = new ArrayList<Integer>(Arrays.asList(1000, 700, 200, 900));
ArrayList<Integer> b = new ArrayList<Integer>(Arrays.asList(500, 300));
ArrayList<Integer> c = null;
ArrayList<Integer> d = new ArrayList<Integer>(Arrays.asList(600, null, 300));
ArrayList<ArrayList<Integer>> varying = 
				new ArrayList<ArrayList<Integer>>(Arrays.asList(a, b, c, d));
				
//varying = [[1000, 700, 200, 900], [500, 300], null, [600, null, 300]]

This is because some sub-lists are of different size, and one is even null.

Traversing list containing sub-lists with different sizes

Traversing each sub-list one by one

To traverse a list safely, we must perform null-checks on all objects before accessing them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//assuming:
//varying = [[1000, 700, 200, 900], [500, 300], null, [600, null, 300]]
if(varying!=null) {
	for(int i=0; i < varying.size(); i++) {
		if(varying.get(i)!=null) {
			for(int k=0; k < varying.get(i).size(); k++) {
				if(varying.get(i).get(k)!=null) {
					System.out.println(varying.get(i).get(k));
				}
			}
			System.out.println("----");
		}
	}
}
/*
OUTPUT:
1000
700
200
900
----
----
500
800
400
----
----
600
900
----
*/

As you can see, null-objects, whether it’s a sub-list or an item in one of the sub-lists are ignored.

Traversing first item of each sub-list followed by second item…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//assuming:
//varying = [[1000, 700, 200, 900], [500, 300], null, [600, null, 300]]
if(varying!=null) {
	int maxItems = 0;
	for(int i=0; i < varying.size(); i++) {
		if(varying!=null) {
			maxItems = Math.max(maxItems, varying.get(i).size());
		}
	}
	
	for(int i=0; i < maxItems; i++) {
		for(int k=0; k < varying.size(); k++) {
			if(varying.get(k)!=null) {
				if(varying.get(k).size() > i) {
					System.out.println(varying.get(i).get(k));
				}
				else {
					System.out.println("out of bounds");
				}
			}
			else {
				System.out.println("list is null");
			}
		}
		System.out.println("----");
	}
}
/*
OUTPUT:
1000
500
list is null
600
----
700
300
list is null
null
----
200
out of bounds
list is null
300
----
900
out of bounds
list is null
out of bounds
----
*/

Working code in ListOfListsComprehensive.java