I'm trying to achieve this using only functional programming constructs (Streams, Collectors, lambda expressions).
Let's say list
is a String[]
:
{"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"}
I want to print out a distinct list of brand names from this array, and number them, i.e.:
1. Apple
2. Huawei
3. LG
4. Oppo
5. Samsung
I can print out the unique elements (sorted):
Stream.of(list)
.distinct()
.sorted()
.forEach(System.out::println);
But this does not show the preceding counter. I tried Collectors.counting()
but that, of course, didn't work.
Any help from FP experts?
Edit: I understand some other questions have asked about iterating over a stream with indices. However, I can't simply do:
IntStream.range(0, list.length)
.mapToObj(a -> (a+1) + ". " + list[a])
.collect(Collectors.toList())
.forEach(System.out::println);
because the original array contains duplicate elements. There might also be other map
and filter
operations I may need to perform on the stream before I print the result.
Edit after Holger’s comment: If you just want the distinct brands in the order encountered in the array, all you need to do is to omit the sort operation.
String[] list = {"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"};
Stream.of(list)
.distinct()
.forEach(System.out::println);
Output is:
Apple Samsung LG Oppo Huawei
As Holger said, distinct
maintains encounter order if the stream had encounter order before the distinct
operation (which your stream has).
I actually need the preceding counter (1. 2. 3. 4. 5.).
My preferred way of doing it is using an enhanced for
loop and no lambdas:
int counter = 0;
for (String brand : new LinkedHashSet<>(Arrays.asList(list))) {
counter++;
System.out.println("" + counter + ". " + brand);
}
Output is:
1. Apple 2. Samsung 3. LG 4. Oppo 5. Huawei
Further comment from you:
I'm wondering what's the "ideal FP" solution.
I’m not necessarily convinced that any good purely functional programming solution exists. My attempt would be, inspired from the linked original question and its answers:
List<String> distinctBrands
= new ArrayList<>(new LinkedHashSet<>(Arrays.asList(list)));
IntStream.range(0, distinctBrands.size())
.mapToObj(index -> "" + (index + 1) + ". " + distinctBrands.get(index))
.forEach(System.out::println);
A LinkedHashSet
maintains insertion order and drops duplicates. Output is the same as before.
A simpler solution could be to use a Set
as a collection to ensure unique strings are accessed and incrementing the index along with for all such elements:
String[] list = {"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"};
Set<String> setOfStrings = new HashSet<>(); // final strings
AtomicInteger index = new AtomicInteger(1); // index
Arrays.stream(list)
.filter(setOfStrings::add) // use the return type of Set.add
.forEach(str -> System.out.println(index.getAndIncrement() + ". " + str));
Edit: (Thanks to Holger) And instead of the index maintained as a separate variable, one can use the size of the collection as:
Arrays.stream(list)
.filter(setOfStrings::add) // use the return type of Set.add
.forEach(str -> System.out.println(setOfStrings.size() + ". " + str)); // use size for index
You can do this in a functional style, without side-effects, by setting up one stream that sorts the items and a second stream for the line numbers, then combining the streams with zip.
This example uses guava's zip function. Zip is a very common utility for functional programming.
import java.util.stream.Stream;
import com.google.common.collect.Streams;
public class ZipExample {
public static void main(String[] args) {
String[] a = {"Apple", "Samsung", "LG", "Oppo", "Apple", "Huawei", "Oppo"};
Stream<String> items = Stream.of(a).sorted().distinct();
Stream<Integer> indices = Stream.iterate(1, i -> i + 1);
Streams.zip(items, indices,
(item, index) -> index + ". " + item)
.forEach(System.out::println);
}
}
It prints out
1. Apple
2. Huawei
3. LG
4. Oppo
5. Samsung
Rewritten as one expression it would look like:
Streams.zip(
Stream.of(a).sorted().distinct(),
Stream.iterate(1, i -> i + 1),
(item, index) -> index + ". " + item)
.forEach(System.out::println);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With