Skip to content
HopperHopper

Course navigation

Unit 4 · Data CollectionsTopic 4.10

Implementing ArrayList Algorithms

  • About 65 minutes
  • 8 sections
  • Runnable Java examples
  • Practice questions

Before you start

About 65 minutes

By the end you will be able to

  1. Find a sum, average, minimum, or maximum of the values in an ArrayList.
  2. Decide whether any element, or every element, of an ArrayList has a property, and count how many do.
  3. Compare each element with the next one, and check a list for duplicate values.
  4. Insert and delete elements while traversing an ArrayList without skipping elements or looping forever.
  5. Shift, rotate, and reverse the elements of an ArrayList.
  6. Build a new list from the elements of another list, and traverse two collections side by side.

What to remember from earlier

Section 1 of 8

4.10.1 Sum, Average, Minimum, and Maximum

A fitness app records your step count once a day. On day 1 there is one number; after a month there are thirty. An array would need its length fixed in advance, so the app keeps the counts in an ArrayList<Integer> that grows by one each day. At the end of the week it wants the total, the daily average, and the best and worst days.

You already know these algorithms. In Topic 4.5 you added up an array with an accumulator, divided by the length for an average, and tracked the largest value seen so far. Every one of those algorithms works on an ArrayList too. Only the way you reach the elements changes:

Job Array (Topic 4.5) ArrayList (Topic 4.8)
How many elements arr.length list.size()
Read element i arr[i] list.get(i)
Replace element i arr[i] = value; list.set(i, value);
Last valid index arr.length - 1 list.size() - 1

Note that length has no parentheses but size() does, because size() is a method call. Every algorithm in this chapter is built on a traversal (Topic 4.9): a loop that visits the elements of a list in order. A standard algorithm is a common pattern, such as a sum or a maximum, that you can reuse in many programs by changing the list and the condition.

Worked example: Total and average steps

Watch total grow on each pass, then see how the average is computed.

SumAverage.java
import java.util.ArrayList;

public class SumAverage
{
    public static void main(String[] args)
    {
        ArrayList<Integer> steps = new ArrayList<Integer>();
        steps.add(4200);
        steps.add(6100);
        steps.add(3800);
        steps.add(7500);
        steps.add(5400);

        int total = 0;
        for (int i = 0; i < steps.size(); i++)
        {
            total += steps.get(i);
        }
        double average = (double) total / steps.size();
        System.out.println("Total: " + total);
        System.out.println("Average: " + average);
    }
}

Line 17 is total += steps.get(i);. The call steps.get(i) returns an Integer object, and Java unboxes it to an int (Topic 4.7) so it can be added to total.

i steps.get(i) total after line 17
0 4200 4200
1 6100 10300
2 3800 14100
3 7500 21600
4 5400 27000

When i becomes 5, 5 < steps.size() is false and the loop ends. Line 19 casts total to double before dividing, so the division keeps its decimal part: 27000.0 / 5 is 5400.0. Without the cast, int / int would throw away any remainder.

Total: 27000
Average: 5400.0

An average divides by size(), so it needs a list with at least one element. This is a precondition of the algorithm: something that must be true before it runs.

For a minimum or maximum, start with a value that really is in the list, the first element, and compare every other element against it.

Worked example: Coldest and warmest morning

This list holds negative temperatures, so pay attention to what lowest and highest start as.

MinMaxTemps.java
import java.util.ArrayList;

public class MinMaxTemps
{
    public static void main(String[] args)
    {
        ArrayList<Integer> temps = new ArrayList<Integer>();
        temps.add(-3);
        temps.add(5);
        temps.add(-8);
        temps.add(2);
        temps.add(0);

        int lowest = temps.get(0);
        int highest = temps.get(0);
        for (int i = 1; i < temps.size(); i++)
        {
            int temp = temps.get(i);
            if (temp < lowest)
            {
                lowest = temp;
            }
            if (temp > highest)
            {
                highest = temp;
            }
        }
        System.out.println("Lowest: " + lowest);
        System.out.println("Highest: " + highest);
    }
}

Lines 14 and 15 start both variables at temps.get(0), which is -3. The loop then starts at index 1, because index 0 has already been used. Line 18 unboxes each element into the int variable temp.

i temp lowest highest
-3 -3
1 5 -3 5
2 -8 -8 5
3 2 -8 5
4 0 -8 5
Lowest: -8
Highest: 5

Using get(0) means the list must not be empty: on an empty list, get(0) throws an IndexOutOfBoundsException.

Key idea

Array algorithms carry over to an ArrayList by swapping arr.length for list.size() and arr[i] for list.get(i). Start a minimum or maximum at an element of the list, never at a made-up value.

Watch out

It is tempting to start a maximum at 0, because 0 feels like “nothing yet.” That works only when at least one value is bigger than 0.

MaxStartZero.java
import java.util.ArrayList;

public class MaxStartZero
{
    public static void main(String[] args)
    {
        ArrayList<Integer> temps = new ArrayList<Integer>();
        temps.add(-4);
        temps.add(-9);
        temps.add(-2);

        int highest = 0;
        for (int temp : temps)
        {
            if (temp > highest)
            {
                highest = temp;
            }
        }
        System.out.println("Highest: " + highest);
    }
}
Highest: 0

Every temperature is below 0, so no comparison on line 15 is ever true, and the program reports 0, a value that is not even in the list. Starting with int highest = temps.get(0); fixes it: the program then prints Highest: -2.

Pause and try

Question 1 · Tracing

What does this program print? Explain why it does not print cherry.

LongestWord.java
import java.util.ArrayList;

public class LongestWord
{
    public static void main(String[] args)
    {
        ArrayList<String> words = new ArrayList<String>();
        words.add("fig");
        words.add("banana");
        words.add("kiwi");
        words.add("cherry");
        words.add("plum");

        String longest = words.get(0);
        for (int i = 1; i < words.size(); i++)
        {
            if (words.get(i).length() > longest.length())
            {
                longest = words.get(i);
            }
        }
        System.out.println(longest);
    }
}
Check your reasoning
banana
i words.get(i) Length Longer than longest? longest after
fig (3)
1 banana 6 6 > 3, yes banana
2 kiwi 4 4 > 6, no banana
3 cherry 6 6 > 6, no banana
4 plum 4 4 > 6, no banana

cherry has the same length as banana, and > is false for equal values, so the first longest word found is kept. With >=, the last word of the longest length would win instead.

Reread 4.10.1 Sum, Average, Minimum, and Maximum if your trace differed.

Question 2 · Multiple choice

readings is a non-empty ArrayList<Integer> whose values may all be negative. A loop from index 1 to the end will update lowest whenever it finds a smaller value. Which declaration should come before the loop?

  1. int lowest = 0;
  2. int lowest = readings.get(0);
  3. int lowest = readings.size();
  4. int lowest = readings.get(1);
Check your reasoning

B. A minimum must start at a value from the list. Starting at the first element, and then looping from index 1, compares every element exactly once.

  • A is wrong if every value is positive: nothing is ever below 0, so lowest would stay 0, a value not in the list. Made-up starting values work only by luck.
  • C is wrong because size() is the number of elements, not one of the readings.
  • D is wrong because the loop starts at index 1, so index 0 would never be compared. It also crashes on a list with one element, since index 1 does not exist.

Reread 4.10.1 Sum, Average, Minimum, and Maximum if you chose differently.

Section 2 of 8

4.10.2 Any, All, and How Many

A teacher’s grade list raises three different questions. Did anyone score below 70? Did everyone pass? How many students earned an A? Each is a yes-or-no question or a count about the elements of the list, and each needs a full traversal.

The tools are the ones from Topic 4.5:

  • At least one: a boolean flag starts false and switches to true the first time an element has the property.
  • All: a flag starts true and switches to false the first time an element does not have the property. One counterexample is enough to make “all” false.
  • How many: an int counter starts at 0 and goes up by 1 for each element with the property.

None of these changes the list, so an enhanced for loop (Topic 4.9) is a good fit: it hands you each element in turn without an index. When the list holds Integer objects, the loop variable can be declared int, and Java unboxes each element as it is assigned.

