How to iterate over every n elements with java? - java

I have a method named calculate and it takes too long to complete. So I decided to send my info list objects to this method partially. How can I iterate over every n elements?
public static void main(String [] args){
Map<String, Long> info....; //my info Map
//I want to call method like
for(int i = 0; i<info.size(); i+=5)
calculate(info.submap(i,i+5));
}
public static boolean calculate(Map<String, Long> info){
//Some calculations
}

You can use following code
class SomeClass {
private final int BUFFER_SIZE = 5;
public static void main(String[] args) {
Map<String, Long> info = new HashMap<>();
LongStream.range(0, 30).boxed().forEach(i -> info.put("key" + i, i)); // for test
IntStream.range(0, info.size() / BUFFER_SIZE)
.boxed()
.parallel()
.map(i -> Arrays.copyOfRange(info.keySet().toArray(), BUFFER_SIZE * i, BUFFER_SIZE * (i + 1)))
.map(Arrays::asList)
.map(keys -> info.entrySet().stream()
.filter(x -> keys.contains(x.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
.forEach(SomeClass::calculate);
}
public static boolean calculate(Map<String, Long> info) {
System.out.println("calculation for " + info.toString());
return true;
}
}

It sounds like what you want to do is to implement a sort of batch processing for the data represented by your Map<String, Long> info instance. You can then create a generator for these batches as a Stream: This is in a way the inverse of the Stream.flatMap(...) family of methods, but, ironically, there doesn't seem to be any idiomatic functional way of doing this and so you may have to create the batches yourself in an imperative manner — for example:
private static <T> Stream<Stream<T>> createBatchStreams(final Iterator<T> iter, final int maxBatchSize) {
final Stream.Builder<Stream<T>> resultBuilder = Stream.builder();
{
// NOTE: This logic could also be encapsulated in a Collector class
// in order to make it less imperative style
Stream.Builder<T> currentBatchBuilder = Stream.builder();
int currentBatchSize = 0;
while (iter.hasNext()) {
final T next = iter.next();
if (currentBatchSize == maxBatchSize) {
resultBuilder.add(currentBatchBuilder.build());
// Start building a new batch
currentBatchBuilder = Stream.builder();
currentBatchSize = 0;
}
currentBatchBuilder.add(next);
currentBatchSize++;
}
// Check if there is a non-empty Stream to add (e.g. if there was a
// final batch which was smaller than the others)
if (currentBatchSize > 0) {
resultBuilder.add(currentBatchBuilder.build());
}
}
return resultBuilder.build();
}
Using this method, you can then create a generator of batches of your map data, which can then be fed to your calculate(...) function (albeit with a slightly different signature):
public static void main(final String[] args) {
final Map<String, Long> info = LongStream.range(0, 10).boxed()
.collect(Collectors.toMap(value -> "key" + value, Function.identity())); // Test data
final Stream<Stream<Entry<String, Long>>> batches = createBatchStreams(info.entrySet().iterator(), 5);
batches.forEach(batch -> {
calculate(batch);
// Do some other stuff after processing each batch
});
}
private static boolean calculate(final Stream<Entry<String, Long>> info) {
// Some calculations
}

Related

Java stream - find most frequent element based on a specific field

I have a list of Person objects, I would like to find the most frequent name in the list, and the frequency, only using java streams. (When there is a tie, return any result)
Currently, my solution uses groupingBy and counting, then again finding the max element in the resulting map.
The current solution makes 2 passes on the input (list/map).
Is it possible to make this a bit more efficient and readable?
Person p1 = Person.builder().id("p1").name("Alice").age(1).build();
Person p2 = Person.builder().id("p2").name("Bob").age(2).build();
Person p3 = Person.builder().id("p3").name("Charlie").age(3).build();
Person p4 = Person.builder().id("p4").name("Alice").age(4).build();
List<Person> people = ImmutableList.of(p1, p2, p3, p4);
Map.Entry<String, Long> mostCommonName = people
.stream()
.collect(collectingAndThen(groupingBy(Person::getName, counting()),
map -> map.entrySet().stream().max(Map.Entry.comparingByValue()).orElse(null)
));
System.out.println(mostCommonName); // Alice=2
If you are insisting on only using streams then your best bet is likely to have a custom collector that includes the info required to aggregate in a single pass:
class MaxNameFinder implements Collector<Person, ?, String> {
public class Accumulator {
private final Map<String,Integer> nameFrequency = new HashMap<>();
private int modeFrequency = 0;
private String modeName = null;
public String getModeName() {
return modeName;
}
public void accept(Person person) {
currentFrequency = frequency.merge(p.getName(), 1, Integer::sum);
if (currentFrequency > modeFrequency) {
modeName = person.getName();
modeFrequency = currentFrequency;
}
}
public Accumulator combine(Accumulator other) {
other.frequency.forEach((n, f) -> this.frequency.merge(n, f, Integer::sum));
if (this.frequency.get(other.modeName) > frequency.get(this.modeName))
modeName = other.modeName;
modeFrequency = frequency.get(modeName);
return this;
};
}
public BiConsumer<Accumulator,​Person> accumulator() {
return Accumulator::accept;
}
public Set<Collector.Characteristics> characteristics() {
return Set.of(Collector.Characteristics.CONCURRENT);
}
public BinaryOperator<Accumulator> combiner() {
return Accumulator::combine;
}
public Function<Accumulator,String> finisher() {
return Accumulator::getModeName;
}
public Supplier<Accumulator> supplier() {
return Accumulator::new;
}
}
Usage would be:
people.stream().collect(new MaxNameFinder())
which would return a string representing the most common name.
It may be possible to squeeze two passes into one using loops and Map::merge function returning the calculated frequency value immediately:
String mostCommonName = null;
int maxFreq = 0;
Map<String, Integer> freq = new HashMap<>();
for (Person p : people) {
if (freq.merge(p.getName(), 1, Integer::sum) > maxFreq) {
maxFreq = freq.get(p.getName());
mostCommonName = p.getName();
}
}
System.out.printf("Most common name '%s' occurred %d times.%n", mostCommonName, maxFreq);

Average for group no within group - stream

How to compute an average value for group using stream. Below code which I would like to transform to stream solution.
public static void main(String[] args) {
List<Item> items = Arrays.asList(
new Item("A", 1.0),
new Item("A", 1.0),
new Item("B", 1.0)
);
System.out.println(averageForGroup(items));
}
public static double averageForGroup(List<Item> items) {
Set<String> uniqueGroups = new HashSet<>();
double sum = 0;
for (Item i : items) {
String groupName = i.getGroupName();
if (!uniqueGroups.contains(groupName)) {
uniqueGroups.add(groupName);
}
sum += i.getValue();
}
return sum / uniqueGroups.size();
}
Item class:
public class Item {
private String groupName;
private Double value;
// Full-args constructor
// Getters and setters
}
I tried something like this:
public static double averageForGroup2(List<Item> items) {
return items.stream()
.collect(Collectors.groupingBy(
Item::getGroupName,
Collectors.averagingDouble(Item::getValue)) )
.entrySet().stream()
.mapToDouble(entry -> entry.getValue())
.sum();
}
But method sums up averages, so not what I expect. If it was possible to revert summing with grouping it may return excepted result.
double result = items.stream()
.collect(
Collectors.collectingAndThen(
Collectors.groupingBy(
Item::getGroupName,
Collectors.summingDouble(Item::getValue)),
map -> map.values().stream().mapToDouble(Double::doubleValue).sum() / map.size()));
To make it more readable, you can do it in two operations:
long distinct = items.stream().map(Item::getGroupName).distinct().count();
double sums = items.stream().mapToDouble(Item::getValue).sum();
System.out.println(sums / distinct);
You can do it in a single pass, but requires a custom collector...
You want something like:
Map<String, Double> map = items.stream() // Stream
.collect(Collectors.groupingBy( // Group to map
Item::getGroupName, // Key is the groupName
Collectors.averagingDouble(Item::getValue))); // Value is the average of values
To get result average of a particular group, get the value from the Map:
double averageForA = map.get("A");
Another way would be using collect(supplier, accumulator, combiner). Based on example from official tutorial (see class Averager) you can write your own class which will let you
collect current sum and unique names
handle each Item element to update sum and set of unique names
combine other instances of that class in case of parallel processing.
So such class can look like
class ItemAverager {
Set<String> set = new HashSet();
double sum = 0;
ItemAverager add(Item item) {
set.add(item.getGroupName());
sum += item.getValue();
return this;
}
ItemAverager combine(ItemAverager ia) {
set.addAll(ia.set);
sum += ia.sum;
return this;
}
double average() {
if (set.size() > 0)
return sum / set.size();
else
return 0; //or throw exception
}
}
and can be used like
List<Item> items = Arrays.asList(
new Item("A", 1.0),
new Item("A", 3.0),
new Item("B", 1.0)
);
double avrg = items
.stream()
.collect(ItemAverager::new,
ItemAverager::add,
ItemAverager::combine
).average(); // `collect` will return ItemAverager
// on which we can call average()
System.out.println(avrg); // Output: 2.5
// (since 1+3+1 = 5 and there are only two groups 5/2 = 2.5)
But to be honest in case of no parallel processing I would prefer your own solution using simple loop (maybe with little improvement since you don't need to call contains before add on sets, add internally calls it anyway)
public static double averageForGroup(List<Item> items) {
Set<String> uniqueGroups = new HashSet<>();
double sum = 0;
for (Item item : items) {
uniqueGroups.add(item.getGroupName());
sum += item.getValue();
}
return sum / uniqueGroups.size();
}
public static double getAverageByGroups(List<Item> items) {
Map<String, Double> map = Optional.ofNullable(items).orElse(Collections.emptyList()).stream()
.collect(Collectors.groupingBy(Item::getGroupName, Collectors.summingDouble(Item::getValue)));
return map.isEmpty() ? 0 : map.values().stream().mapToDouble(value -> value).sum() / map.size();
}
In this example getAverageByGroups returns 0 for empty items.

Java stream. Sum two fields in a stream of objects

I have something like this:
Integer totalIncome = carDealer.getBrands().stream().mapToInt(brand -> brand.getManufacturer().getIncome()).sum();
Integer totalOutcome = carDealer.getBrands().stream().mapToInt(brand -> brand.getManufacturer().getOutcome()).sum();
How could I write that in one stream ? to collect f.e. Pair<Integer, Integer> with totalIncome and totalOutcome ?
EDITED:
Thank you guys for your comments, answers, and involvment. I would have a question about different approach to that problem using streams. What do you think about that:
final IncomeAndOutcome incomeAndOutcome = carDealer.getBrands()
.stream()
.map(Brand::getManufacturer)
.map(IncomeAndOutcome::of)
.reduce(IncomeAndOutcome.ZERO, IncomeAndOutcome::sum);
static class IncomeAndOutcome {
private static final IncomeAndOutcome ZERO = of(0, 0);
#Getter
private final int income;
#Getter
private final int outcome;
public static IncomeAndOutcome of(final int income, final int outcome) {
return new IncomeAndOutcome(income, outcome);
}
public static IncomeAndOutcome of(final Manufacturer manufacturer) {
return new IncomeAndOutcome(manufacturer.getIncome(), manufacturer.getOutcome());
}
IncomeAndOutcome(final int income, final int outcome) {
this.income = income;
this.outcome = outcome;
}
IncomeAndOutcome sum(final IncomeAndOutcome incomeAndOutcome) {
return of(this.income + incomeAndOutcome.getIncome(), this.outcome + incomeAndOutcome.getOutcome());
}
}
Without measuring correctly - everything is guessing. The only argument I do agree with is about readability - this is hardly the case here; but in case you wanted to know this for academic purposes, you can do it:
int[] result = carDealer.getBrands()
.stream()
.map(brand -> new int[]{brand.getManufacturer().getIncome(),
brand.getManufacturer().getOutcome()})
.collect(Collector.of(
() -> new int[2],
(left, right) -> {
left[0] += right[0];
left[1] += right[1];
},
(left, right) -> {
left[0] += right[0];
left[1] += right[1];
return left;
}));
This will give you total of income & outcome. Here 1st argument of reduce() is the identity.
If you are not specifying that reduce() function will give optional value.
Pair<Integer, Integer> result = carDealer.getBrands()
.stream()
.map(brand -> Pair.of(brand.getManufacturer().getIncome(), brand.getManufacturer().getOutcome()))
.reduce(Pair.of(0, 0), (pair1, pair2) -> Pair.of(pair1.getFirst() + pair2.getFirst(), pair1.getSecond() + pair2.getSecond()));

Converting alpha numeric string to integer?

I have a hashMap that contains key and value as 'String'. I am getting these values from a web page in my selenium automation script.
my hashmap has following
<Italy, 3.3 millions>
<Venezuela, 30.69 millions>
<Japan, 127.1 millions>
How can I convert all the string alphanumeric values to integers so that I can apply sorting on the hashmap?
I have to display the word 'millions'.
As far as I understand from your question what you need to do is to be able to sort those values, so what you need is a Comparator.
Here is the Comparator that could do the trick:
Comparator<String> comparator = new Comparator<String>() {
#Override
public int compare(final String value1, final String value2) {
return Double.compare(
Double.parseDouble(value1.substring(0, value1.length() - 9)),
Double.parseDouble(value2.substring(0, value2.length() - 9))
);
}
};
System.out.println(comparator.compare("3.3 millions", "30.69 millions"));
System.out.println(comparator.compare("30.69 millions", "30.69 millions"));
System.out.println(comparator.compare("127.1 millions", "30.69 millions"));
Output:
-1
0
1
If you have only millions you can try something like this
String str = "3.3 Millions";
String[] splitted = str.split(" ");
double i = Double.valueOf(splitted[0])*1000000;
System.out.println(i);
or do your calculation depending on the substring
not sure if this is what you are looking for.. If i get it right you have to change your map from
<String, String> to <String, Double>.
See my example below :
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
public class NewClass9 {
public static void main(String[] args) throws ParseException{
Map<String,String> oldMap = new HashMap<>();
oldMap.put("Italy", "3.3 millions");
oldMap.put("Venezuela", "30.69 millions");
oldMap.put("Japan", "127.1 millions");
Map<String,Double> newMap = new HashMap<>();
for(String key : oldMap.keySet()){
newMap.put(key, convert(oldMap.get(key)));
}
for(String key : newMap.keySet()){
System.out.printf("%.0f millions\n" ,newMap.get(key));
}
}
private static double convert(String str) {
String[] splitted = str.split(" ");
return Double.valueOf(splitted[0])*1000000;
}
}
A bit overkill but this should be extensible.
NB: I've only covered the multiplier lookup.
/**
* Possible units and their multipliers.
*/
enum Unit {
Unit(1),
Hundred(100),
Thousand(1000),
Million(1000000),
Billion(1000000000),
Squillion(Integer.MAX_VALUE);
private final int multiplier;
Unit(int multiplier) {
this.multiplier = multiplier;
}
}
/**
* Comparator that matches caseless and plurals
*
* NB: Not certain if this is consistent.
*/
private static final Comparator<String> COMPARECASELESSANDPLURALS
= (String o1, String o2) -> {
// Allow case difference AND plurals.
o1 = o1.toLowerCase();
o2 = o2.toLowerCase();
int diff = o1.compareTo(o2);
if (diff != 0) {
// One character different in length?
if (Math.abs(o1.length() - o2.length()) == 1) {
// Which may be plural?
if (o1.length() > o2.length()) {
// o1 might be plural.
if (o1.endsWith("s")) {
diff = o1.substring(0, o1.length() - 1).compareTo(o2);
}
} else if (o2.endsWith("s")) {
// o2 might be plural.
diff = -o2.substring(0, o2.length() - 1).compareTo(o1);
}
}
}
return diff;
};
// Build my lookup.
static final Map<String, Integer> MULTIPLIERS
= Arrays.stream(Unit.values())
// Collect into a Map
.collect(Collectors.toMap(
// From name of the enum.
u -> u.name(),
// To its multiplier.
u -> u.multiplier,
// Runtime exception in case of duplicates.
(k, v) -> {
throw new RuntimeException(String.format("Duplicate key %s", k));
},
// Use a TreeMap that ignores case and plural.
() -> new TreeMap(COMPARECASELESSANDPLURALS)));
// Gives the multiplier for a word.
public Optional<Integer> getMultiplier(String word) {
return Optional.ofNullable(MULTIPLIERS.get(word));
}
public void test() {
String[] tests = {"Million", "Millions", "Thousand", "Aardvark", "billion", "billions", "squillion"};
for (String s : tests) {
System.out.println("multiplier(" + s + ") = " + getMultiplier(s).orElse(1));
}
}

Java Lambda: Iterate over 2 dim-array keeping the current index

I'm new to Java 8's Lambda Expressions and I want to formulate the following:
I have a 2-dimensional array which I want to iterate over several times in my application code and do stuff with the items in the array. Before i'd do the following:
public static abstract class BlaBlaIterator {
private final BlaBla[][] blabla;
public BlaBlaIterator(final BlaBla[][] blabla) {
this.blabla = blabla;
}
public void iterate() {
final int size = blabla.length;
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
final BlaBla bla = blabla[x][y];
iterateAction(x, y, bla, bla == null);
}
}
}
public abstract void iterateAction(int x, int y, BlaBla bla, boolean isNull);
}
and then
BlaBla[][] blabla = ...
new BlaBlaIterator(blabla) {
#Override
public void iterateAction(final int x, final int y, final BlaBla bla, final boolean isNull) {
//...
}
}.iterate();
Crucial thing: I need access to the current x/y and I need to get calculated things like the isNull.
What I want to do now is to convert this to lambda. I want to write something like this:
BlaBla[] blabla = ...
blabla.stream().forEach((x, y, blabla, isNull) -> ... );
To get a stream from the 2-dimensional Array I can do
Arrays.stream(field).flatMap(x -> Arrays.stream(x))
But then I loose the x/y info and cannot pass calculated stuff like isNull. How can i do this?
To be honest I would keep the traditionnal nested loop, IMO this is a much cleaner approach. Streams are not a substition for all the "old" Java code. Nevertheless, I posted some possible approaches.
First approach
Here's a first possible approach (Object-oriented). Create a class ArrayElement to hold the indices:
class ArrayElement<V> {
public final int row;
public final int col;
public final V elem;
...
}
Then you'll need to create a method that creates a Stream of elements from a single array (the one that we will call for flatMap), and iterateAction just print out the current instance
private static <T> Stream<ArrayElement<T>> createStream(int row, T[] arr) {
OfInt columns = IntStream.range(0, arr.length).iterator();
return Arrays.stream(arr).map(elem -> new ArrayElement<>(row, columns.nextInt(), elem));
}
private static <V> void iterateAction(ArrayElement<V> elem) {
System.out.println(elem);
}
Finally the main looks like this:
String[][] arr = {{"One", "Two"}, {"Three", "Four"}};
OfInt rows = IntStream.range(0, arr.length).iterator();
Arrays.stream(arr)
.flatMap(subArr -> createStream(rows.nextInt(), subArr))
.forEach(Main::iterateAction);
and outputs:
ArrayElement [row=0, col=0, elem=One]
ArrayElement [row=0, col=1, elem=Two]
ArrayElement [row=1, col=0, elem=Three]
ArrayElement [row=1, col=1, elem=Four]
This solution has the disadvantage that it creates a new Object for each Object in the array.
Second approach
The second approach is more direct, it's the same idea but you don't create a new ArrayElement instance for each elem in the array. Again this could be done in a one liner but the lamdba would become ugly so I splitted those up in methods (like in the first approach):
public class Main {
public static void main(String[] args) {
String[][] arr = { {"One", "Two"}, {null, "Four"}};
OfInt rows = IntStream.range(0, arr.length).iterator();
Arrays.stream(arr).forEach(subArr -> iterate(subArr, rows.nextInt()));
}
static <T> void iterate(T[] arr, int row) {
OfInt columns = IntStream.range(0, arr.length).iterator();
Arrays.stream(arr).forEach(elem -> iterateAction(row, columns.nextInt(), elem, elem == null));
}
static <T> void iterateAction(int x, int y, T elem, boolean isNull) {
System.out.println(x+", "+y+", "+elem+", "+isNull);
}
}
and it outputs:
0, 0, One, false
0, 1, Two, false
1, 0, null, true
1, 1, Four, false
Third approach
Using two instances of AtomicInteger
String[][] arr = {{"One", "Two"}, {null, "Four"}};
AtomicInteger rows = new AtomicInteger();
Arrays.stream(arr).forEach(subArr -> {
int row = rows.getAndIncrement();
AtomicInteger colums = new AtomicInteger();
Arrays.stream(subArr).forEach(e -> iterateAction(row, colums.getAndIncrement(), e, e == null));
});
which produces the same output as above.
My conclusion
It's duable using Streams but I really prefer the nested loop in your use-case since you need both the x and y values.
This is an issue, similar to the different forms of for-loop. If you are not interested in the indices, you can imply say:
for(BlaBla[] array: blabla) for(BlaBla element: array) action(element);
But if you are interested in the indices, you can’t use the for-each loop but have to iterate over the indices and get the array element in the loop body. Similarly, you have to stream the indices when using Stream and need the indices:
IntStream.range(0, blabla.length)
.forEach(x -> IntStream.range(0, blabla[x].length)
.forEach(y -> {
final BlaBla bla = blabla[x][y];
iterateAction(x, y, bla, bla == null);
})
);
This is a 1:1 translation which has the advantage of not requiring additional classes, but it consists of two distinct Stream operations rather than one fused operation, as would be preferred.
A single, fused operation might look like this:
helped class:
class ArrayElement {
final int x, y;
BlaBla element;
final boolean isNull;
ArrayElement(int x, int y, BlaBla obj) {
this.x=x; this.y=y;
element=obj; isNull=obj==null;
}
}
actual operation:
IntStream.range(0, blabla.length).boxed()
.flatMap(x -> IntStream.range(0, blabla[x].length)
.mapToObj(y->new ArrayElement(x, y, blabla[x][y])))
.forEach(e -> iterateAction(e.x, e.y, e.element, e.isNull));

Categories

Resources