views:

232

answers:

7

I have this code:

public static String SelectRandomFromTemplate(String template,int count) {
        String[] split = template.split("|");
        List<String> list=Arrays.asList(split);
        Random r = new Random();
        while (list.size()>count) {
            list.remove(r.nextInt(list.size()));
        }
        return StringUtils.join(list, ", ");
    }

I get this:

06-03 15:05:29.614: ERROR/AndroidRuntime(7737): java.lang.UnsupportedOperationException
06-03 15:05:29.614: ERROR/AndroidRuntime(7737):     at java.util.AbstractList.remove(AbstractList.java:645)

How would be this the correct way? Java.15

+2  A: 

Probably because you're working with unmodifiable wrapper.

Change this line:

List<String> list=Arrays.asList(split);

to this line:

List<String> list = new LinkedList (Arrays.asList(split));
Roman
Arrays.asList() is not an unmodifiable wrapper.
Dimitris Andreou
@Dimitris: correct; it's modifiable, just not structurally.
polygenelubricants
@polygenelubricants: it seems you mix up `unmodifiable` and `immutable`. `unmodifiable` means exactly "modifiable, but not structurally".
Roman
@Roman: I just tried creating an `unmodifiableList` wrapper and trying a `set`; it throws `UnsupportedOperationException`. I'm quite certain `Collections.unmodifiable*` really means full immutability, not just structural.
polygenelubricants
+1  A: 

This one has burned me many times. Arrays.asList creates an unmodifiable list. From the Javadoc: Returns a fixed-size list backed by the specified array.

Wrap it in a modifiable list:

newList.addAll(Arrays.asList(newArray));

This will create a little extra garbage, but you will me able to mutate it.

Nick Orton
Minor point, but you aren't "wrapping" the original list, you are creating a completely new list (which is why it works).
Jack Leow
That's true. Apologies to those who may have been misled.
Nick Orton
A: 

the list returned by Arrays.asList might be immutable. could you try

List<String> list=new ArrayList(Arrays.asList(split));
Pierre
he's deleting, ArrayList is not the best data structure for deleting its values. LinkedList feets much more for his problem.
Roman
Wrong regarding the LinkedList. He is accessing by index, so LinkedList would spend as much time to find an element through iteration. See my answer for a better approach, using an ArrayList.
Dimitris Andreou
+3  A: 

Just read the JavaDoc for the asList method:

Returns a {@code List} of the objects in the specified array. The size of the {@code List} cannot be modified, i.e. adding and removing are unsupported, but the elements can be set. Setting an element modifies the underlying array.

This is from Java 6 but it looks like it is the same for the android java.

EDIT

The type of the resulting list is Arrays.ArrayList, which is a private class inside Arrays.class. Practically speaking, it is nothing but a List-view on the array that you've passed with Arrays.asList. With a consequence: if you change the array, the list is changed too. And because an array is not resizeable, remove and add operation must be unsupported.

Andreas_D
+2  A: 

Arrays.asList() returns a list that doesn't allow operations affecting its size (note that this is not the same as "unmodifiable").

You could do new ArrayList<String>(Arrays.asList(split)); to create a real copy, but seeing what you are trying to do, here is an additional suggestion (you have a O(n^2) algorithm right below that).

You want to remove list.size() - count (lets call this k) random elements from the list. Just pick as many random elements and swap them to the end k positions of the list, then delete that whole range (e.g. using subList() and clear() on that). That would turn it to a lean and mean O(n) algorithm (O(k) is more precise).

Update: As noted below, this algorithm only makes sense if the elements are unordered, e.g. if the List represents a Bag. If, on the other hand, the List has a meaningful order, this algorithm would not preserve it (polygenelubricants' algorithm instead would).

Update 2: So in retrospect, a better (linear, maintaining order, but with O(n) random numbers) algorithm would be something like this:

LinkedList<String> elements = ...; //to avoid the slow ArrayList.remove()
int k = elements.size() - count; //elements to select/delete
int remaining = elements.size(); //elements remaining to be iterated
for (Iterator i = elements.iterator(); k > 0 && i.hasNext(); remaining--) {
  i.next();
  if (random.nextInt(remaining) < k) {
     //or (random.nextDouble() < (double)k/remaining)
     i.remove();
     k--;
  }
}
Dimitris Andreou
+1 for the algorithm, though OP says that there's only 10 elements. And nice way of using the random numbers with `ArrayList`. Much simpler than my suggestion. I think it would result in reordering of the elements though.
polygenelubricants
Yes, you are right, I'd better update this, thanks!
Dimitris Andreou
+8  A: 

Quite a few problems with your code:

On Arrays.asList returning a fixed-size list

From the API:

Arrays.asList: Returns a fixed-size list backed by the specified array.

You can't add to it; you can't remove from it. You can't structurally modify the List.

Fix

Create a LinkedList, which supports faster remove.

List<String> list = new LinkedList<String>(Arrays.asList(split));

On split taking regex

From the API:

String.split(String regex): Splits this string around matches of the given regular expression.

| is a regex metacharacter; if you want to split on a literal |, you must escape it to \|, which as a Java string literal is "\\|".

Fix:

template.split("\\|")

On better algorithm

Instead of calling remove one at a time with random indices, it's better to generate enough random numbers in the range, and then traversing the List once with a listIterator(), calling remove() at appropriate indices. There are questions on stackoverflow on how to generate random but distinct numbers in a given range.

With this, your algorithm would be O(N).

polygenelubricants
Thanks, I have only limited elements in the string <10 so won't be an optimization problem.
Pentium10
@Pentium: one more thing: you shouldn't be creating a new instance of `Random` everytime. Make it a `static` field and seed it only once.
polygenelubricants
A: 

This UnsupportedOperationException comes when you try to perform some operation on collection where its not allowed and in your case, When you call Arrays.asList it does not return a java.util.ArrayList. It returns a java.util.Arrays$ArrayList which is an immutable list. You cannot add to it and you cannot remove from it.

Mayank Gupta