Worked example: Three questions about one grade list

One loop can answer all three questions at once. Watch each variable change only when its own condition is true.

GradeChecks.java
import java.util.ArrayList;

public class GradeChecks
{
    public static void main(String[] args)
    {
        ArrayList<Integer> grades = new ArrayList<Integer>();
        grades.add(88);
        grades.add(92);
        grades.add(67);
        grades.add(95);
        grades.add(74);

        boolean anyBelow70 = false;
        boolean allPassing = true;
        int countA = 0;
        for (int grade : grades)
        {
            if (grade < 70)
            {
                anyBelow70 = true;
            }
            if (grade < 60)
            {
                allPassing = false;
            }
            if (grade >= 90)
            {
                countA++;
            }
        }
        System.out.println("Any below 70: " + anyBelow70);
        System.out.println("All passing: " + allPassing);
        System.out.println("A grades: " + countA);
    }
}
grade anyBelow70 allPassing countA
(start) false true 0
88 false true 0
92 false true 1
67 true true 1
95 true true 2
74 true true 2
Any below 70: true
All passing: true
A grades: 2

anyBelow70 switched once, at 67, and never switched back. allPassing never found a grade below 60, so it kept its starting value.

The same patterns work on a list of strings. Here the “all” flag and the counter share one if/else: an element either has the property (count it) or it does not (it breaks “all”).

Worked example: Do all tags start with a hash sign?

TagChecks.java
import java.util.ArrayList;

public class TagChecks
{
    public static void main(String[] args)
    {
        ArrayList<String> tags = new ArrayList<String>();
        tags.add("#java");
        tags.add("#code");
        tags.add("fun");
        tags.add("#ap");

        boolean allTags = true;
        int tagCount = 0;
        for (String tag : tags)
        {
            if (tag.substring(0, 1).equals("#"))
            {
                tagCount++;
            }
            else
            {
                allTags = false;
            }
        }
        System.out.println("All hashtags: " + allTags);
        System.out.println("Hashtags: " + tagCount);
    }
}

tag.substring(0, 1) is the first character of each tag (Topic 1.15), compared with equals. The tags #java and #code raise tagCount to 2. The tag fun goes to the else, so allTags becomes false. Then #ap raises tagCount to 3. Setting allTags to false a second time would change nothing, and nothing ever sets it back to true.

All hashtags: false
Hashtags: 3

Watch out

A flag must never be switched back inside the loop. This version adds an else that resets allPassing:

FlagElseBug.java
import java.util.ArrayList;

public class FlagElseBug
{
    public static void main(String[] args)
    {
        ArrayList<Integer> grades = new ArrayList<Integer>();
        grades.add(55);
        grades.add(80);
        grades.add(90);

        boolean allPassing = true;
        for (int grade : grades)
        {
            if (grade < 60)
            {
                allPassing = false;
            }
            else
            {
                allPassing = true;
            }
        }
        System.out.println("All passing: " + allPassing);
    }
}
All passing: true

The 55 sets the flag to false, but the 80 sets it back to true, so the result describes only the last grade. Students write the else because it feels like “the other case.” Delete the else block: with only the if, the program prints All passing: false.

Pause and try

Question 3 · Tracing

What does this program print?

CountRange.java
import java.util.ArrayList;

public class CountRange
{
    public static void main(String[] args)
    {
        ArrayList<Integer> values = new ArrayList<Integer>();
        values.add(12);
        values.add(55);
        values.add(20);
        values.add(9);
        values.add(31);
        values.add(10);

        int inRange = 0;
        boolean over50 = false;
        for (int value : values)
        {
            if (value >= 10 && value <= 20)
            {
                inRange++;
            }
            if (value > 50)
            {
                over50 = true;
            }
        }
        System.out.println(inRange + " " + over50);
    }
}
Check your reasoning
3 true
value In 10 to 20? inRange over50
12 yes 1 false
55 no 1 true
20 yes 2 true
9 no 2 true
31 no 2 true
10 yes 3 true

Both ends of the range count because the condition uses >= and <=. Once over50 becomes true at 55, nothing sets it back.

Reread 4.10.2 Any, All, and How Many if your trace differed.

Question 4 · Multiple choice

names is an ArrayList<String>. After this code runs, what does result tell you?

boolean result = true;
for (String name : names)
{
    if (name.length() < 3)
    {
        result = false;
    }
}
  1. Whether at least one name has fewer than 3 characters
  2. Whether every name has at least 3 characters
  3. How many names have at least 3 characters
  4. Whether every name has fewer than 3 characters
Check your reasoning

B. The flag starts true and switches to false when a name is shorter than 3 characters. It stays true only if no name is short, which means every name has 3 or more characters. This is the “all” pattern.

  • A describes the opposite: result is false when a short name exists. The “at least one” pattern starts false.
  • C is wrong because a boolean cannot hold a count; counting needs an int counter.
  • D confuses the property being searched for with the property being confirmed. Short names make result false, not true.

Reread 4.10.2 Any, All, and How Many if you chose differently.

Section 3 of 8

4.10.3 Consecutive Pairs and Duplicates

A stock-price app keeps one closing price per day. To report “the price rose 3 times this week,” it has to compare each day with the next day. A school database storing student ID numbers has a different worry: does any ID appear twice? Both questions look at two elements at a time instead of one.

Consecutive pairs

Consecutive elements are elements next to each other, at indices i and i + 1. To visit every consecutive pair, the loop must stop one index early. In a list of 5 elements, the pairs start at indices 0, 1, 2, and 3; a pair starting at index 4 would need index 5, which does not exist. So the condition is i < list.size() - 1.

Worked example: Counting price rises

ConsecutiveRises.java
import java.util.ArrayList;

public class ConsecutiveRises
{
    public static void main(String[] args)
    {
        ArrayList<Integer> prices = new ArrayList<Integer>();
        prices.add(20);
        prices.add(22);
        prices.add(21);
        prices.add(25);
        prices.add(30);

        int rises = 0;
        int biggestJump = 0;
        for (int i = 0; i < prices.size() - 1; i++)
        {
            int today = prices.get(i);
            int tomorrow = prices.get(i + 1);
            if (tomorrow > today)
            {
                rises++;
                if (tomorrow - today > biggestJump)
                {
                    biggestJump = tomorrow - today;
                }
            }
        }
        System.out.println("Rises: " + rises);
        System.out.println("Biggest jump: " + biggestJump);
    }
}

Lines 18 and 19 copy the pair into two int variables, which unboxes them and gives them readable names.

i today tomorrow Rise? rises biggestJump
0 20 22 yes 1 2
1 22 21 no 1 2
2 21 25 yes 2 4
3 25 30 yes 3 5

At i = 4, the condition 4 < 4 is false, so get(5) is never called.

Rises: 3
Biggest jump: 5

Watch out

Writing the usual i < prices.size() in a pair loop goes one step too far:

PairsTooFar.java
import java.util.ArrayList;

public class PairsTooFar
{
    public static void main(String[] args)
    {
        ArrayList<Integer> prices = new ArrayList<Integer>();
        prices.add(20);
        prices.add(22);
        prices.add(21);
        prices.add(25);
        prices.add(30);

        for (int i = 0; i < prices.size(); i++)
        {
            System.out.println(prices.get(i) + " -> " + prices.get(i + 1));
        }
    }
}
20 -> 22
22 -> 21
21 -> 25
25 -> 30
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 5 out of bounds for length 5

The four real pairs print, then at i = 4 the call prices.get(i + 1) asks for index 5 and the program crashes. (The exact message wording can vary between Java versions.) The fix is i < prices.size() - 1. Another correct form starts at i = 1 and compares get(i - 1) with get(i).

Duplicates

To find out whether any value appears twice, every element must be compared with every other element, not only its neighbor, since the two copies could be far apart. That calls for nested loops (Topic 2.11): the outer loop picks an element at index i, and the inner loop compares it with every element after it, starting at j = i + 1. Starting after i means an element is never compared with itself, and each pair is checked only once.

Worked example: Is any ID repeated?

