0

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?).

Sean
  • 139
  • 1
  • 1
  • 15
  • 1
    Do you have good reason to believe efficiency is an issue? – shmosel Jul 13 '18 at 21:44
  • Not really. Just want to learn best practices. – Sean Jul 13 '18 at 21:47
  • that would always add the attributes, even if there wasn't a '*'. I only want it to add the values if the * exists – Sean Jul 13 '18 at 21:49
  • 2
    Best practice is to focus on readability and maintainability. If you have reason to believe there's a performance concern, do some [benchmarking](https://stackoverflow.com/q/504103/1553851). – shmosel Jul 13 '18 at 21:49
  • @shmosel you have right that readability and maintainability are high priority, but since java9 and jigsaw project we can put java on smaller devices than PC, where performance is a priority not readability – Thomas Banderas Jul 13 '18 at 21:57
  • 1
    @ThomasBanderas Well sure, that's true since way before Java 9. But that would be a reason to believe performance is a concern. – shmosel Jul 13 '18 at 21:58

3 Answers3

2

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).

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
0

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"]
Oleg Cherednik
  • 17,377
  • 4
  • 21
  • 35
  • additionalValues can be any kind of collection, it's just how I happened to define the list up there. In the actual code, it's a collection coming through the method, so it has already been defined elsewhere – Sean Jul 13 '18 at 21:48
  • Ah I see what you're saying for creating a new list now. Ok, thanks! – Sean Jul 13 '18 at 22:11
0

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]
Andreas
  • 154,647
  • 11
  • 152
  • 247