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.
Related
My program calculates the digit sums of all values entered into a text file. The entered values and their according digit sums are stored in two seperate ArrayLists.
Both ArrayLists are combined into a LinkedHashMap in the end which should be ordered by the digit sum in descending order.
If you enter multiple values with the same digit sum, it's supposed to order those (and only those) in descending order by their original value, not the digit sum this time (the rest stays the same as before).
How do I achieve this with Comparators?
My lists and the map:
String filePath = args[0];
LineNumberReader br = new LineNumberReader(new FileReader(filePath));
LinkedHashMap<BigInteger, BigInteger> unsortedMap = new LinkedHashMap<BigInteger, BigInteger>();
List<BigInteger> inputList = new ArrayList<>();
List<BigInteger> DSList = new ArrayList<>();
if(br.ready()){
while (true) {
String line = br.readLine();
if (line == null) {
break;
}
BigInteger input = new BigInteger(line);
inputList.add(input);
DSList.add(methods.digitSum(input));
}
}
for(int i = 0; i < inputList.size(); i++){
unsortedMap.put(inputList.get(i), DSList.get(i));
}
for(BigInteger key : unsortedMap.keySet()){
System.out.println(new BigDecimal(key).toPlainString() + " (Digit Sum: " + unsortedMap.get(key) + (")"));
}
methods.digitSum:
public static BigInteger digitSum(BigInteger number) {
String digits = number.toString();
int sum = 0;
for(int i = 0; i < digits.length(); i++) {
int digit = (int) (digits.charAt(i) - '0');
sum = sum + digit;
}
return BigInteger.valueOf(sum);
}
Output has to look like this:
x (digit sum: y)
x (digit sum: y)
...
x = entered value
y = digit sum of x
If you need any further information, feel free to ask.
Here is a solution with a simple class and a Comparator
class Values {
BigInteger number;
BigInteger digitSum;
Values(BigInteger number, BigInteger sum) {
this.number = number;
this.digitSum = sum;
}
#Override
public String toString() {
return number + " (digit sun:" + digitSum + ")";
}
}
And then create a list with this class
List<Values> inputList = new ArrayList<>();
and add objects of Values to the list using the constructor when reading the file
For sorting you can create a Comparator object like this
Comparator<Values> compareSum = (Values v1, Values v2) -> {
int result = v1.digitSum.compareTo(v2.digitSum);
return result != 0 ? result : v1.number.compareTo(v2.number);
};
and sort your list in descending order
inputList.sort(compareSum.reversed());
As there are duplicate keys with different values, you cannot use LinkedHashMap or any other Java Map implementations to store the same key with different value.
You can either create your custom Map or use Apache's Common Map Implementations. The following code shows a solution using MultiValueMap and a custom comparator that sorts by key and then by value in descending order:
MultiValuedMap<BigInteger, BigInteger> unsortedMap = new ArrayListValuedHashMap<>();
if(br.ready()){
while (true) {
String line = br.readLine();
if (line == null) {
break;
}
//Use a single map to put both values
unsortedMap.put(new BigInteger(line), new BigInteger(methods.digitSum(line)));
}
}
//Do the sorting using custom comparator
List<Map.Entry<BigInteger, BigInteger>> list = new LinkedList<>(unsortedMap.entries());
// Sort the list
Collections.sort(list, new Comparator<Map.Entry<BigInteger, BigInteger> >() {
public int compare(Map.Entry<BigInteger, BigInteger> o1,
Map.Entry<BigInteger, BigInteger> o2) {
int c = o1.getKey().compareTo(o2.getKey());
if (c==0){
c = o1.getValue().compareTo(o2.getValue());
}
return c * -1; //descending
}
});
//Place the sorted List into another Map that retains the insert order
Multimap<BigInteger, BigInteger> sortedMap = LinkedHashMultimap.create();
for (Map.Entry<BigInteger, BigInteger> entry : list) {
sortedMap.put(entry.getKey(), entry.getValue());
}
//Print
for(BigInteger key : sortedMap.keySet()){
for (BigInteger bi: sortedMap.get(key)) {
System.out.println("x: " + new BigDecimal(key).toPlainString() + " (Digit Sum: " + bi + (")"));
}
}
To use the above Maps you should include the apache commons collections dependency
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
You can refer to below code to sort your map by value in descending order (in case of same values, sort in descending order of keys). In your case, the keys of the map (passed in parameter) are the inputs from text file and respective values in map are the digit sum.
public static LinkedHashMap<BigInteger, BigInteger> sortByValue(LinkedHashMap<BigInteger, BigInteger> hm)
{
List<Map.Entry<BigInteger, BigInteger> > list =
new LinkedList<Map.Entry<BigInteger, BigInteger> >(hm.entrySet());
Collections.sort(list, new Comparator<Map.Entry<BigInteger, BigInteger> >() {
public int compare(Map.Entry<BigInteger, BigInteger> o1,
Map.Entry<BigInteger, BigInteger> o2)
{
// To handle same value case
if(o1.getValue().compareTo(o2.getValue()) == 0) {
return (o2.getKey()).compareTo(o1.getKey());
}
return (o2.getValue()).compareTo(o1.getValue());
}
});
LinkedHashMap<BigInteger, BigInteger> temp = new LinkedHashMap<BigInteger, BigInteger>();
for (Map.Entry<BigInteger, BigInteger> aa : list) {
temp.put(aa.getKey(), aa.getValue());
}
return temp;
}
The compareTo method is called on object o2 rather than object o1 to get the result in descending order. As mentioned in comment of above code snippet, the if condition will handle your requirement of comparing the keys and sort in descending order of keys when the values are same.
The list of Entry objects is sorted as as per your requirement and these sorted Entry objects are added to LinkedHashMap to maintain the insertion order into the map.
I have a custom Object Itemized which has two fields amount and tax. I have an array of Itemized objects and I am looking to sum the two fields in the same stream. Below is how I am calculating the sum of both the fields.
double totalAmount = Arrays.stream(getCharges()).map(Itemized::getAmount).reduce(0.0, Double::sum));
double totalTax = Arrays.stream(getCharges()).map(Itemized::getTax).reduce(0.0, Double::sum));
Is there any way I don't have to parse the stream two times and can sum the two fields in one go ? I am not looking to sum totalTax and totalAmount but want their sum separately. I was looking at Collectors but was not able to find any example which would allow aggregating of multiple fields in one go.
use a for loop ?
double taxSum = 0;
double amountSum = 0;
for (Itemized itemized : getCharges()) {
taxSum += itemized.getTax();
amountSum += itemized.getAmount();
}
You can try to use the teeing Collector, like so:
Arrays.stream(getCharges()) // Get the charges as a stream
.collect(Collectors // collect
.teeing( // both of the following:
Collectors.summingDouble(Itemized::getAmount), // first, the amounts
Collectors.summingDouble(Itemized::getTax), // second, the sums
Map::entry // and combine them as an Entry
)
);
This should give you a Map.Entry<Double, Double> with the sum of amounts as the key and the sum of tax as the value, which you can extract.
Edit:
Tested and compiled it - it works. Here we go:
ItemizedTest.java
public class ItemizedTest
{
static Itemized[] getCharges()
{
// sums should be first param = 30.6, second param = 75
return new Itemized[] { new Itemized(10, 20), new Itemized(10.4,22), new Itemized(10.2, 33) };
}
public static void main(String[] args)
{
Map.Entry<Double, Double> sums = Arrays.stream(getCharges())
.collect(Collectors
.teeing(
Collectors.summingDouble(Itemized::getAmount),
Collectors.summingDouble(Itemized::getTax),
Map::entry
)
);
System.out.println("sum of amounts: "+sums.getKey());
System.out.println("sum of tax: "+sums.getValue());
}
}
Itemized.java
public final class Itemized
{
final double amount;
final double tax;
public double getAmount()
{
return amount;
}
public double getTax()
{
return tax;
}
public Itemized(double amount, double tax)
{
super();
this.amount = amount;
this.tax = tax;
}
}
Output:
sum of amounts: 30.6
sum of tax: 75.0
P.S.: teeing Collector is only available in Java 12+.
Instead of summing by field, you define a custom object to hold both field's sum values:
ItemizedValues {
private double amount;
private double tax;
public static final ItemizedValues EMPTY = new ItemizedValues(0, 0);
// Constructor - getter - setter
public static ItemizedValues of(Itemized item) {
return new ItemizedValues(amount, tax);
}
public static ItemizedValues sum(ItemizedValues a, ItemizedValues b) {
// Sum the fields respectively
// It's your choice to reuse the existing instances, modify the values or create a brand new one
}
}
Arrays.stream(getCharges())
.map(ItemizedValues::of)
.reduce(ItemizedValues.EMPTY, ItemizedValues::sum);
With a data structure that can allow one to accumulate both sums, you can reduce the stream to a single object.
This is using double[] to hold totalAmount at index 0 and totalTax at index 1 (other options include SimpleEntry, Pair):
double[] res = Arrays.stream(getCharges())
.map(ch -> new double[] { ch.getAmount(), ch.getTax() })
.reduce(new double[] { 0, 0 },
(a1, a2) -> new double[] { a1[0] + a2[0], a1[1] + a2[1] });
double totalAmount = res[0],
totalTax = res[1];
You can do it by using Entry but still you will end up in creating lot of objects, the best solution i would suggest is for loop answered by NimChimpsky
Entry<Double, Double> entry = Arrays.stream(new Itemized[] {i1,i2})
.map(it->new AbstractMap.SimpleEntry<>(it.getAmount(), it.getTax()))
.reduce(new AbstractMap.SimpleEntry<>(0.0,0.0),
(a,b)->new AbstractMap.SimpleEntry<>(a.getKey()+b.getKey(),a.getValue()+b.getValue()));
System.out.println("Amount : "+entry.getKey());
System.out.println("Tax : "+entry.getValue());
In your specific case, it's could be done by using your Itemized class as value holder.
Itemized result = Arrays.stream(getCharges())
.reduce(new Itemized(), (acc, item) -> {
acc.setAmount(acc.getAmount() + item.getAmount());
acc.setTax(acc.getTax() + item.getTax());
return acc;
});
double totalAmount = result.getAmount();
double totalTax = result.getTax();
I hope someone can help me I am trying to find a way with which i can filter a list based on a condition
public class Prices {
private String item;
private double price;
//......
}
For example i have a list of above object List has the following data
item, price
a 100,
b 200,
c 250,
d 350,
e 450
is there a way to use streams and filter on List so that at the end of it we are left with only objects that have a sum of prices less that a given input value
Say if the input value is 600, so the resultant list would only have a,b,c,d as these are the objects whose price, when added to each other, the sum takes it closer to 600. So e would not be included in the final filtered list.
If the input/given value is 300 then the filtered list will only have a and b.
The list is already sorted and will start from the top and keep on adding till the given value is reached
Thanks
Regards
You can write this static method, that create suitable predicate:
public static Predicate<Prices> byLimitedSum(int limit) {
return new Predicate<Prices>() {
private int sum = 0;
#Override
public boolean test(Prices prices) {
if (sum < limit) {
sum += prices.price;
return true;
}
return false;
}
};
}
And use it:
List<Prices> result = prices.stream()
.filter(byLimitedSum(600))
.collect(Collectors.toList());
But it is bad solution for parallelStream.
Anyway i think in this case stream and filter using is not so good decision, cause readability is not so good. Better way, i think, is write util static method like this:
public static List<Prices> filterByLimitedSum(List<Prices> prices, int limit) {
List<Prices> result = new ArrayList<>();
int sum = 0;
for (Prices price : prices) {
if (sum < limit) {
result.add(price);
sum += price.price;
} else {
break;
}
}
return result;
}
Or you can write wrapper for List<Prices> and add public method into new class. Use streams wisely.
Given you requirements, you can use Java 9's takeWhile.
You'll need to define a Predicate having a state:
Predicate<Prices> pred = new Predicate<Prices>() {
double sum = 0.0;
boolean reached = false;
public boolean test (Prices p) {
sum += p.getPrice();
if (sum >= 600.0) { // reached the sum
if (reached) { // already reached the some before, reject element
return false;
} else { // first time we reach the sum, so current element is still accepted
reached = true;
return true;
}
} else { // haven't reached the sum yet, accept current element
return true;
}
}
};
List<Prices> sublist =
input.stream()
.takeWhile(pred)
.collect(Collectors.toList());
The simplest solution for this kind of task is still a loop, e.g.
double priceExpected = 600;
int i = 0;
for(double sumCheck = 0; sumCheck < priceExpected && i < list.size(); i++)
sumCheck += list.get(i).getPrice();
List<Prices> resultList = list.subList(0, i);
A Stream solution fulfilling all formal criteria for correctness, is much more elaborated:
double priceThreshold = 600;
List<Prices> resultList = list.stream().collect(
() -> new Object() {
List<Prices> current = new ArrayList<>();
double accumulatedPrice;
},
(o, p) -> {
if(o.accumulatedPrice < priceThreshold) {
o.current.add(p);
o.accumulatedPrice += p.getPrice();
}
},
(a,b) -> {
if(a.accumulatedPrice+b.accumulatedPrice <= priceThreshold) {
a.current.addAll(b.current);
a.accumulatedPrice += b.accumulatedPrice;
}
else for(int i=0; a.accumulatedPrice<priceThreshold && i<b.current.size(); i++) {
a.current.add(b.current.get(i));
a.accumulatedPrice += b.current.get(i).getPrice();
}
}).current;
This would even work in parallel by just replacing stream() with parallelStream(), but it would not only require a sufficiently large source list to gain a benefit, since the loop can stop at the first element exceeding the threshold, the result list must be significantly larger than ยน/n of the source list (where n is the number of cores) before the parallel processing can have an advantage at all.
Also the loop solution shown above is non-copying.
Using a simple for loop would be much much simpler, and this is abusive indeed as Holger mentions, I took it only as an exercise.
Seems like you need a stateful filter or a short-circuit reduce. I can think of this:
static class MyException extends RuntimeException {
private final List<Prices> prices;
public MyException(List<Prices> prices) {
this.prices = prices;
}
public List<Prices> getPrices() {
return prices;
}
// make it a cheap "stack-trace-less" exception
#Override
public Throwable fillInStackTrace() {
return this;
}
}
This is needed to break from the reduce when we are done. From here the usage is probably trivial:
List<Prices> result;
try {
result = List.of(
new Prices("a", 100),
new Prices("b", 200),
new Prices("c", 250),
new Prices("d", 350),
new Prices("e", 450))
.stream()
.reduce(new ArrayList<>(),
(list, e) -> {
double total = list.stream().mapToDouble(Prices::getPrice).sum();
ArrayList<Prices> newL = new ArrayList<>(list);
if (total < 600) {
newL.add(e);
return newL;
}
throw new MyException(newL);
},
(left, right) -> {
throw new RuntimeException("Not for parallel");
});
} catch (MyException e) {
e.printStackTrace();
result = e.getPrices();
}
result.forEach(x -> System.out.println(x.getItem()));
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));
}
}
I have an ArrayList of Dico and I try to extract a distinct string from Arraylist of Dico.
This is the Dico class.
public class Dico implements Comparable {
private final String m_term;
private double m_weight;
private final int m_Id_doc;
public Dico(int Id_Doc, String Term, double tf_ief) {
this.m_Id_doc = Id_Doc;
this.m_term = Term;
this.m_weight = tf_ief;
}
public String getTerm() {
return this.m_term;
}
public double getWeight() {
return this.m_weight;
}
public void setWeight(double weight) {
this.m_weight = weight;
}
public int getDocId() {
return this.m_Id_doc;
}
}
I use this function to extract 1000 distinct value from middle of this array:
i start form the middle and i take only distinct value in both direction left and right
public static List <String> get_sinificativ_term(List<Dico> dico)
{
List <String> term = new ArrayList();
int pos_median= ( dico.size() / 2 );
int count=0;
int i=0;
int j=0;
String temp_d = dico.get(pos_median).getTerm();
String temp_g =temp_d;
term.add(temp_d);
while(count < 999) // count of element
{
if(!temp_d.equals(dico.get( ( pos_median + i) ).getTerm()))
{
temp_d = dico.get(( pos_median + i)).getTerm(); // save current term in temp
// System.out.println(temp_d);
term.add(temp_d); // add term to list
i++; // go to the next value-->right
count++;
// System.out.println(temp_d);
}
else
i++; // go to the next value-->right
if(!temp_g.equals(dico.get( ( pos_median+j ) ).getTerm()))
{
temp_g = dico.get(( pos_median+j )).getTerm();
term.add(temp_g );// add term to array
// System.out.println(temp_g);
j--; // go to the next value-->left
count++;
}
else
j--;// go to the next value-->left
}
return term;
}
I would like to make my solution more faster than this function,if is possible can i make this with Java SE 8 Streams ?
Streams will not make it faster but can make it much simpler and clearer.
Here's the simplest version. It will take all list indexes, sort them by distance to the middle of the list, get the corresponding term, filter out duplicates and limit to 1000 elements. It will certainly be slower than your iterative code, but much easier to follow because the code neatly mirrors its English description:
public static List<String> get_sinificativ_term(List<Dico> dicolist) {
int size = dicolist.size();
return IntStream.range(0, size)
.boxed()
.sorted(comparing(i -> Math.abs(size / 2 - i)))
.map(dicolist::get)
.map(Dico::getTerm)
.distinct()
.limit(1000)
.collect(toList());
}
If your list is really huge and you want to avoid sorting it, you can trade away some simplicity for performance. This version does a bit of math to go right-left-right-left from center:
public static List<String> get_sinificativ_term(List<Dico> dicolist) {
int size = dicolist.size();
return IntStream.range(0, size)
.map(i -> i % 2 == 0 ? (size + i) / 2 : (size - i - 1) / 2)
.mapToObj(i -> dicolist.get(i).getTerm())
.distinct()
.limit(1000)
.collect(toList());
}
Can't you do something like this?
public static List <String> get_sinificativ_term(List<Dico> dico) {
List<String> list = dico.stream()
.map(Dico::getTerm)
.distinct()
.limit(1000)
.collect(Collectors.toList());
if(list.size() != 1000) {
throw new IllegalStateException("Need at least 1000 distinct values");
}
return list;
}
You need to check the size because you can have less than 1000 distinct values. If efficiency is a concern you can try to run the pipeline in parallel and measure if its faster.