Sorting sets alphabetically, letters in sets separated by commas - java

public static void main(String[] args) throws IOException
{
HashSet set = new HashSet<String>();
set.add("{}");
set.add("{a}");
set.add("{b}");
set.add("{a, b}");
set.add("{a, c}");
sortedSet(set);
}
public static void sortedSet(HashSet set)
{
List<String> setList = new ArrayList<String>(set);
List<String> orderedByAlpha = new ArrayList<String>(set);
//sort by alphabetical order
orderedByAlpha = (List<String>) setList.stream()
.sorted((s1, s2) -> s1.compareToIgnoreCase(s2))
.collect(Collectors.toList());
System.out.println(orderedByAlpha);
}
I am trying to sort alphabetically but the output I get is this :
[{a, b}, {a, c}, {a}, {b}, {}]
but it should be:
[{a}, {a, b}, {a, c}, {b}, {}]

You're output doesn't match your code. You are showing 2D array lists, but your converting to a 1D arraylist, doesn't make sense.
public static void main(String[] args)
{
test(Arrays.asList("a", "d", "f", "a", "b"));
}
static void test(List<String> setList)
{
List<String> out = setList.stream().sorted((a, b) -> a.compareToIgnoreCase(b)).collect(Collectors.toList());
System.out.println(out);
}
This is properly sorting 1D arrays, so you're correct there.
You'll probably need to implement your own comparator to compare the 2D array lists to sort them.

instead of having the source as a List<String> I'd recommend you have it as a List<Set<String>> e.g.
List<Set<String>> setList = new ArrayList<>();
setList.add(new HashSet<>(Arrays.asList("a","b")));
setList.add(new HashSet<>(Arrays.asList("a","c")));
setList.add(new HashSet<>(Collections.singletonList("a")));
setList.add(new HashSet<>(Collections.singletonList("b")));
setList.add(new HashSet<>());
Then apply the following comparator along with the mapping operation to yield the expected result:
List<String> result =
setList.stream()
.sorted(Comparator.comparing((Function<Set<String>, Boolean>) Set::isEmpty)
.thenComparing(s -> String.join("", s),
String.CASE_INSENSITIVE_ORDER))
.map(Object::toString)
.collect(Collectors.toList());
and this prints:
[[a], [a, b], [a, c], [b], []]
note that, currently the result is a list of strings where each string is the string representation of a given set. if however, you want the result to be a List<Set<String>> then simply remove the map operation above.
Edit:
Managed to get a solution working based on your initial idea....
So, first, you need a completely new comparator instead of just (s1, s2) -> s1.compareToIgnoreCase(s2) as it will not suffice.
Given the input:
Set<String> set = new HashSet<>();
set.add("{}");
set.add("{a}");
set.add("{b}");
set.add("{a, b}");
set.add("{a, c}");
and the following stream pipeline:
List<String> result = set.stream()
.map(s -> s.replaceAll("[^A-Za-z]+", ""))
.sorted(Comparator.comparing(String::isEmpty)
.thenComparing(String.CASE_INSENSITIVE_ORDER))
.map(s -> Arrays.stream(s.split(""))
.collect(Collectors.joining(", ", "{", "}")))
.collect(Collectors.toList());
Then we would have a result of:
[{a}, {a, b}, {a, c}, {b}, {}]