HasDuplicate.java
import java.util.ArrayList;

public class HasDuplicate
{
    public static void main(String[] args)
    {
        ArrayList<Integer> ids = new ArrayList<Integer>();
        ids.add(104);
        ids.add(317);
        ids.add(250);
        ids.add(317);
        ids.add(999);

        boolean duplicate = false;
        for (int i = 0; i < ids.size(); i++)
        {
            for (int j = i + 1; j < ids.size(); j++)
            {
                int first = ids.get(i);
                int second = ids.get(j);
                if (first == second)
                {
                    duplicate = true;
                    System.out.println("Match at " + i + " and " + j);
                }
            }
        }
        System.out.println("Has duplicate: " + duplicate);
    }
}

The comparisons run like this. Only one is a match.

i j values checked Match?
0 1, 2, 3, 4 no (104 appears once)
1 2, 3, 4 at j = 3: 317 and 317
2 3, 4 no
3 4 no
4 none inner loop does not run
Match at 1 and 3
Has duplicate: true

Five elements take 4 + 3 + 2 + 1 = 10 comparisons. If no match had been found, duplicate would have stayed false, which is how the algorithm reports the absence of duplicates.

Key idea

A pair loop stops at i < list.size() - 1 so get(i + 1) stays in range. A duplicate check compares each element with every later element, using an inner loop that starts at j = i + 1.

Watch out

Lines 19 and 20 of HasDuplicate copy the elements into int variables before comparing them. Skipping that step looks harmless, but it changes what == compares:

IntegerEqualsBug.java
import java.util.ArrayList;

public class IntegerEqualsBug
{
    public static void main(String[] args)
    {
        ArrayList<Integer> ids = new ArrayList<Integer>();
        ids.add(1000);
        ids.add(2000);
        ids.add(1000);

        boolean duplicate = false;
        for (int i = 0; i < ids.size(); i++)
        {
            for (int j = i + 1; j < ids.size(); j++)
            {
                if (ids.get(i) == ids.get(j))
                {
                    duplicate = true;
                }
            }
        }
        System.out.println("Has duplicate: " + duplicate);
    }
}
Has duplicate: false

ids.get(i) and ids.get(j) are both Integer objects, so == compares their references (Topic 2.6) and asks whether they are the same object. The two 1000s are equal values stored in different objects, so the answer is false. (For small numbers Java may reuse one object, so this bug can stay hidden until the values grow.) Unbox first, with int first = ids.get(i); and int second = ids.get(j);, and compare the int values; that version prints Has duplicate: true. For lists of String, compare with equals.

Pause and try

Question 5 · Debugging

codes holds 4, 8, and 15, so there are no duplicates, but this program prints Has duplicate: true. Find the error and write the corrected line.

DupSelfBug.java
import java.util.ArrayList;

public class DupSelfBug
{
    public static void main(String[] args)
    {
        ArrayList<Integer> codes = new ArrayList<Integer>();
        codes.add(4);
        codes.add(8);
        codes.add(15);

        boolean duplicate = false;
        for (int i = 0; i < codes.size(); i++)
        {
            for (int j = 0; j < codes.size(); j++)
            {
                int first = codes.get(i);
                int second = codes.get(j);
                if (first == second)
                {
                    duplicate = true;
                }
            }
        }
        System.out.println("Has duplicate: " + duplicate);
    }
}
Check your reasoning

The error is on line 15. The inner loop starts at j = 0, so on its very first pass i and j are both 0, and the code compares codes.get(0) with itself. 4 equals 4, so duplicate becomes true even though every value is different.

Corrected line:

for (int j = i + 1; j < codes.size(); j++)

Now each element is compared only with the elements after it, never with itself, and the program prints Has duplicate: false.

Reread 4.10.3 Consecutive Pairs and Duplicates if you missed the error.

Question 6 · Tracing

What does this program print? Give the comparison made for each value of i.

AdjacentRepeats.java
import java.util.ArrayList;

public class AdjacentRepeats
{
    public static void main(String[] args)
    {
        ArrayList<String> words = new ArrayList<String>();
        words.add("the");
        words.add("the");
        words.add("cat");
        words.add("sat");
        words.add("sat");
        words.add("sat");

        int repeats = 0;
        for (int i = 1; i < words.size(); i++)
        {
            if (words.get(i).equals(words.get(i - 1)))
            {
                repeats++;
            }
        }
        System.out.println(repeats);
    }
}
Check your reasoning
3
i words.get(i - 1) words.get(i) Equal? repeats
1 the the yes 1
2 the cat no 1
3 cat sat no 1
4 sat sat yes 2
5 sat sat yes 3

This loop starts at 1 and looks back at i - 1, so it may run all the way to i < words.size(). Three sats in a row make two pairs, which is why the count is 3, not 2.

Reread 4.10.3 Consecutive Pairs and Duplicates if your trace differed.

Section 4 of 8

4.10.4 Inserting While Traversing

A race app keeps finish times in order, fastest first. When a new runner finishes, the time has to go into the right place, not at the end. With an array this was hard, because an array’s length cannot change. An ArrayList can grow, and its add(index, value) method from Topic 4.8 makes room by moving the element at index, and every element after it, one place to the right.

To insert in order, traverse until you find the first element bigger than the new value, then add the new value at that index.

Worked example: Inserting into a sorted list

The method insertInOrder takes the list as a parameter. As in Topic 3.6, the parameter refers to the same ArrayList object as times in main, so the insertion changes the caller’s list. Printing an ArrayList with println shows its elements in order, inside square brackets, separated by commas.

InsertInOrder.java
import java.util.ArrayList;

public class InsertInOrder
{
    public static void insertInOrder(ArrayList<Integer> list, int value)
    {
        int index = 0;
        while (index < list.size() && list.get(index) < value)
        {
            index++;
        }
        list.add(index, value);
    }

    public static void main(String[] args)
    {
        ArrayList<Integer> times = new ArrayList<Integer>();
        times.add(10);
        times.add(25);
        times.add(40);
        times.add(55);

        System.out.println(times);
        insertInOrder(times, 30);
        System.out.println(times);
        insertInOrder(times, 70);
        System.out.println(times);
        insertInOrder(times, 5);
        System.out.println(times);
    }
}

Inserting 30 into [10, 25, 40, 55]: line 8 moves index forward while the element there is less than 30.

index index < 4? list.get(index) < 30? Action
0 true 10, true index = 1
1 true 25, true index = 2
2 true 40, false loop ends

Line 12 runs add(2, 30): 40 and 55 each move one place right, and 30 lands at index 2.

Inserting 70: every element is less than 70, so index climbs to 5, the size of the list. Now 5 < 5 is false, and because && short-circuits (Topic 2.5), list.get(5) is never evaluated. add(5, 70) is allowed: an insertion index may go from 0 up to and including size(), and size() means “at the end.”

Inserting 5: 10 < 5 is false right away, so add(0, 5) puts 5 at the front.

[10, 25, 40, 55]
[10, 25, 30, 40, 55]
[10, 25, 30, 40, 55, 70]
[5, 10, 25, 30, 40, 55, 70]

Inserting during a traversal needs one more step. Say a to-do list should get a study task in front of every test. When the loop finds test at index i and inserts study there, test moves to index i + 1. The loop’s own i++ would land right back on that same test, insert again, and repeat forever. The fix is an extra i++ after inserting, which moves past the new element so the loop’s i++ then moves past the test.

Worked example: Adding a study task before each test

InsertStudy.java
import java.util.ArrayList;

public class InsertStudy
{
    public static void main(String[] args)
    {
        ArrayList<String> tasks = new ArrayList<String>();
        tasks.add("laundry");
        tasks.add("test");
        tasks.add("dishes");
        tasks.add("test");

        for (int i = 0; i < tasks.size(); i++)
        {
            if (tasks.get(i).equals("test"))
            {
                tasks.add(i, "study");
                i++;
            }
        }
        System.out.println(tasks);
    }
}
i at check tasks.get(i) Action List afterward
0 laundry none [laundry, test, dishes, test]
1 test insert at 1; i becomes 2 [laundry, study, test, dishes, test]
3 dishes none (same)
4 test insert at 4; i becomes 5 [laundry, study, test, dishes, study, test]

