Index: src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java =================================================================== --- src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java (revision 12765) +++ src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java Mon Jun 16 21:32:45 EST 2008 @@ -4356,39 +4356,67 @@ } /** - * Flatten a list. This collection and any nested collections have their - * contents (recursively) added to the new collection. + * Flatten a collection. This collection and any nested arrays or + * collections have their contents (recursively) added to the new collection. * - * @param self a List - * @return a flattened List + * @param self a Collection to flatten + * @return a flattened Collection */ - public static List flatten(List self) { - return new ArrayList(flatten(self, new LinkedList())); + public static Collection flatten(Collection self) { + return flatten(self, createSimilarCollection(self)); } + private static Collection flatten(Collection elements, Collection addTo) { + for (Object element : elements) { + if (element instanceof Collection) { + flatten((Collection) element, addTo); + } else if (element.getClass().isArray()) { + flatten(DefaultTypeTransformation.arrayAsCollection(element), addTo); + } else { + // found a leaf + addTo.add(element); + } + } + return addTo; + } + /** - * Flatten a set. This collection and any nested collections have their - * contents (recursively) added to the new collection. + * Flatten a collection. This collection and any nested arrays or + * collections have their contents (recursively) added to the new collection. + * For any non-Array, non-Collection object which represents some sort + * of collective type, the supplied closure should yield the contained items; + * otherwise, the closure should just return any element which corresponds to a leaf. * - * @param self a Set - * @return a flattened Set + * @param self a Collection + * @param flattenUsing a closure to determine how to flatten non-Array, non-Collection elements + * @return a flattened Collection */ - public static Set flatten(Set self) { - return new HashSet(flatten(self, new LinkedList())); + public static Collection flatten(Collection self, Closure flattenUsing) { + return flatten(self, createSimilarCollection(self), flattenUsing); } - private static List flatten(Collection elements, List addTo) { - Iterator iter = elements.iterator(); - while (iter.hasNext()) { - Object element = iter.next(); + private static Collection flatten(Collection elements, Collection addTo, Closure flattenUsing) { + for (Object element : elements) { if (element instanceof Collection) { - flatten((Collection) element, addTo); - } else if (element instanceof Map) { - flatten(((Map) element).values(), addTo); + flatten((Collection) element, addTo, flattenUsing); + } else if (element.getClass().isArray()) { + flatten(DefaultTypeTransformation.arrayAsCollection(element), addTo, flattenUsing); } else { + Object flattened = flattenUsing.call(new Object[]{element}); + boolean returnedSelf = flattened == element; + if (!returnedSelf && flattened instanceof Collection) { + List list = toList((Collection)flattened); + if (list.size() == 1 && list.get(0) == element) { + returnedSelf = true; + } + } + if (flattened instanceof Collection && !returnedSelf) { + flatten((Collection) flattened, addTo, flattenUsing); + } else { - addTo.add(element); - } - } + addTo.add(element); + } + } + } return addTo; } Index: src/test/groovy/ListTest.groovy =================================================================== --- src/test/groovy/ListTest.groovy (revision 12765) +++ src/test/groovy/ListTest.groovy Mon Jun 16 21:41:57 EST 2008 @@ -81,7 +81,6 @@ def l = [1, 2, 3, "abc"] def block = {i -> println(i) } l.each(block) - l.each {i-> println(i) } } @@ -221,15 +220,51 @@ } void testListFlatten() { - def l = [[[4, 5, 6, [46, 7, "erer"]], 4, [3, 6, 78]], 4] - assert l.flatten() == [4, 5, 6, 46, 7, "erer", 4, 3, 6, 78, 4] + def orig = [[[4, 5, 6, [46, 7, "erer"]], 4, [3, 6, 78]], 4] + def flat = orig.flatten() + assert flat == [4, 5, 6, 46, 7, "erer", 4, 3, 6, 78, 4] } void testSetFlatten() { - Set l = [[[4, 5, 6, [46, 7, "erer"] as Set] as Set, 4, [3, 6, 78] as Set] as Set, 4] - assert l.flatten() == [3, 4, 5, 6, 7, 46, 78, "erer"] as Set + Set orig = [[[4, 5, 6, [46, 7, "erer"] as Set] as Set, 4, [3, 6, 78] as Set] as Set, 4] + Set flat = orig.flatten() + assert flat == [3, 4, 5, 6, 7, 46, 78, "erer"] as Set } + void testFlattenListOfMaps() { + def orig = [[a:1, b:2], [c:3, d:4]] + def flat = orig.flatten() + assert flat == orig + } + + void testFlattenListOfArrays() { + def orig = ["one".toList().toArray(), "two".toList().toArray()] + def flat = orig.flatten() + assert flat == ["o", "n", "e", "t", "w", "o"] + } + + void testFlattenListWithSuppliedClosure() { + def orig = [[[4, 5, 6, [46, 7, "erer"]], 4, [3, 6, 78]], 4] + def flat = orig.flatten{ it.iterator().toList() } + assert flat == [4, 5, 6, 46, 7, "e", "r", "e", "r", 4, 3, 6, 78, 4] + } + + void testFlattenListOfMapsWithClosure() { + def orig = [[a:1, b:2], [c:3, d:4]] + def flat = orig.flatten{ it instanceof Map ? it.values() : it } + assert flat == [1, 2, 3 ,4] + flat = orig.flatten{ it instanceof Map ? it.keySet() : it } + assert flat == ["a", "b", "c", "d"] + } + + void testFlattenSetOfMapsWithClosure() { + Set orig = [[a:1, b:2], [c:3, d:4]] as Set + Set flat = orig.flatten{ it instanceof Map ? it.values() : it } + assert flat == [1, 2, 3 ,4] as Set + flat = orig.flatten{ it instanceof Map ? it.keySet() : it } + assert flat == ["a", "b", "c", "d"] as Set + } + void testFlattenWithRanges() { def flat = [1, 3, 20..24, 33].flatten() assert flat == [1, 3, 20, 21, 22, 23, 24, 33]