Sean
Sean

Reputation: 159

Best way to replace an element of a Collection with multiple elements in Java

I have a Collection of String values. If one of the values is an '*', I'd like to replace that value with 3 others, let's say "X", "Y", and "Z".

In other words, I'd like [ "A", "B", "*", "C"] to turn into ["A","B","X","Y","Z","C"]. Order does not matter, so it is simply get rid of one and add the others. These are the ways I can think of for doing it, using that example:

Collection<String> additionalValues = Arrays.asList("X","Y","Z"); // or set or whatever
if (attributes.contains("*")) {
    attributes.remove("*");
    attributes.addAll(additionalValues);
}

or

attributes.stream()
          .flatMap(val -> "*".equals(val) ? additionalValues.stream() : Stream.of(val))
          .collect(Collectors.toList());

What's the most efficient way of doing this? Again, order doesn't matter, and ideally I'd like to remove duplicates (so maybe distinct() on stream, or HashSet?).

Upvotes: 0

Views: 2190

Answers (3)

Andreas
Andreas

Reputation: 159096

For best performance, and to insert replacement values in the correct position, use indexOf(o), remove(index), and addAll(index, c):

Demo

List<String> attributes = new ArrayList<>(Arrays.asList("A", "B", "*", "C"));
Collection<String> additionalValues = Arrays.asList("X","Y","Z");

int idx = attributes.indexOf("*");
if (idx != -1) {
    attributes.remove(idx);
    attributes.addAll(idx, additionalValues);
}

System.out.println(attributes);

Output

[A, B, X, Y, Z, C]

If order doesn't matter, use the return value of remove(o):

if (attributes.remove("*"))
    attributes.addAll(additionalValues);

Output

[A, B, C, X, Y, Z]

Upvotes: 0

Andy Turner
Andy Turner

Reputation: 140326

I'd do it very similarly to your first way:

if (attributes.remove("*")) {
    attributes.addAll(additionalValues);
}

You don't need a separate remove and contains call for a correctly-implemented collection:

[Collection.remove(Object)] Removes a single instance of the specified element from this collection, if it is present (optional operation). More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if this collection contains one or more such elements. Returns true if this collection contained the specified element (or equivalently, if this collection changed as a result of the call).

Upvotes: 2

Oleg Cherednik
Oleg Cherednik

Reputation: 18245

I think that second one is better. Arrays.asList("X","Y","Z") retrieve ArrayList i.e. an array. It is not good for replace values in it.

In general case, if you want to modify a colletion (e.g. replace * with X,Y and Z), the do create new collection in some way.

Do look at LinkedList if you want to modify collection itself.

Using Streams:

public static List<String> replace(Collection<String> attributes, String value, Collection<String> additionalValues) {
    return attributes.stream()
                     .map(val -> value.equals(val) ? additionalValues.stream() : Stream.of(val))
                     .flatMap(Function.identity())
                     .collect(Collectors.toList());
}

Not using Streams

public static List<String> replace(Collection<String> attributes, String value, Collection<String> additionalValues) {
    List<String> res = new LinkedList<>();

    for (String attribute : attributes) {
        if (value.equals(attribute))
            res.addAll(additionalValues);
        else
            res.add(attribute);
    }

    return res;
}

Demo:

List<String> attributes = Arrays.asList("A", "B", "*", "C");
List<String> res = replace(attributes, "*", Arrays.asList("X", "Y", "Z"));  // ["A", "B", "X", "Y", "Z", "C"]

Upvotes: 1

Related Questions