After the second insertion, the loop’s i++ makes i 6, and 6 < 6 is false. Notice that line 13 calls tasks.size() on every check. The list grew from 4 to 6 elements, and the loop kept up because it asked for the current size each time.

[laundry, study, test, dishes, study, test]

Watch out

Without the extra i++ on line 18, the loop never ends:

for (int i = 0; i < tasks.size(); i++)
{
    if (tasks.get(i).equals("test"))
    {
        tasks.add(i, "study");
    }
}

At i = 1 it inserts study, and test moves to index 2. The loop’s i++ makes i 2, which finds the same test again, inserts again, and so on. The list keeps growing and the program never finishes on its own. When you insert at or before the current index, step i past what you inserted.

Pause and try

Question 7 · Tracing

What does this program print? Show the list after each insertion.

InsertAfterEven.java
import java.util.ArrayList;

public class InsertAfterEven
{
    public static void main(String[] args)
    {
        ArrayList<Integer> nums = new ArrayList<Integer>();
        nums.add(3);
        nums.add(4);
        nums.add(7);
        nums.add(8);

        for (int i = 0; i < nums.size(); i++)
        {
            if (nums.get(i) % 2 == 0)
            {
                nums.add(i + 1, 0);
                i++;
            }
        }
        System.out.println(nums);
    }
}
Check your reasoning
[3, 4, 0, 7, 8, 0]
i at check nums.get(i) Action List afterward
0 3 odd, none [3, 4, 7, 8]
1 4 insert 0 at 2; i becomes 2 [3, 4, 0, 7, 8]
3 7 odd, none (same)
4 8 insert 0 at 5; i becomes 5 [3, 4, 0, 7, 8, 0]

Then the loop’s i++ makes i 6, and 6 < 6 is false. The extra i++ skips each inserted 0, which matters here because 0 is even.

Reread 4.10.4 Inserting While Traversing if your trace differed.

Question 8 · Multiple choice

In the program from Question 7, suppose the i++; inside the if (line 18) is deleted. What happens?

  1. The output is the same, [3, 4, 0, 7, 8, 0]
  2. A 0 is inserted after every element: [3, 0, 4, 0, 7, 0, 8, 0]
  3. The loop never ends, because each inserted 0 is itself even
  4. An IndexOutOfBoundsException is thrown at add(i + 1, 0)
Check your reasoning

C. At i = 1, a 0 goes in at index 2. The loop’s i++ moves to index 2, which holds that new 0. It is even, so another 0 goes in at index 3, then the loop visits it, and so on. The list keeps growing and the loop never catches up to the end.

  • A is wrong because the extra i++ is what skipped each inserted 0.
  • B is wrong because the code inserts only after even values, and the new 0s keep triggering more insertions.
  • D is wrong because i + 1 is at most size() here, and inserting at size() is allowed.

Reread 4.10.4 Inserting While Traversing if you chose differently.

Section 5 of 8

4.10.5 Deleting While Traversing

The teacher from 4.10.2 wants to drop every score below 60 from a list before computing a class average. Removing elements one at a time during a traversal sounds simple, but remove(i) from Topic 4.8 moves every later element one place left. The element that was at i + 1 is now at i, and a loop that goes on to i + 1 never looks at it.

Worked example: The skipped element

This code tries to remove every score below 60 from [55, 48, 90, 30]. The correct answer is [90].

RemoveSkipBug.java
import java.util.ArrayList;

public class RemoveSkipBug
{
    public static void main(String[] args)
    {
        ArrayList<Integer> scores = new ArrayList<Integer>();
        scores.add(55);
        scores.add(48);
        scores.add(90);
        scores.add(30);

        for (int i = 0; i < scores.size(); i++)
        {
            if (scores.get(i) < 60)
            {
                scores.remove(i);
            }
        }
        System.out.println(scores);
    }
}
i scores.get(i) Action List afterward
0 55 remove 0 [48, 90, 30]
1 90 none [48, 90, 30]
2 30 remove 2 [48, 90]
[48, 90]

When 55 was removed, 48 slid into index 0. The loop then moved on to index 1, so 48 was never checked. No error appears; the answer is just wrong, which makes this bug easy to miss. It strikes whenever two elements to delete sit next to each other.

There are two standard fixes. The first moves forward but adds 1 to the index only when nothing was removed. A while loop makes that easy to write.

RemoveForwardFixed.java
import java.util.ArrayList;

public class RemoveForwardFixed
{
    public static void main(String[] args)
    {
        ArrayList<Integer> scores = new ArrayList<Integer>();
        scores.add(55);
        scores.add(48);
        scores.add(90);
        scores.add(30);

        int i = 0;
        while (i < scores.size())
        {
            if (scores.get(i) < 60)
            {
                scores.remove(i);
            }
            else
            {
                i++;
            }
        }
        System.out.println(scores);
    }
}
i scores.get(i) Action List afterward
0 55 remove 0 [48, 90, 30]
0 48 remove 0 [90, 30]
0 90 i becomes 1 [90, 30]
1 30 remove 1 [90]

The check at i = 1 then finds 1 < 1 false. The program prints [90].

The second fix traverses backward, from the last index down to 0. Removing at i moves only the elements to the right of i, and those have already been checked, so nothing that still needs checking moves.

RemoveBackward.java
import java.util.ArrayList;

public class RemoveBackward
{
    public static void main(String[] args)
    {
        ArrayList<Integer> scores = new ArrayList<Integer>();
        scores.add(55);
        scores.add(48);
        scores.add(90);
        scores.add(30);

        for (int i = scores.size() - 1; i >= 0; i--)
        {
            if (scores.get(i) < 60)
            {
                scores.remove(i);
            }
        }
        System.out.println(scores);
    }
}
i scores.get(i) Action List afterward
3 30 remove 3 [55, 48, 90]
2 90 none [55, 48, 90]
1 48 remove 1 [55, 90]
0 55 remove 0 [90]
[90]

Key idea

After remove(i), the next element slides into index i. To delete during a traversal, either add 1 to i only when nothing was removed, or traverse backward from size() - 1 to 0.

Watch out

An enhanced for loop cannot be used to delete, even with your own index counter, as Topic 4.9 warned:

RemoveEnhancedFor.java
import java.util.ArrayList;

public class RemoveEnhancedFor
{
    public static void main(String[] args)
    {
        ArrayList<Integer> scores = new ArrayList<Integer>();
        scores.add(55);
        scores.add(90);
        scores.add(48);

        int index = 0;
        for (int score : scores)
        {
            if (score < 60)
            {
                scores.remove(index);
            }
            index++;
        }
        System.out.println(scores);
    }
}
Exception in thread "main" java.util.ConcurrentModificationException

The 55 is removed on the first pass. When the loop asks for the next element, Java notices that the list changed size during the loop and throws a ConcurrentModificationException, so nothing prints. The same is true of adding. Use an indexed for or while loop whenever the list’s size changes.

Pause and try

Question 9 · Tracing

What does this program print? Show the list after each removal.

RemoveShortWords.java
import java.util.ArrayList;

public class RemoveShortWords
{
    public static void main(String[] args)
    {
        ArrayList<String> words = new ArrayList<String>();
        words.add("a");
        words.add("be");
        words.add("cat");
        words.add("do");
        words.add("eagle");

        for (int i = words.size() - 1; i >= 0; i--)
        {
            if (words.get(i).length() < 3)
            {
                words.remove(i);
            }
        }
        System.out.println(words);
    }
}
Check your reasoning
[cat, eagle]
i words.get(i) Length below 3? List afterward
4 eagle no [a, be, cat, do, eagle]
3 do yes, remove [a, be, cat, eagle]
2 cat no (same)
1 be yes, remove [a, cat, eagle]
0 a yes, remove [cat, eagle]

a and be are next to each other, the case that breaks a plain forward loop, but traversing backward still checks both.

Reread 4.10.5 Deleting While Traversing if your trace differed.

