views:

156

answers:

6

If I have an ArrayList<Double> dblList and a Predicate<Double> IS_EVEN I am able to remov e all even elements from dblList using:

Collections2.filter(dblList, IS_EVEN).clear()

if dblList however is a result of a transformation like

dblList = Lists.transform(intList, TO_DOUBLE) 

this does not work any more as the transformed list is immutable :-)

Any solution?

A: 

Maybe:

Collection<Double> odds = Collections2.filter(dblList, Predicates.not(IS_EVEN));

or

dblList = Lists.newArrayList(Lists.transform(intList, TO_DOUBLE));
Collections2.filter(dblList, IS_EVEN).clear();
superfav
That's fine if I'm solely interested in the result.Instead I really want to modify the original collection indirectly.So, it is obvious, that a transformed List does not support modifying operations like set().But it's not clear to me why transform() and filter() on its own support a remove operation, a combination of both however does not support it. I'm about to degug this ...But it is not
Ditz
A: 

As long as you have no need for the intermediate collection, then you can just use Predicates.compose() to create a predicate that first transforms the item, then evaluates a predicate on the transformed item.

For example, suppose I have a List<Double> from which I want to remove all items where the Integer part is even. I already have a Function<Double,Integer> that gives me the Integer part, and a Predicate<Integer> that tells me if it is even.

I can use these to get a new predicate, INTEGER_PART_IS_EVEN

Predicate<Double> INTEGER_PART_IS_EVEN = Predicates.compose(IS_EVEN, DOUBLE_TO_INTEGER);
Collections2.filter(dblList, INTEGER_PART_IS_EVEN).clear();
Michael D
For this attempt I need the original (or at least the transformed) list. Unfortunately (in my case) it is encapsulated and I don't want to expose it. I may introduce a callback to perform the deletion internally, but this can only some kind of workaround.Instead I like to see a working Iterator.remove() operation even on cascaded transformed and filtered Collectios.
Ditz
A: 

After some tries, I think I've found it :)

final ArrayList<Integer> ints = Lists.newArrayList(1, 2, 3, 4, 5);
Iterables.removeIf(Iterables.transform(ints, intoDouble()), even());
System.out.println(ints);

[1,3,5]
Sylvain M
but I wonder why Iterables.removeIf(Lists.transform(ints, intoDouble()), even());crash...I post the question on the guava discuss group
Sylvain M
+2  A: 

Lists.transform() accepts a List and helpfully returns a result that is RandomAccess list. Iterables.transform() only accepts an Iterable, and the result is not RandomAccess. Finally, Iterables.removeIf (and as far as I see, this is the only one in Iterables) has an optimization in case that the given argument is RandomAccess, the point of which is to make the algorithm linear instead of quadratic, e.g. think what would happen if you had a big ArrayList (and not an ArrayDeque - that should be more popular) and kept removing elements from its start till its empty.

But the optimization depends not on iterator remove(), but on List.set(), which is cannot be possibly supported in a transformed list. If this were to be fixed, we would need another marker interface, to denote that "the optional set() actually works".

So the options you have are:

  • Call Iterables.removeIf() version, and run a quadratic algorithm (it won't matter if your list is small or you remove few elements)
  • Copy the List into another List that supports all optional operations, then call Iterables.removeIf().
Dimitris Andreou
Hi dimitris, clear explanation, but I don't see any Lists.removeIf()
Sylvain M
Sorry, typo, meant Iterables.removeIf().
Dimitris Andreou
In your second option, the copy won't lead to lower performance ?
Sylvain M
Yes, but it's still O(N), so, beyond a certain size of the list, this is going to be faster than the quadratic algorithm.
Dimitris Andreou
But Iterables.removeIf() is not public, why?
Ditz
It is public: http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Iterables.html
Dimitris Andreou
Sorry, I just recognized I got stuck on google-collections and missed the actual development of guava. I will switch to guava now before I post further problems with outdated google-collections ....
Ditz
A: 

I don't have a solution, instead I found some kind of a problem with Iterables.removeIf() in combination with Lists.TransformingRandomAccessList.

The transformed list implements RandomAccess, thus Iterables.removeIf() delegates to Iterables.removeIfFromRandomAccessList() which depends on an unsupported List.set() operation. Calling Iterators.removeIf() however would be successful, as the remove() operation IS supported by Lists.TransformingRandomAccessList.

see: Iterables: 147

Conclusion: instanceof RandomAccess does not guarantee List.set().

Addition: In special situations calling removeIfFromRandomAccessList() even works: if and only if the elements to erase form a compact group at the tail of the List or all elements are covered by the Predicate.

Ditz
+1  A: 

The following approach should work, though I haven't tried it yet.

Collection<Double> dblCollection =
    Collections.checkedCollection(dblList, Double.class);
Collections2.filter(dblCollection, IS_EVEN).clear();

The checkCollection() method generates a view of the list that doesn't implement List. [It would be cleaner, but more verbose, to create a ForwardingCollection instead.] Then Collections2.filter() won't call the unsupported set() method.

The library code could be made more robust. Iterables.removeIf() could generate a composed Predicate, as Michael D suggested, when passed a transformed list. However, we previously decided not to complicate the code by adding special-case logic of that sort.

Jared Levy