7
\$\begingroup\$

I created a utility method ListUtils.partition(iterable, size) to partition a potentially large list to a list of smaller lists:

public class ListUtils {
    public static <T> List<List<T>> partition(Iterable<T> iterable, int size) {
        if (iterable == null) {
            return Collections.emptyList();
        }
        if (size < 1) {
            List<T> list = new ArrayList<>();
            for (T item : iterable) {
                list.add(item);
            }
            return Arrays.asList(list);
        }
        List<List<T>> result = new ArrayList<>();
        List<T> segment = new ArrayList<>(size);
        int i = 0;
        for (T item : iterable) {
            segment.add(item);
            if (++i == size) {
                result.add(segment);
                segment = new ArrayList<>(size);
                i = 0;
            }
        }
        if (i > 0) {
            result.add(segment);
        }
        return result;
    }
}

Unit tests:

public class ListUtilsTest {
    private <T> List<List<T>> partition(Iterable<T> iterable, int size) {
        return ListUtils.partition(iterable, size);
    }

    @Test
    public void testPartitionEmpty() {
        assertEquals(Arrays.<List<Object>>asList(), partition(Arrays.asList(), 3));
    }

    @Test
    public void testPartitionNull() {
        assertEquals(Arrays.<List<Object>>asList(), partition(null, 3));
    }

    @Test
    public void testPartitionZeroSize() {
        List<Integer> orig = Arrays.asList(1, 2, 3);
        assertEquals(Arrays.asList(orig), partition(orig, 0));
    }

    @Test
    public void testPartitionNegativeSize() {
        List<Integer> orig = Arrays.asList(1, 2, 3);
        assertEquals(Arrays.asList(orig), partition(orig, -3));
    }

    @Test
    public void testPartitionInts() {
        assertEquals(
                Arrays.asList(
                        Arrays.asList(1, 2),
                        Arrays.asList(3, 4),
                        Arrays.asList(5)),
                partition(Arrays.asList(1, 2, 3, 4, 5), 2));
    }

    @Test
    public void testPartitionStrings() {
        assertEquals(
                Arrays.asList(
                        Arrays.asList("1", "2"),
                        Arrays.asList("3", "4"),
                        Arrays.asList("5")),
                partition(Arrays.asList("1", "2", "3", "4", "5"), 2));
    }

    @Test
    public void testPartition_MultipleOfSize() {
        assertEquals(
                Arrays.asList(
                        Arrays.asList(1, 2),
                        Arrays.asList(3, 4)),
                partition(Arrays.asList(1, 2, 3, 4), 2));
    }

    @Test
    public void testPartition_Size1() {
        assertEquals(
                Arrays.asList(
                        Arrays.asList(1),
                        Arrays.asList(2),
                        Arrays.asList(3),
                        Arrays.asList(4)),
                partition(Arrays.asList(1, 2, 3, 4), 1));
    }

    @Test
    public void testPartition_BiggerSize() {
        List<Integer> orig = Arrays.asList(1, 2, 3, 4);
        assertEquals(
                Arrays.asList(orig),
                partition(orig, orig.size() + 11));
    }
}

Can you improve this? Are there interesting corner cases I might have missed in the tests?

\$\endgroup\$
1
  • \$\begingroup\$ You could overload the method, with one that takes a List instead of an Iterable and return a List of subLists of the original list. \$\endgroup\$
    – bowmore
    Commented Aug 27, 2014 at 17:47

3 Answers 3

5
\$\begingroup\$

I don't agree with your method of handling 0 or negative partition sizes.

A partition is always meant to be a sub-section of a whole. A partition with size 0 is impossible if the whole is non-empty, as there is no way to cut up a whole into sub-sections of size 0. (Note, an empty whole will of course be divisible into partitions of size 0). The same logic applies for negative values. I would think raising a RuntimeException such as an IllegalArgumentException would make more sense in that context. null objects shouldn't be partition-able either, as they are not even empty "wholes".

Now as for handling sub-section sizes larger than the size of your list, you already define that by having incomplete sub-sections if your sub-section size doesn't divide into your container size. You are missing a test for this, however.

A final remark: since Iterable isn't confined to Collection's measly 2 billion item limit for indexing, you might consider using a long for your partition size if you plan to use this for large data sets.

\$\endgroup\$
0
4
\$\begingroup\$

You can remove the variable i, which I believe makes it cleaner (though possibly at the cost of some performance).

List<List<T>> result = new ArrayList<>();
List<T> segment = new ArrayList<>(size);
for (T item : iterable) {
    segment.add(item);
    if (segment.size() == size) {
        result.add(segment);
        segment = new ArrayList<>(size);
    }
}
if (segment.size() > 0) {
    result.add(segment);
}
\$\endgroup\$
0
2
\$\begingroup\$

Bug:

assertEquals(partition(Collections.emptyList(), 0).size(), partition(Collection.emptyList(),1).size())

fails.

That is, there is a special case in which partitioning a list in partitions equal in size to or greater in size than the size of a list to be partitioned does not equal a result with 1 partition.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Indeed there is a bug, well spotted! But he bug is not what you describe. The bug is that when size < 1, partition(emptyList, size) returns an empty-list-within-a-list, but when size >= 1 it returns an empty list. So the behavior is inconsistent. It would make more sense to standardize on the the second behavior: return an empty list, never an empty-list-within-a-list. \$\endgroup\$
    – janos
    Commented Aug 27, 2014 at 12:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.