Well, as #Aomine and #Holger noted already, you need a custom comparator.
But IMHO their solutions look over-engineered. You don't need any of costly operations like split and substring:
String.substring creates a new String object and calls System.arraycopy() under the hood
String.split is even more costly. It iterates over your string and calls String.substring multiple times. Moreover it creates an ArrayList to store all the substrings. If the number of substrings is big enough then your ArrayList will need to expand its capacity (perhaps not only once) causing another call of System.arraycopy().
For your simple case I would slightly modify the code of built-in String.compareTo method:
Comparator<String> customComparator =
(s1, s2) -> {
int len1 = s1.length();
int len2 = s2.length();
if (len1 == 2) return 1;
if (len2 == 2) return -1;
int lim = Math.min(len1, len2) - 1;
for (int k = 1; k < lim; k++) {
char c1 = s1.charAt(k);
char c2 = s2.charAt(k);
if (c1 != c2) {
return c1 - c2;
}
}
return len1 - len2;
};
It will compare the strings with complexity O(n), where n is the length of shorter string. At the same time it will neither create any new objects nor perform any array replication.
The same comparator can be implemented using Stream API:
Comparator<String> customComparatorUsingStreams =
(s1, s2) -> {
if (s1.length() == 2) return 1;
if (s2.length() == 2) return -1;
return IntStream.range(1, Math.min(s1.length(), s2.length()) - 1)
.map(i -> s1.charAt(i) - s2.charAt(i))
.filter(i -> i != 0)
.findFirst()
.orElse(0);
};
You can use your custom comparator like this:
List<String> orderedByAlpha = setList.stream()
.sorted(customComparatorUsingStreams)
.collect(Collectors.toList());
System.out.println(orderedByAlpha);

A take on it (slightly similar to the answer by Aomine) would be to strip the strings of the characters that makes String#compareTo() fail, in this case ('{' and '}'). Also, the special case that an empty string ("{}") is to be sorted after the rest needs to be taken care of.
The following code implements such a comparator:
static final Comparator<String> COMPARE_IGNORING_CURLY_BRACES_WITH_EMPTY_LAST = (s1, s2) -> {
Function<String, String> strip = string -> string.replaceAll("[{}]", "");
String strippedS1 = strip.apply(s1);
String strippedS2 = strip.apply(s2);
return strippedS1.isEmpty() || strippedS2.isEmpty() ?
strippedS2.length() - strippedS1.length() :
strippedS1.compareTo(strippedS2);
};
Of course, this is not the most efficient solution. If efficiency is truly important here, I would loop through the characters, like String#compareTo() does, as suggested by ETO.

Related

How to find out what digits are missing in an IntStream