Question 10 · Multiple choice

Each segment below starts with its list holding [2, 4, 5, 6] and is meant to remove every even number, leaving [5]. Which segments work?

// I
for (int i = 0; i < list.size(); i++)
{
    if (list.get(i) % 2 == 0)
    {
        list.remove(i);
    }
}

// II
for (int i = list.size() - 1; i >= 0; i--)
{
    if (list.get(i) % 2 == 0)
    {
        list.remove(i);
    }
}

// III
int k = 0;
while (k < list.size())
{
    if (list.get(k) % 2 == 0)
    {
        list.remove(k);
    }
    else
    {
        k++;
    }
}
  1. I only
  2. II only
  3. II and III only
  4. I, II, and III
Check your reasoning

C. Segment II traverses backward, so removals move only elements already checked. Segment III adds 1 to k only when nothing was removed. Both leave [5].

  • A and D are wrong because segment I skips an element: removing 2 at index 0 slides 4 into index 0, and the loop moves on to index 1. Segment I leaves [4, 5].
  • B is wrong because it leaves out segment III, which is also correct.

Reread 4.10.5 Deleting While Traversing if you chose differently.

Section 6 of 8

4.10.6 Shifting, Rotating, and Reversing

A weather display shows the last four temperature readings. When a new reading arrives, the oldest one drops off the front and the new one joins at the back. A board game passes the turn around a table, and after the last player it is the first player’s turn again. In Topic 4.5, moving every element of an array one place took a loop that copied element after element. With an ArrayList, remove and add do the moving for you.

Two terms describe these moves:

  • To shift a list is to move every element one place left or right. One element falls off one end, and a new value fills the empty spot at the other end.
  • To rotate a list is to shift it with wraparound: the element that falls off one end comes back in at the other end, so no element is lost.

Worked example: Shifting a window of readings

ShiftWindow.java
import java.util.ArrayList;

public class ShiftWindow
{
    public static void main(String[] args)
    {
        ArrayList<Integer> window = new ArrayList<Integer>();
        window.add(3);
        window.add(5);
        window.add(4);
        window.add(6);

        System.out.println(window);
        window.remove(0);
        window.add(8);
        System.out.println(window);
        window.remove(window.size() - 1);
        window.add(0, 0);
        System.out.println(window);
    }
}
  • Shift left (lines 14–15): remove(0) drops the oldest reading, 3, and moves the rest left. add(8) puts the new reading at the end. The list still has 4 elements.
  • Shift right (lines 17–18): remove(window.size() - 1) drops the last element, 8. add(0, 0) inserts the filler value 0 at the front, which moves everything else right.
[3, 5, 4, 6]
[5, 4, 6, 8]
[0, 5, 4, 6]

A rotation uses the fact that remove returns the element it removed (Topic 4.8). Save it, then add it back at the other end.

Worked example: Passing the turn

RotateTurns.java
import java.util.ArrayList;

public class RotateTurns
{
    public static void main(String[] args)
    {
        ArrayList<String> turns = new ArrayList<String>();
        turns.add("Ana");
        turns.add("Ben");
        turns.add("Cy");
        turns.add("Dee");

        System.out.println(turns);
        String first = turns.remove(0);
        turns.add(first);
        System.out.println(turns);
        String last = turns.remove(turns.size() - 1);
        turns.add(0, last);
        System.out.println(turns);
    }
}
  • Rotate left (lines 14–15): remove(0) takes Ana off the front and returns her name, which is stored in first. add(first) puts her at the back.
  • Rotate right (lines 17–18): remove(turns.size() - 1) takes Ana off the back, and add(0, last) puts her at the front again, undoing the left rotation.
[Ana, Ben, Cy, Dee]
[Ben, Cy, Dee, Ana]
[Ana, Ben, Cy, Dee]

The two steps can also be written as one statement, turns.add(turns.remove(0));. Java runs the inner call first, and the element it returns becomes the argument of add.

To reverse a list in place, swap the first and last elements, then the second and second-to-last, and so on toward the middle. A swap needs a temporary variable, just as with arrays in Topic 4.5, because the first set overwrites one of the two values.

Worked example: Reversing a list by swapping

ReverseList.java
import java.util.ArrayList;

public class ReverseList
{
    public static void main(String[] args)
    {
        ArrayList<Integer> nums = new ArrayList<Integer>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);

        int last = nums.size() - 1;
        for (int i = 0; i < nums.size() / 2; i++)
        {
            int temp = nums.get(i);
            nums.set(i, nums.get(last - i));
            nums.set(last - i, temp);
        }
        System.out.println(nums);
    }
}

last is 4. The partner of index i is last - i: 0 pairs with 4, and 1 pairs with 3.

i temp After set(i, …) After set(last - i, temp)
0 1 [5, 2, 3, 4, 5] [5, 2, 3, 4, 1]
1 2 [5, 4, 3, 4, 1] [5, 4, 3, 2, 1]

At i = 2 the check 2 < 5 / 2 is 2 < 2, false, so the loop ends. The middle element, 3, has no partner and stays put. For a list of 6, 6 / 2 is 3 and the loop swaps three pairs.

[5, 4, 3, 2, 1]

Watch out

A reverse loop that runs over the whole list swaps every pair twice:

ReverseTwice.java
import java.util.ArrayList;

public class ReverseTwice
{
    public static void main(String[] args)
    {
        ArrayList<Integer> nums = new ArrayList<Integer>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);

        int last = nums.size() - 1;
        for (int i = 0; i < nums.size(); i++)
        {
            int temp = nums.get(i);
            nums.set(i, nums.get(last - i));
            nums.set(last - i, temp);
        }
        System.out.println(nums);
    }
}
[1, 2, 3, 4, 5]

By the end of i = 1 the list is reversed. Then i = 2 swaps the middle with itself, i = 3 swaps indices 3 and 1 back, and i = 4 swaps indices 4 and 0 back, restoring the original order. Students write i < nums.size() from habit, because most traversals use it. A reverse must stop halfway, at i < nums.size() / 2.

Pause and try

Question 11 · Tracing

What does this program print? Show the list after each pass.

RotateLoop.java
import java.util.ArrayList;

public class RotateLoop
{
    public static void main(String[] args)
    {
        ArrayList<Integer> nums = new ArrayList<Integer>();
        nums.add(1);
        nums.add(2);
        nums.add(3);
        nums.add(4);
        nums.add(5);

        for (int k = 0; k < 2; k++)
        {
            nums.add(0, nums.remove(nums.size() - 1));
        }
        System.out.println(nums);
    }
}
Check your reasoning
[4, 5, 1, 2, 3]
k Removed from the end List afterward
0 5 [5, 1, 2, 3, 4]
1 4 [4, 5, 1, 2, 3]

Each pass is a rotate right: the last element is removed, and add(0, …) puts it at the front. Two passes rotate right by 2.

Reread 4.10.6 Shifting, Rotating, and Reversing if your trace differed.

Question 12 · Tracing

This reverse leaves out the temporary variable. What does it print?

SwapNoTemp.java
import java.util.ArrayList;

public class SwapNoTemp
{
    public static void main(String[] args)
    {
        ArrayList<Integer> nums = new ArrayList<Integer>();
        nums.add(10);
        nums.add(20);
        nums.add(30);
        nums.add(40);
        nums.add(50);
        nums.add(60);

        int last = nums.size() - 1;
        for (int i = 0; i < nums.size() / 2; i++)
        {
            nums.set(i, nums.get(last - i));
            nums.set(last - i, nums.get(i));
        }
        System.out.println(nums);
    }
}
Check your reasoning
[60, 50, 40, 40, 50, 60]

last is 5, and the loop runs for i = 0, 1, 2.

i After set(i, get(last - i)) After set(last - i, get(i))
0 [60, 20, 30, 40, 50, 60] (no change)
1 [60, 50, 30, 40, 50, 60] (no change)
2 [60, 50, 40, 40, 50, 60] (no change)

The first set overwrites the value at i before it is saved anywhere, so the second set copies the new value back. The front half is lost and the list ends up a mirror of its back half. Saving get(i) in temp first fixes it.

Reread 4.10.6 Shifting, Rotating, and Reversing if your trace differed.

Section 7 of 8

4.10.7 Building a Filtered List

Deleting from a list changes it for good. Often a program needs the original data later, such as every score for the class average and a separate list of just the failing scores to send home. Instead of removing elements from the original, you can build a second list.

The pattern: create a new, empty ArrayList, traverse the original, and add each element that has the property. The result is a filtered list. The original is only read, never changed, so an enhanced for loop is safe, and no element can be skipped.

Worked example: Fruits that come before m

BeforeM.java
import java.util.ArrayList;

public class BeforeM
{
    public static void main(String[] args)
    {
        ArrayList<String> fruits = new ArrayList<String>();
        fruits.add("apple");
        fruits.add("kiwi");
        fruits.add("mango");
        fruits.add("fig");
        fruits.add("peach");

        ArrayList<String> early = new ArrayList<String>();
        for (String fruit : fruits)
        {
            if (fruit.compareTo("m") < 0)
            {
                early.add(fruit);
            }
        }
        System.out.println(early);
        System.out.println(fruits.size() + " " + early.size());
    }
}

compareTo (Topic 1.15) returns a negative number when the first string comes before the second in dictionary order.

fruit fruit.compareTo("m") < 0? early afterward
apple yes [apple]
kiwi yes [apple, kiwi]
mango no [apple, kiwi]
fig yes [apple, kiwi, fig]
peach no [apple, kiwi, fig]

mango starts with m, but it is longer than "m", so it comes after "m" in dictionary order. The last line shows that fruits still has all 5 elements.

[apple, kiwi, fig]
5 3

Watch out

Adding to the wrong list is a one-word mistake with a loud result:

FilterAddBug.java
import java.util.ArrayList;

public class FilterAddBug
{
    public static void main(String[] args)
    {
        ArrayList<Integer> nums = new ArrayList<Integer>();
        nums.add(4);
        nums.add(12);
        nums.add(7);
        nums.add(15);

        ArrayList<Integer> big = new ArrayList<Integer>();
        for (int n : nums)
        {
            if (n > 10)
            {
                nums.add(n);
            }
        }
        System.out.println(big);
    }
}
Exception in thread "main" java.util.ConcurrentModificationException

Line 18 adds to nums, the list being traversed, instead of big. When 12 is added, nums changes size in the middle of the enhanced for loop, and the next step of the loop throws. Change line 18 to big.add(n); and the program prints [12, 15].

Pause and try

Question 13 · Short answer

A student wants the scores below 60 in their own list, and also needs the full list of scores for an average afterward. They plan to copy the failing scores into a new list and then delete them from the original. Explain why building the filtered list alone is the better plan here, and name one mistake the deleting step could add.

Check your reasoning

The program needs the full list later, so deleting from the original destroys data it still needs: the average would then be computed without the failing scores. Building a new list reads the original without changing it, so both lists are available.

A complete answer mentions:

  • Filtering into a new list leaves the original unchanged.
  • Deleting changes the original permanently, which here makes the later average wrong.
  • Deleting during a traversal can skip elements (with a forward loop that always adds 1 to the index) or throw a ConcurrentModificationException (with an enhanced for loop). Building a new list avoids both.

Reread 4.10.7 Building a Filtered List and 4.10.5 Deleting While Traversing if your answer differed.

Section 8 of 8

4.10.8 Traversing Several Collections at Once

A class keeps student names in an array and their GPAs in an ArrayList<Double>, with the GPA at index i belonging to the name at index i. To print the honor roll, the program has to read both collections together. Some algorithms need several strings, arrays, or lists traversed at the same time.

When the collections line up by position, one loop index can serve all of them: names[i] and gpas.get(i) refer to the same student. The one rule is that the loop bound must keep i valid in every collection it reads.

Worked example: Building an honor roll from two collections

HonorRoll.java
import java.util.ArrayList;

public class HonorRoll
{
    public static void main(String[] args)
    {
        String[] names = {"Ava", "Ben", "Cleo", "Dev"};
        ArrayList<Double> gpas = new ArrayList<Double>();
        gpas.add(3.8);
        gpas.add(3.2);
        gpas.add(3.9);
        gpas.add(3.5);

        ArrayList<String> honorRoll = new ArrayList<String>();
        for (int i = 0; i < names.length; i++)
        {
            if (gpas.get(i) >= 3.5)
            {
                honorRoll.add(names[i]);
            }
        }
        System.out.println(honorRoll);
        System.out.println(gpas);
    }
}

The array and the list both have 4 elements, so i < names.length keeps i in range for both. Line 17 reads a Double from the list and compares it with 3.5; line 19 adds the matching name to a new list.

i names[i] gpas.get(i) 3.5 or higher? honorRoll afterward
0 Ava 3.8 yes [Ava]
1 Ben 3.2 no [Ava]
2 Cleo 3.9 yes [Ava, Cleo]
3 Dev 3.5 yes [Ava, Cleo, Dev]
[Ava, Cleo, Dev]
[3.8, 3.2, 3.9, 3.5]

A string can be one of the collections, too. Here a quiz’s answer key is a String, one letter per question, and a student’s answers are in an ArrayList<String>.

Worked example: Grading a quiz against a key

QuizKey.java
import java.util.ArrayList;

public class QuizKey
{
    public static void main(String[] args)
    {
        String key = "BDAC";
        ArrayList<String> answers = new ArrayList<String>();
        answers.add("B");
        answers.add("A");
        answers.add("A");
        answers.add("C");

        int correct = 0;
        ArrayList<Integer> missed = new ArrayList<Integer>();
        for (int i = 0; i < answers.size(); i++)
        {
            if (answers.get(i).equals(key.substring(i, i + 1)))
            {
                correct++;
            }
            else
            {
                missed.add(i + 1);
            }
        }
        System.out.println("Correct: " + correct);
        System.out.println("Missed: " + missed);
    }
}

On each pass, answers.get(i) is compared with key.substring(i, i + 1), the letter at index i of the key. A match adds to correct; a miss adds the question number, i + 1, to the new list missed.

i Student Key Result
0 B B correct = 1
1 A D missed = [2]
2 A A correct = 2
3 C C correct = 3
Correct: 3
Missed: [2]

Key idea

To traverse collections side by side, use one index for all of them, and bound the loop by a collection that every index is valid for.

Watch out

If the collections have different lengths, the bound matters. This student skipped the last question, so the list has only 3 answers, but the loop is bounded by the key:

ShortAnswersBug.java
import java.util.ArrayList;

public class ShortAnswersBug
{
    public static void main(String[] args)
    {
        String key = "BDAC";
        ArrayList<String> answers = new ArrayList<String>();
        answers.add("B");
        answers.add("A");
        answers.add("A");

        for (int i = 0; i < key.length(); i++)
        {
            if (answers.get(i).equals(key.substring(i, i + 1)))
            {
                System.out.println("Question " + (i + 1) + ": right");
            }
            else
            {
                System.out.println("Question " + (i + 1) + ": wrong");
            }
        }
    }
}
Question 1: right
Question 2: wrong
Question 3: right
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3

At i = 3 the key still has a letter, but answers.get(3) does not exist. Bound the loop by the shorter collection, i < answers.size(); then the program prints the three lines and stops normally. (When either one could be shorter, Math.min(key.length(), answers.size()) from Topic 1.11 works as the bound.)

Pause and try

Question 14 · Multiple choice

What does this program print?

LargerOfPairs.java
import java.util.ArrayList;

public class LargerOfPairs
{
    public static void main(String[] args)
    {
        ArrayList<Integer> a = new ArrayList<Integer>();
        a.add(3);
        a.add(8);
        a.add(2);
        ArrayList<Integer> b = new ArrayList<Integer>();
        b.add(5);
        b.add(1);
        b.add(2);

        ArrayList<Integer> larger = new ArrayList<Integer>();
        for (int i = 0; i < a.size(); i++)
        {
            larger.add(Math.max(a.get(i), b.get(i)));
        }
        System.out.println(larger);
    }
}
  1. [5, 8, 2]
  2. [8]
  3. [3, 1, 2]
  4. [5, 8, 2, 2]