I want to return a string consisting of those digits (in ascending order) that do not appear in any of the argument numbers. I don't know how this should be done, but I would like to use the long as a comparator to find out the missing digits in IntStream.
For example, if numeros = [1201, 23045], I must return "6789"
My code:
public static String digitosQueNoEstanEn(List<Long> numeros)
{
long n = 1234567890;
IntStream numStream = numeros.stream()
.map(c -> c.toString())
.flatMapToInt(c -> c.chars())
.distinct();
There are many solutions to this problem, one of which is a simple set removal. You essentially want to remove all used digits from the set of valid digits, and store the result as a String. That can be done with the following:
public static String digitosQueNoEstanEn(List<Long> numeros) {
Set<Integer> usedDigits = numeros.stream()
.map(String::valueOf)
.flatMapToInt(String::chars)
.map(c -> Character.digit(c, 10))
.boxed()
.collect(Collectors.toSet());
return IntStream.range(0, 10)
.filter(i -> !usedDigits.contains(i))
.sorted()
.mapToObj(Integer::toString)
.collect(Collectors.joining());
}
When invoked with your example of [1201L, 23045L], the output is what you expect:
6789
Andy Turner also mentioned that using a BitSet may be a viable solution. If you choose to use one, your solution may look like the following:
public static String digitosQueNoEstanEn(List<Long> numeros) {
BitSet bitSet = BitSet.valueOf(new long[] { 0b0011_1111_1111 });
numeros.stream()
.map(String::valueOf)
.flatMapToInt(String::chars)
.map(c -> Character.digit(c, 10))
.forEach(bitSet::clear);
return bitSet.stream()
.sorted()
.mapToObj(Integer::toString)
.collect(Collectors.joining());
}
Here is what I came up with.
public static String digitosQueNoEstanEn(List<Long> numeros) {
String digits = "0123456789";
return numeros.stream()
.flatMap(numero->Arrays.stream(Long.toString(numero).split("")))
.distinct()
.reduce(digits, (dgts,d)->dgts.contains(d) ? dgts.replace(d,"") : dgts);
}
List<Long> numeros = List.of(1201L, 23045L);
digitosQueNoEstanEn(numeros);
System.out.println(result);
6789
Basically I flatten the list of supplied numbers to digits and then just remove them in a reducing operation. What is left are the digits that were not in the original numbers.

How to find duplicate values based upon first 10 digits?

I have a scenario where i have a list as below :
List<String> a1 = new ArrayList<String>();
a1.add("1070045028000");
a1.add("1070045028001");
a1.add("1070045052000");
a1.add("1070045086000");
a1.add("1070045052001");
a1.add("1070045089000");
I tried below to find duplicate elements but it will check whole string instead of partial string(first 10 digits).
for (String s:al){
if(!unique.add(s)){
System.out.println(s);
}
}
Is there any possible way to identify all duplicates based upon the first 10 digits of a number & then find the lowest strings by comparing from the duplicates & add in to another list?
Note: Also there will be only 2 duplicates with each 10 digit string code always!!
You may group by a (String s) -> s.substring(0, 10)
Map<String, List<String>> map = list.stream()
.collect(Collectors.groupingBy(s -> s.substring(0, 10)));
map.values() would give you Collection<List<String>> where each List<String> is a list of duplicates.
{
1070045028=[1070045028000, 1070045028001],
1070045089=[1070045089000],
1070045086=[1070045086000],
1070045052=[1070045052000, 1070045052001]
}
If it's a single-element list, no duplicates were found, and you can filter these entries out.
{
1070045028=[1070045028000, 1070045028001],
1070045052=[1070045052000, 1070045052001]
}
Then the problem boils down to reducing a list of values to a single value.
[1070045028000, 1070045028001] -> 1070045028000
We know that the first 10 symbols are the same, we may ignore them while comparing.
[1070045028000, 1070045028001] -> [000, 001]
They are still raw String values, we may convert them to numbers.
[000, 001] -> [0, 1]
A natural Comparator<Integer> will give 0 as the minimum.
0
0 -> 000 -> 1070045028000
Repeat it for all the lists in map.values() and you are done.
The code would be
List<String> result = map
.values()
.stream()
.filter(list -> list.size() > 1)
.map(l -> l.stream().min(Comparator.comparingInt(s -> Integer.valueOf(s.substring(10)))).get())
.collect(Collectors.toList());
A straight-forward loop solution would be
List<String> a1 = Arrays.asList("1070045028000", "1070045028001",
"1070045052000", "1070045086000", "1070045052001", "1070045089000");
Set<String> unique = new HashSet<>();
Map<String,String> map = new HashMap<>();
for(String s: a1) {
String firstTen = s.substring(0, 10);
if(!unique.add(firstTen)) map.put(firstTen, s);
}
for(String s1: a1) {
String firstTen = s1.substring(0, 10);
map.computeIfPresent(firstTen, (k, s2) -> s1.compareTo(s2) < 0? s1: s2);
}
List<String> minDup = new ArrayList<>(map.values());
First, we add all duplicates to a Map, then we iterate over the list again and select the minimum for all values present in the map.
Alternatively, we may add all elements to a map, collecting them into lists, then select the minimum out of those, which have a size bigger than one:
List<String> minDup = new ArrayList<>();
Map<String,List<String>> map = new HashMap<>();
for(String s: a1) {
map.computeIfAbsent(s.substring(0, 10), x -> new ArrayList<>()).add(s);
}
for(List<String> list: map.values()) {
if(list.size() > 1) minDup.add(Collections.min(list));
}
This logic is directly expressible with the Stream API:
List<String> minDup = a1.stream()
.collect(Collectors.groupingBy(s -> s.substring(0, 10)))
.values().stream()
.filter(list -> list.size() > 1)
.map(Collections::min)
.collect(Collectors.toList());
Since you said that there will be only 2 duplicates per key, the overhead of collecting a List before selecting the minimum is negligible.
The solutions above assume that you only want to keep values having duplicates. Otherwise, you can use
List<String> minDup = a1.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(s -> s.substring(0, 10), Function.identity(),
BinaryOperator.minBy(Comparator.<String>naturalOrder())),
m -> new ArrayList<>(m.values())));
which is equivalent to
Map<String,String> map = new HashMap<>();
for(String s: a1) {
map.merge(s.substring(0, 10), s, BinaryOperator.minBy(Comparator.naturalOrder()));
}
List<String> minDup = new ArrayList<>(map.values());
Common to those solutions is that you don’t have to identify duplicates first, as when you want to keep unique values too, the task reduces to selecting the minimum when encountering a minimum.
While I hate doing your homework for you, this was fun. :/
public static void main(String[] args) {
List<String> al=new ArrayList<>();
al.add("1070045028000");
al.add("1070045028001");
al.add("1070045052000");
al.add("1070045086000");
al.add("1070045052001");
al.add("1070045089000");
List<String> ret=new ArrayList<>();
for(String a:al) {
boolean handled = false;
for(int i=0;i<ret.size();i++){
String ri = ret.get(i);
if(ri.substring(0, 10).equals(a.substring(0,10))) {
Long iri = Long.parseLong(ri);
Long ia = Long.parseLong(a);
if(ia < iri){
//a is smaller, so replace it in the list
ret.set(i, a);
}
//it was a duplicate, we are done with it
handled = true;
break;
}
}
if(!handled) {
//wasn't a duplicate, just add it
ret.add(a);
}
}
System.out.println(ret);
}
prints
[1070045028000, 1070045052000, 1070045086000, 1070045089000]
Here's another way to do it – construct a Set and store just the 10-digit prefix:
Set<String> set = new HashSet<>();
for (String number : a1) {
String prefix = number.substring(0, 10);
if (set.contains(prefix)) {
System.out.println("found duplicate prefix [" + prefix + "], skipping " + number);
} else {
set.add(prefix);
}
}