Check your reasoning

A. One index walks both lists. At each index, Math.max receives the two Integer elements, which Java unboxes to int because Math.max expects int values. It returns the larger: max(3, 5) is 5, max(8, 1) is 8, and max(2, 2) is 2.

  • B is the single largest value overall, a maximum algorithm, not a comparison at each position.
  • C takes the smaller of each pair, which is Math.min.
  • D adds both 2s, but the loop adds exactly one value per index, and there are 3 indices.

Reread 4.10.8 Traversing Several Collections at Once if you chose differently.

Wrap-up · 1 of 6

Putting it together

A classroom sensor records a reading every minute and stores -1 whenever it fails to read. Before the data can be used, the bad readings must go. Then the program reports the average, the readings above average, and whether any reading repeated two minutes in a row. Each job is a separate method, and each method is one algorithm from this chapter.

CleanReadings.java
import java.util.ArrayList;

public class CleanReadings
{
    public static void removeInvalid(ArrayList<Integer> list)
    {
        for (int i = list.size() - 1; i >= 0; i--)
        {
            if (list.get(i) < 0)
            {
                list.remove(i);
            }
        }
    }

    public static double average(ArrayList<Integer> list)
    {
        int total = 0;
        for (int value : list)
        {
            total += value;
        }
        return (double) total / list.size();
    }

    public static ArrayList<Integer> aboveAverage(ArrayList<Integer> list, double avg)
    {
        ArrayList<Integer> result = new ArrayList<Integer>();
        for (int value : list)
        {
            if (value > avg)
            {
                result.add(value);
            }
        }
        return result;
    }