Show all the longest words from finite Stream

I have to find all longest words from given file using Streams API. I did it in few steps but looking for some "one liner", actually I processing whole file two times, first to find max length of word and second for comparing all to max length, assuming its not the best for performance ; P Could someone help me? Just look at code:
public class Test {
public static void main(String[] args) throws IOException {
List<String> words = Files.readAllLines(Paths.get("alice.txt"));
OptionalInt longestWordLength = words.stream().mapToInt(String::length).max();
Map<Integer, List<String>> groupedByLength = words.stream().collect(Collectors.groupingBy(String::length));
List<String> result = groupedByLength.get(longestWordLength.getAsInt());
}
}
I wish to make it straight:
List<String> words = Files.readAllLines(Paths.get("alice.txt"));
List<String> result = // code
File contains just one word per line, anyway it's not important - question is about the right stream code.
Instead of just keeping the largest length, you could collect the words into a map from their length to the words, and then just take the longest one:
List<String> longestWords =
Files.lines(Paths.get("alice.txt"))
.collect(Collectors.groupingBy(String::length))
.entrySet()
.stream()
.sorted(Map.Entry.<Integer, List<String>> comparingByKey().reversed())
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
EDIT:
As Malte Hartwig noted, using max on the streamed map is much more elegant (and probably faster):
List<String> longestWords =
Files.lines(Paths.get("alice.txt"))
.collect(Collectors.groupingBy(String::length))
.entrySet()
.stream()
.max(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.orElse(null);
EDIT2:
There's a built-in inefficiency in both the above solutions, as they both build a map a essentially store the lengths for all the strings in the file instead of just the longest ones. If performance is more important than elegance in your usecase, you could write your own Collector to just preserve the longest strings in list:
private static int stringInListLength(List<String> list) {
return list.stream().map(String::length).findFirst().orElse(0);
}
List<String> longestWords =
Files.lines(Paths.get("alice.txt"))
.collect(Collector.of(
LinkedList::new,
(List<String> list, String string) -> {
int stringLen = string.length();
int listStringLen = stringInListLength(list);
if (stringLen > listStringLen) {
list.clear();
}
if (stringLen >= listStringLen) {
list.add(string);
}
},
(list1, list2) -> {
int list1StringLen = stringInListLength(list1);
int list2StringLen = stringInListLength(list2);
if (list1StringLen > list2StringLen) {
return list1;
}
if (list2StringLen > list1StringLen) {
return list2;
}
list1.addAll(list2);
return list1;
}
));
reduce will help you:
Optional<String> longest = words.stream()
.reduce((s1, s2) -> {
if (s1.length() > s2.length())
return s1;
else
return s2;
});
In case the Stream is empty it will return an Optional.empty
In case you want the list of all the words that have the maximum length this piece will help you:
Optional<List<String>> longest = words.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.toList()
))
.entrySet()
.stream()
.reduce(
(entry1, entry2) -> {
if (entry1.getKey() > entry2.getKey())
return entry1;
else
return entry2;
}
)
.map(Map.Entry::getValue);
iterate over the map keys to find the longest wordlength

Adding non-duplicated elements to existing keys in java 8 functional style

I have a map I want to populate:
private Map<String, Set<String>> myMap = new HashMap<>();
with this method:
private void compute(String key, String[] parts) {
myMap.computeIfAbsent(key, k -> getMessage(parts));
}
compute() is invoked as follows:
for (String line : messages) {
String[] parts = line.split("-");
validator.validate(parts); //validates parts are as expected
String key = parts[parts.length - 1];
compute(key, parts);
}
parts elements are like this:
[AB, CC, 123]
[AB, FF, 123]
[AB, 456]
In the compute() method, as you can see I am trying to use the last part of the element of the array as a key and the other parts to be used as values for the map I am looking to build.
My Question: How do I add to existing key only the unique values using Java 8 functional style e.g.
{123=[AB, FF, CC]}
As you requested I added a lambda variant, which just adds the parts via lambda to the map in the compute-method:
private void compute(String key, String[] parts) {
myMap.computeIfAbsent(key,
s -> Stream.of(parts)
.limit(parts.length - 1)
.collect(toSet()));
}
But in this case you will only get something like 123=[AB, CC] in your map. Use merge instead, if you want to add also all values which come on subsequent calls:
private void compute(String key, String[] parts) {
myMap.merge(key,
s -> Stream.of(parts)
.limit(parts.length - 1)
.collect(toSet()),
(currentSet, newSet) -> {currentSet.addAll(newSet); return currentSet;});
}
I am not sure what you intend with computeIfAbsent, but from what you listed as parts and what you expect as output, you may also want to try the following instead of the whole code you listed :
// the function to identify your key
Function<String[], String> keyFunction = strings -> strings[strings.length - 1];
// the function to identify your values
Function<String[], List<String>> valuesFunction = strings -> Arrays.asList(strings).subList(0, strings.length - 1);
// a collector to add all entries of a collection to a (sorted) TreeSet
Collector<List<String>, TreeSet<Object>, TreeSet<Object>> listTreeSetCollector = Collector.of(TreeSet::new, TreeSet::addAll, (left, right) -> {
left.addAll(right);
return left;
});
Map myMap = Arrays.stream(messages) // or: messages.stream()
.map(s -> s.split("-"))
.peek(validator::validate)
.collect(Collectors.groupingBy(keyFunction,
Collectors.mapping(valuesFunction, listTreeSetCollector)));
Using your samples as input you get the result you mentioned (well, actually sorted, as I used a TreeSet).
String[] messages = new String[]{
"AB-CC-123",
"AB-FF-123",
"AB-456"};
produces a map containing:
123=[AB, CC, FF]
456=[AB]
Last, but not least: if you can, pass the key and the values themselves to your method. Don't split the logic about identifying the key and identifying the values. That makes it really hard to understand your code later on or by someone else.
Try this:
private void compute(String[] parts) {
int lastIndex = parts.length - 1;
String key = parts[lastIndex];
List<String> values = Arrays.asList(parts).subList(0, lastIndex);
myMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(values);
}
Or if you want, you can replace the entire loop with a stream:
Map<String, Set<String>> myMap = messages.stream() // if messages is an array, use Arrays.stream(messages)
.map(line -> line.split("-"))
.peek(validator::validate)
.collect(Collectors.toMap(
parts -> parts[parts.length - 1],
parts -> new HashSet<>(Arrays.asList(parts).subList(0, parts.length - 1)),
(a, b) -> { a.addAll(b); return a; }));
To add more parts to a possibly existing key you're using the wrong method; you want merge(), not computeIfAbsent().
If validator.valudate() throws a checked Exception, you must call it outside a stream, so you'll need a foreach loop:
for (String message : messages) {
String[] parts = message.split("-");
validator.validate(parts);
LinkedList<String> list = new LinkedList(Arrays.asList(parts));
String key = list.getLast();
list.removeLast();
myMap.merge(key, new HashSet<>(list), Set::addAll);
}
Using a LinkedList, which has methods getLast() and removeLast(), makes the code very readable.
Disclaimer: Code may not compile or work as it was thumbed in on my phone (but there's a reasonable chance it will work)

Find missing integer in a sequential sorted stream

Let's say I have a list
ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
How do I find "N4", I mean, how I find that the missing integer is 4?
What I've tried so far
Integer missingID = arr.stream().map(p -> Integer.parseInt(p.substring(1))).sorted()
.reduce((p1, p2) -> (p2 - p1) > 1 ? p1 + 1 : 0).get();
This doesn't work because reduce is not intended to work in the way I need in this situation, actually, I have no idea how do that.
If there's no missing number, than the next must be "N6" - or just 6 - (in this example)
It must be done with java standard stream's library, no use of third parties.
The algorithm to implement here is based from this one: to find the missing number in a sequence of integers, the trick is to:
calculate the sum of the elements in the sequence.
calculate the sum of the elements the sequence would have with the missing number: this is easy to do since we can determine the minimum, the maximum and we know that the sum from a sequence of integer going from min to max is max*(max+1)/2 - (min-1)*min/2.
find the difference between those two sums: that's our missing number
In this case, we can collect statistics on our Stream by first mapping to an IntStream formed by only the numbers themselves and then calling summaryStatistics(). This returns a IntSummaryStatistics that has all the values we want: min, max and sum:
public static void main(String[] args) {
List<String> arr = Arrays.asList("N3", "N7", "N4", "N5", "N2");
IntSummaryStatistics statistics =
arr.stream()
.mapToInt(s -> Integer.parseInt(s.substring(1)))
.summaryStatistics();
long max = statistics.getMax();
long min = statistics.getMin();
long missing = max*(max+1)/2 - (min-1)*min/2 - statistics.getSum();
System.out.println(missing); // prints "6" here
}
If there is no missing number, this will print 0.
Here's the solution involving the pairMap operation from my free StreamEx library. It prints all the missing elements of the sorted input:
ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
StreamEx.of(arr).map(n -> Integer.parseInt(n.substring(1)))
.pairMap((a, b) -> IntStream.range(a+1, b))
.flatMapToInt(Function.identity())
.forEach(System.out::println);
The pairMap operation allows you to map every adjacent pair of the stream to something else. Here we map them to the streams of the skipped numbers, then flatten these streams.
The same solution is possible without third-party library, but looks more verbose:
ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
IntStream.range(0, arr.size()-1)
.flatMap(idx -> IntStream.range(
Integer.parseInt(arr.get(idx).substring(1))+1,
Integer.parseInt(arr.get(idx+1).substring(1))))
.forEach(System.out::println);
If there's only ONE missing number in the array, and if all numbers are positive, you could use the XOR algorithm, as explained in this question and its answers:
List<String> list = Arrays.asList("N5", "N2", "N3", "N6");
int xorArray = list.stream()
.mapToInt(p -> Integer.parseInt(p.substring(1)))
.reduce(0, (p1, p2) -> p1 ^ p2);
int xorAll = IntStream.rangeClosed(2, 6)
.reduce(0, (p1, p2) -> p1 ^ p2);
System.out.println(xorArray ^ xorAll); // 4
The advantage of this approach is that you don't need to use extra data structures, all you need is a couple of ints.
EDIT as per #Holger's comments below:
This solution requires you to know the range of the numbers in advance. Although on the other hand, it doesn't require the list and stream to be sorted.
Even if the list wasn't sorted, you could still get min and max (hence, the range) with IntSummaryStatistics, but this would require an extra iteration.
You could create a state object which is used to transform a single input stream into multiple streams of missing entries. These missing entry streams can then be flat mapped to produce a single output:
public class GapCheck {
private String last;
public GapCheck(String first) {
last = first;
}
public Stream<String> streamMissing(String next) {
final int n = Integer.parseInt(next.replaceAll("N", ""));
final int l = Integer.parseInt(last.replaceAll("N", ""));
last = next;
return IntStream.range(l + 1, n).mapToObj(Integer::toString);
}
}
Usage:
final List<String> arr = new ArrayList(Arrays.asList("N1", "N3", "N5"));
arr.stream()
.flatMap(new GapCheck(arr.get(0))::streamMissing)
.forEach(System.out::println);
output:
2
4
This is more work than you might expect, but it can be done with a collect call.
public class Main {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<String>(Arrays.asList("N1", "N2", "N3", "N5", "N7", "N14"));
Stream<Integer> st = arr.stream().map(p -> Integer.parseInt(p.substring(1))).sorted();
Holder<Integer> holder = st.collect(() -> new Holder<Integer>(),
(h, i) -> {
Integer last = h.getProcessed().isEmpty() ? null : h.getProcessed().get(h.getProcessed().size() - 1);
if (last != null) {
while (i - last > 1) {
h.getMissing().add(++last);
}
}
h.getProcessed().add(i);
},
(h, h2) -> {});
holder.getMissing().forEach(System.out::println);
}
private static class Holder<T> {
private ArrayList<T> processed;
private ArrayList<T> missing;
public Holder() {
this.processed = new ArrayList<>();
this.missing = new ArrayList<>();
}
public ArrayList<T> getProcessed() {
return this.processed;
}
public ArrayList<T> getMissing() {
return this.missing;
}
}
}
This prints
4
6
8
9
10
11
12
13
Note that this sort of thing isn't really a particularly strong fit for Streams. All of the stream processing methods will tend to pass you each item exactly one time, so you need to handle all runs of missing numbers at once, and in the end, you're writing kind of a lot of code to avoid just writing a loop.
Here is one solution using pure streams, albeit not very efficient.
public void test() {
List<String> arr = new ArrayList(
Arrays.asList("N1", "N2", "N3", "N5", "N7"));
List<Integer> list = IntStream
.range(1, arr.size())
.mapToObj(t -> new AbstractMap.SimpleEntry<Integer, Integer>(
extract(arr, t), extract(arr, t) - extract(arr, t - 1)))
.filter(t -> t.getValue() > 1)
.map(t -> t.getKey() - 1)
.collect(Collectors.toList());
System.out.println(list);
}
private int extract(List<String> arr, int t) {
return Integer.parseInt(arr.get(t).substring(1));
}
Major performance block will be because of repeated parsing of list elements. However, this solution will be able to provide all missing numbers.

Categories