    public static boolean hasRepeatInARow(ArrayList<Integer> list)
    {
        for (int i = 0; i < list.size() - 1; i++)
        {
            int current = list.get(i);
            int next = list.get(i + 1);
            if (current == next)
            {
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args)
    {
        ArrayList<Integer> readings = new ArrayList<Integer>();
        readings.add(12);
        readings.add(-1);
        readings.add(15);
        readings.add(15);
        readings.add(-1);
        readings.add(20);
        readings.add(9);

        removeInvalid(readings);
        System.out.println("Cleaned: " + readings);
        double avg = average(readings);
        System.out.println("Average: " + avg);
        System.out.println("Above average: " + aboveAverage(readings, avg));
        System.out.println("Repeat in a row: " + hasRepeatInARow(readings));
    }
}

Step 1, removeInvalid (lines 5–14), deletes while traversing (4.10.5). It walks backward and removes every negative reading. The list readings in main is changed, because the parameter list refers to the same object (Topic 3.6).

i list.get(i) Action List afterward
6 9 none [12, -1, 15, 15, -1, 20, 9]
5 20 none (same)
4 -1 remove 4 [12, -1, 15, 15, 20, 9]
3 15 none (same)
2 15 none (same)
1 -1 remove 1 [12, 15, 15, 20, 9]
0 12 none [12, 15, 15, 20, 9]

Step 2, average (lines 16–24), sums and divides (4.10.1). 12 + 15 + 15 + 20 + 9 = 71, and 71.0 / 5 is 14.2. Running this step after the cleanup matters: with the two -1s still in, the average would be wrong.

Step 3, aboveAverage (lines 26–37), builds a filtered list (4.10.7). It returns a new list holding 15, 15, and 20, the readings greater than 14.2, and leaves readings alone.

Step 4, hasRepeatInARow (lines 39–51), checks consecutive pairs (4.10.3). The pair loop stops at i < list.size() - 1. The first pair, 12 and 15, differs; the second, 15 and 15, matches, so the method returns true at once without checking the rest. That early return is an “at least one” check (4.10.2) written without a flag.

Cleaned: [12, 15, 15, 20, 9]
Average: 14.2
Above average: [15, 15, 20]
Repeat in a row: true
Wrap-up · 2 of 6

Common mistakes

Several ArrayList bugs look alike in the code but behave very differently when the program runs. Use this table to go from the symptom back to the cause.

What you see Likely cause Fix
Wrong answer, no error, after deleting Forward loop added 1 to i after a remove(i), skipping an element Add 1 only when nothing was removed, or traverse backward
Program never finishes Inserted at or before i without moving i past the new element Add an extra i++ after inserting
IndexOutOfBoundsException in a pair loop get(i + 1) with i < size() Use i < size() - 1
IndexOutOfBoundsException with two collections Loop bounded by the longer collection Bound by the shorter one
ConcurrentModificationException Added to or removed from the list an enhanced for loop is traversing Use an indexed loop, or add to a different list
Equal Integer values reported as different == on two Integer objects compares references Unbox into int variables first
Wrap-up · 3 of 6

Vocabulary

Term Definition Example
traversal A loop that visits the elements of a list in order for (int i = 0; i < list.size(); i++)
standard algorithm A common, reusable pattern for a job such as a sum, a maximum, or a count Track the largest value seen so far
unboxing Java’s automatic conversion of an Integer to an int (or a Double to a double) int temp = temps.get(i);
precondition Something that must be true before an algorithm runs The list is not empty
consecutive elements Elements next to each other, at indices i and i + 1 get(i) and get(i + 1)
insert Add an element at a given index, moving later elements one place right tasks.add(i, "study");
shift Move every element one place, dropping one at an end and filling the other end remove(0) then add(8)
rotate Shift with wraparound, so the element that leaves one end returns at the other list.add(list.remove(0));
reverse Put the elements in the opposite order, by swapping pairs toward the middle [1, 2, 3] becomes [3, 2, 1]
filtered list A new list holding only the elements of another list that have a property early.add(fruit);
Wrap-up · 4 of 6

Chapter summary

  • Array algorithms carry over to ArrayList by using size(), get(i), and set(i, value). Start a minimum or maximum at an element of the list, and remember that an average needs a non-empty list.
  • “At least one” flags start false, “all” flags start true, and counters start at 0. None of them is ever switched back inside the loop.
  • Pair loops stop at i < size() - 1. Duplicate checks use nested loops with j = i + 1, and Integer elements are unboxed before comparing with ==.
  • Inserting at index i moves later elements right, so step past the new element. Deleting at index i moves later elements left, so add 1 only when nothing was removed, or traverse backward. Never change a list’s size inside an enhanced for loop.
  • remove and add shift and rotate a list, since remove returns the element it took out. A reverse swaps pairs with a temporary variable and stops at size() / 2.
  • A filtered list is built by adding matching elements to a new list, leaving the original unchanged. Collections traversed side by side share one index, bounded so it is valid for every one of them.
Wrap-up · 5 of 6

Review questions

Question 15 · Tracing

This program is meant to remove every negative change and then add up what is left, which should give [5, 4] and Sum: 9. Write the exact output, and explain where it goes wrong.

RemoveThenSum.java
import java.util.ArrayList;

public class RemoveThenSum
{
    public static void main(String[] args)
    {
        ArrayList<Integer> changes = new ArrayList<Integer>();
        changes.add(-2);
        changes.add(-3);
        changes.add(5);
        changes.add(-1);
        changes.add(4);

        for (int i = 0; i < changes.size(); i++)
        {
            if (changes.get(i) < 0)
            {
                changes.remove(i);
            }
        }
        int sum = 0;
        for (int change : changes)
        {
            sum += change;
        }
        System.out.println(changes);
        System.out.println("Sum: " + sum);
    }
}
Check your reasoning
[-3, 5, 4]
Sum: 6
i changes.get(i) Action List afterward
0 -2 remove 0 [-3, 5, -1, 4]
1 5 none (same)
2 -1 remove 2 [-3, 5, 4]

Then 3 < 3 is false. Removing -2 slid -3 into index 0, and the loop moved on to index 1, so -3 was never checked. The sum loop then adds -3 + 5 + 4 = 6. The sum algorithm itself is correct; it just ran on a list that the deletion step got wrong. Traversing backward in the first loop would give [5, 4] and Sum: 9.

Reread 4.10.5 Deleting While Traversing if your trace differed.

Question 16 · Debugging

This version of insertInOrder correctly prints [10, 25, 30, 40, 55], then crashes with an IndexOutOfBoundsException when inserting 70. Find the error and write the corrected line.

InsertNoBoundCheck.java
import java.util.ArrayList;

public class InsertNoBoundCheck
{
    public static void insertInOrder(ArrayList<Integer> list, int value)
    {
        int index = 0;
        while (list.get(index) < value)
        {
            index++;
        }
        list.add(index, value);
    }

    public static void main(String[] args)
    {
        ArrayList<Integer> times = new ArrayList<Integer>();
        times.add(10);
        times.add(25);
        times.add(40);
        times.add(55);

        insertInOrder(times, 30);
        System.out.println(times);
        insertInOrder(times, 70);
        System.out.println(times);
    }
}
Check your reasoning

The error is on line 8. Every element is less than 70, so index climbs to 5, the size of the list, and the condition then calls list.get(5). Index 5 does not exist, so Java throws:

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 5 out of bounds for length 5

(Wording can vary by Java version.) Inserting 30 worked only because 40 stopped the loop before it reached the end.

Corrected line:

while (index < list.size() && list.get(index) < value)

The range check must come first. When index reaches the size, index < list.size() is false, && short-circuits, and get is never called. Then add(5, 70) appends 70.

Reread 4.10.4 Inserting While Traversing if you missed the error.

Question 17 · Multiple choice

words is an ArrayList<String>, and dup starts as false. Which segment sets dup to true exactly when some word appears more than once anywhere in the list?

  1. Outer loop over i; inner loop for (int j = 0; j < words.size(); j++); set dup when words.get(i).equals(words.get(j))
  2. One loop for (int i = 0; i < words.size() - 1; i++); set dup when words.get(i).equals(words.get(i + 1))
  3. Outer loop over i; inner loop for (int j = i + 1; j < words.size(); j++); set dup when words.get(i).equals(words.get(j))
  4. Outer loop over i; inner loop for (int j = i + 1; j < words.size(); j++); set dup when words.get(i) == words.get(j)
Check your reasoning

C. Each word is compared with every later word, never with itself, and equals compares the characters.

  • A compares each word with itself when j equals i, so dup becomes true for any non-empty list. For [red, blue], which has no duplicates, it sets dup to true.
  • B checks only neighbors. For [red, blue, red] the two reds are not next to each other, so dup stays false.
  • D uses ==, which compares references (Topic 2.6), not characters. Two equal strings can be different objects, so it is not a reliable test.

Reread 4.10.3 Consecutive Pairs and Duplicates if you chose differently.

Question 18 · Tracing

What does this program print? Show the list after the rotation and after each swap.

RotateReverse.java
import java.util.ArrayList;

public class RotateReverse
{
    public static void main(String[] args)
    {
        ArrayList<String> letters = new ArrayList<String>();
        letters.add("a");
        letters.add("b");
        letters.add("c");
        letters.add("d");

        letters.add(letters.remove(0));
        int last = letters.size() - 1;
        for (int i = 0; i < letters.size() / 2; i++)
        {
            String temp = letters.get(i);
            letters.set(i, letters.get(last - i));
            letters.set(last - i, temp);
        }
        System.out.println(letters);
    }
}
Check your reasoning
[a, d, c, b]

Line 13 rotates left: remove(0) takes out a and add puts it at the end, giving [b, c, d, a]. Then the reverse runs with last = 3 for i = 0 and 1:

i Swaps indices List afterward
0 0 and 3 [a, c, d, b]
1 1 and 2 [a, d, c, b]

Reversing the rotated list does not give back the original. Order matters when algorithms are combined.

Reread 4.10.6 Shifting, Rotating, and Reversing if your trace differed.

Question 19 · Short answer

A classmate says, “When I delete while traversing, I always go backward, and I never skip anything. But going backward is just a trick; it is luck that it works.” Explain why traversing backward is guaranteed not to skip elements, using what remove(i) does to the other elements.

Check your reasoning

remove(i) moves only the elements after index i, each one place left. The elements before i keep their indices. A backward traversal has already checked every element after i, and the ones it has not checked yet are all before i. So a removal never moves an element that still needs checking, and the next index, i - 1, holds exactly the element it held before.

A complete answer mentions:

  • remove(i) shifts only the elements to the right of i.
  • Going backward, those elements have already been checked.
  • The unchecked elements, to the left, keep their indices, so none is skipped.

Reread 4.10.5 Deleting While Traversing if your explanation differed.

Question 20 · Tracing

What does this program print? Show difference and improved for each day, and explain how best is found.

WeekCompare.java
import java.util.ArrayList;

public class WeekCompare
{
    public static void main(String[] args)
    {
        ArrayList<Integer> thisWeek = new ArrayList<Integer>();
        thisWeek.add(5000);
        thisWeek.add(6200);
        thisWeek.add(4800);
        thisWeek.add(7000);
        ArrayList<Integer> lastWeek = new ArrayList<Integer>();
        lastWeek.add(5200);
        lastWeek.add(6000);
        lastWeek.add(4800);
        lastWeek.add(6500);

        int improved = 0;
        ArrayList<Integer> change = new ArrayList<Integer>();
        for (int i = 0; i < thisWeek.size(); i++)
        {
            int difference = thisWeek.get(i) - lastWeek.get(i);
            change.add(difference);
            if (difference > 0)
            {
                improved++;
            }
        }
        int best = change.get(0);
        for (int value : change)
        {
            if (value > best)
            {
                best = value;
            }
        }
        System.out.println(change);
        System.out.println("Days improved: " + improved);
        System.out.println("Best change: " + best);
    }
}
Check your reasoning
[-200, 200, 0, 500]
Days improved: 2
Best change: 500
i thisWeek.get(i) lastWeek.get(i) difference improved
0 5000 5200 -200 0
1 6200 6000 200 1
2 4800 4800 0 1
3 7000 6500 500 2

Both lists are traversed with one index and both elements are unboxed for the subtraction. Each difference is added to a new list, and a counter tallies the positive differences. A difference of 0 is not an improvement, because the condition is > 0.

The second loop is a maximum over the new list. best starts at its first element, -200, so it works even if every difference is negative. It becomes 200, stays 200 for the 0, and becomes 500.

Reread 4.10.8 Traversing Several Collections at Once or 4.10.1 Sum, Average, Minimum, and Maximum if your trace differed.

Question 21 · Multiple choice

The BeforeM program in 4.10.7 builds early, a filtered list of the fruits that come before "m", from the list fruits. After the loop, when is early.size() == fruits.size() true?

  1. Exactly when every fruit comes before "m"
  2. Exactly when at least one fruit comes before "m"
  3. Exactly when no fruit comes before "m"
  4. Exactly when early and fruits refer to the same list object
Check your reasoning

A. The filter adds one element to early for each fruit that passes the test, so early.size() counts the passing fruits. It equals fruits.size() only if every fruit passed. Comparing a filtered list’s size with the original’s is another way to answer an “all” question.

  • B would be early.size() > 0.
  • C would be early.size() == 0.
  • D is wrong because == here compares two int sizes, not references; early is always a separate, new list.

Reread 4.10.7 Building a Filtered List and 4.10.2 Any, All, and How Many if you chose differently.

Wrap-up · 6 of 6

Reflection

Check each statement. If the answer is “not yet,” reread the linked subsection.

  • I can find the sum, average, minimum, or maximum of an ArrayList, and explain why a maximum starts at an element of the list. Reread
  • I can use a flag or a counter to decide whether any or all elements have a property, or how many do. Reread
  • I can write the bound for a consecutive-pair loop and check a list for duplicates with nested loops. Reread
  • I can insert elements during a traversal without looping forever. Reread
  • I can delete elements during a traversal without skipping any, in two different ways. Reread
  • I can shift, rotate, and reverse an ArrayList, and explain why a reverse stops halfway. Reread
  • I can build a filtered list from another list without changing the original. Reread
  • I can traverse two collections side by side with one index and choose a safe loop bound. Reread