Java stream with list - java

I'm new to streams and how they work and I am trying to get the occurrences of a specific object that is added in the list.
I found a way doing this using Collections. It goes as follows:
for (int i = 0; i < countries.size(); i++) {
int occurrences = Collections.frequency(countries, countries.get(i));
}
But I want to use stream.
The method I used with streams was:
countries.parallelStream().filter(p -> p.contentEquals(countries.get(countries.size()-1))).count()
This only returns the current object and its occurrences, instead I want all the objects and their occurrences.
Edit:
`private final ArrayList<String> countries = new ArrayList<>();
dataset.setValue(countries.parallelStream()
.filter(p -> p.contentEquals(countries.get(countries.size()-1)) )
.count(),
"",
countries.get(countries.size()-1)); //sets the graph for the given country
#Override
public void addCountries(String country) {
countries.add(country);
}
#Override
public void removeCountries(int country) {
countries.remove(country);
}`
I am making a graph. The first statement of dataset.setValue() is the amount of occurrences of the country. This has to be done for each country so you can see how many occurrences of a country there is. hope this helps
the graph
Edit 2: solved!
countries.stream().distinct().forEach(o ->
dataset.setValue(Collections.frequency(countries, o),
"",
o)); //sets the graph for the given country

You could also use grouping collector:
Collection<Integer> collection = Arrays.asList(1, 2, 1, 4, 2);
final Map<Integer, Long> map = collection.stream().collect(
Collectors.groupingBy(el -> el, Collectors.counting()));
System.out.println(map);
This produces
{1=2, 2=2, 4=1}

You can do this:
List<Integer> lista = new ArrayList<>(Arrays.asList(1,2,3,3,2,1,5,4,1,2,3));
lista.stream().distinct().forEach(o -> System.out.println(o + " occures " + Collections.frequency(lista, o) + " times in a list !"));
Output:
1 occures 3 times in a list !
2 occures 3 times in a list !
3 occures 3 times in a list !
5 occures 1 times in a list !
4 occures 1 times in a list !
In short:
We make stream out of your list, we remove duplicates from the stream by using .distinct(), and now when we are left with unique elements from the list we use Collections.frequency to print out how many times each element occurs in the list.

Related

Java functional programming for multiple functionality with single stream data

There is a List of object like:-
ID Employee IN_COUNT OUT_COUNT Date
1 ABC 5 7 2020-06-11
2 ABC 12 5 2020-06-12
3 ABC 9 6 2020-06-13
This is the an employee data for three date which I get from a query in List object.
Not I want total number of IN_COUNT and OUT_COUNT between three date. This can be achieved by doing first iterating stream for only IN_COUNT and calling sum() and then in second iteration, only OUT_COUNT data can be summed. But I don't want to iterate the list two times.
How is this possible in functional programming using stream or any other option.
What you are trying to do is called a 'fold' operation in functional programming. Java streams call this 'reduce' and 'sum', 'count', etc. are just specialized reduces/folds. You just have to provide a binary accumulation function. I'm assuming Java Bean style getters and setters and an all args constructor. We just ignore the other fields of the object in our accumulation:
List<MyObj> data = fetchData();
Date d = new Date();
MyObj res = data.stream()
.reduce((a, b) -> {
return new MyObj(0, a.getEmployee(),
a.getInCount() + b.getInCount(), // Accumulate IN_COUNT
a.getOutCount() + b.getOutCount(), // Accumulate OUT_COUNT
d);
})
.orElseThrow();
This is simplified and assumes that you only have one employee in the list, but you can use standard stream operations to partition and group your stream (groupBy).
If you don't want to or can't create a MyObj, you can use a different type as accumulator. I'll use Map.entry, because Java lacks a Pair/Tuple type:
Map.Entry<Integer, Integer> res = l.stream().reduce(
Map.entry(0, 0), // Identity
(sum, x) -> Map.entry(sum.getKey() + x.getInCount(), sum.getValue() + x.getOutCount()), // accumulate
(s1, s2) -> Map.entry(s1.getKey() + s2.getKey(), s1.getValue() + s2.getValue()) // combine
);
What's happening here? We now have a reduce function of Pair accum, MyObj next -> Pair. The 'identity' is our start value, the accumulator function adds the next MyObj to the current result and the last function is only used to combine intermediate results (e.g., if done in parallel).
Too complicated? We can split the steps of extracting interesting properties and accumulating them:
Map.Entry<Integer, Integer> res = l.stream()
.map(x -> Map.entry(x.getInCount(), x.getOutCount()))
.reduce((x, y) -> Map.entry(x.getKey() + y.getKey(), x.getValue() + y.getValue()))
.orElseGet(() -> Map.entry(0, 0));
You can use reduce to done this:
public class Counts{
private int inCount;
private int outCount;
//constructor, getters, setters
}
public static void main(String[] args){
List<Counts> list = new ArrayList<>();
list.add(new Counts(5, 7));
list.add(new Counts(12, 5));
list.add(new Counts(9, 6));
Counts total = list.stream().reduce(
//it's start point, like sum = 0
//you need this if you don't want to modify objects from list
new Counts(0,0),
(sum, e) -> {
sum.setInCount( sum.getInCount() + e.getInCount() );
sum.setOutCount( sum.getOutCount() + e.getOutCount() );
return sum;
}
);
System.out.println(total.getInCount() + " - " + total.getOutCount());
}

How to get 10 Objects based on a value from the Object?

I have to create a method that gives the 10 Taxpayers that spent the most in the entire system.
There's a lot of classes already created and code that would have to be in between but what I need is something like:
public TreeSet<Taxpayer> getTenTaxpayers(){
TreeSet<Taxpayer> taxp = new TreeSet<Taxpayer>();
...
for(Taxpayer t: this.taxpayers.values()){ //going through the Map<String, Taxpayer>
for(Invoice i: this.invoices.values()){ //going through the Map<String, Invoice>
if(taxp.size()<=10){
if(t.getTIN().equals(i.getTIN())){ //if the TIN on the taxpayer is the same as in the Invoice
...
}
}
}
}
return taxp;
}
To sum it up, I have to go through a Map<String, Taxpayer> which has for example 100 Taxpayers, then go through a Map<String, Invoice> for each respective invoice and return a new Collection holding the 10 Taxpayers that spent the most on the entire system based on 1 attribute on the Invoice Class. My problem is how do I get those 10, and how do I keep it sorted. My first look at it was to use a TreeSet with a Comparator but the problem is the TreeSet would be with the class Taxpayer while what we need to compare is an attribute on the class Invoice.
Is this a classic Top K problem ? Maybe you can use the java.util.PriorityQueue to build a min heap to get the top 10 Taxpayer.
This can be broken down into 3 steps:
Extract distinct TaxPayers
Extract Invoices for each payer and then sum amount
Sort by the payed amount and limit to first 10
If you are using java-8 you can do something like:
final Map<TaxPayer, Double> toTenMap = payersMap.values() // get values from map
.stream() // create java.util.Stream
.distinct() // do not process duplicates (TaxPayer must provide a standard-compliant equals method)
.map(taxPayer -> {
final double totalAmount = invoicesMap
.values() // get values from the invoices map
.stream() // create Stream
.filter(invoice -> invoice.getTIN().equals(taxPayer.getTIN())) // get only those for the current TaxPayer
.mapToDouble(Invoice::getAmount) // get amount
.sum(); // sum amount
return new AbstractMap.SimpleEntry<>(taxPayer, totalAmount); // create Map.Entry
})
.sorted( ( entry1, entry2 ) -> { // sort by total amount
if (entry1.getValue() > entry2.getValue()) return 1;
if (entry1.getValue() < entry2.getValue()) return -1;
return 0;
})
.limit(10) // get only top ten payers
.collect(Collectors.toMap( // save to map
AbstractMap.SimpleEntry::getKey,
AbstractMap.SimpleEntry::getValue
));
Surely there is a more elegant solution. Also, I haven't tested it because I don't have much time now.

Count and remove similar elements in a list while iterating through it

I used many references in the site to build up my program but I'm kind of stuck right now. I think using iterator will do the job. Sadly even though I went through questions which had iterator, I couldn't get the way of using it properly to implement it on my code.
I want to,
1. remove the similar elements found in the list fname
2. count & add the that count of each element found in fname to
counter.
Please help me do the above using iterator or with any other method. Following is my code,
List<String> fname = new ArrayList<>(Arrays.asList(fullname.split(""))); //Assigning the string to a list//
int count = 1;
ArrayList<Integer> counter = new ArrayList<>();
List<String> holder = new ArrayList<>();
for(int element=0; element<=fname.size; element++)
{
for(int run=(element+1); run<=fname.size; run++)
{
if((fname.get(element)).equals(fname.get(run)))
{
count++;
holder.add(fname.get(run));
}
counter.add(count);
}
holder.add(fname.get(element));
fname.removeAll(holder);
}
System.out.println(fname);
System.out.println(counter);
Thanks.
From your questions, you basically want to:
1. Eliminate duplicates from given String List
You can simply convert your List to HashSet (it doesn't allow duplicates) and then convert it back to list (if you want the end result to be a List so you can do something else with it...)
2. Count all occurences of unique words in your list
The fastest coding is to use Java 8 Streams (code borrowed frome here: How to count the number of occurrences of an element in a List)
Complete code
public static void main(String[] args) {
String fullname = "a b c d a b c"; //something
List<String> fname = new ArrayList<>(Arrays.asList(fullname.split(" ")));
// Convert input to Set, and then back to List (your program output)
Set<String> uniqueNames = new HashSet<>(fname);
List<String> uniqueNamesInList = new ArrayList<>(uniqueNames);
System.out.println(uniqueNamesInList);
// Collects (reduces) your list
Map<String, Long> counts = fname.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(counts);
}
I do not think that you need iterators here. However, there are many other possible solutions you could use, like recursion. Nevertheless, I have just modified your code as the following:
final List<String> fname = new ArrayList<String>(Arrays.asList(fullname.split("")));
// defining a list that will hold the unique elements.
final List<String> resultList = new ArrayList<>();
// defining a list that will hold the number of replication for every item in the fname list; the order here is same to the order in resultList
final ArrayList<Integer> counter = new ArrayList<>();
for (int element = 0; element < fname.size(); element++) {
int count = 1;
for (int run = (element + 1); run < fname.size(); run++) {
if ((fname.get(element)).equals(fname.get(run))) {
count++;
// we remove the element that has been already counted and return the index one step back to start counting over.
fname.remove(run--);
}
}
// we add the element to the resulted list and counter of that element
counter.add(count);
resultList.add(fname.get(element));
}
// here we print out both lists.
System.out.println(resultList);
System.out.println(counter);
Assuming String fullname = "StringOfSomeStaff"; the output will be as the following:
[S, t, r, i, n, g, O, f, o, m, e, a]
[3, 2, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1]
You can try something like this:
Set<String> mySet = new HashSet<>();
mySet.addAll( fname ); // Now you have unique values
for(String s : mySet) {
count = 0;
for(String x : fname) {
if( s.equals(x) ) { count++; }
}
counter.add( count );
}
This way we don't have a specific order. But I hope it helps.
In Java 8, there's a one-liner:
List<Integer> result = fname
.stream()
.collect(Collectors.groupingBy(s -> s))
.entrySet()
.stream()
.map(e -> e.getValue().size())
.collect(Collectors.toList());
I was using LinkedHashMap to preserve order of elements. Also for loop, which I am using, implicitly uses Iterator. Code example is using Map.merge method, which is available since Java 8.
List<String> fname = new ArrayList<>(Arrays.asList(fullname.split("")));
/*
Create Map which will contain pairs kay=values
(in this case key is a name and value is the counter).
Here we are using LinkedHashMap (instead of common HashMap)
to preserve order in which name occurs first time in the list.
*/
Map<String, Integer> countByName = new LinkedHashMap<>();
for (String name : fname) {
/*
'merge' method put the key into the map (first parameter 'name').
Second parameter is a value which we that to associate with the key
Last (3rd) parameter is a function which will merge two values
(new and ald) if map already contains this key
*/
countByName.merge(name, 1, Integer::sum);
}
System.out.println(fname); // original list [a, d, e, a, a, f, t, d]
System.out.println(countByName.values()); // counts [3, 2, 1, 1, 1]
System.out.println(countByName.keySet()); // unique names [a, d, e, f, t]
Also same might be done using Stream API but it would be probably hard for understanding if you are not familiar with Streams.
Map<String, Long> countByName = fname.stream()
.collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));

Java8 Lambda Expressions Evaluation

List <Person> roster = new List<Person>();
Integer totalAgeReduce = roster
.stream()
.map(Person::getAge)
.reduce(
0,
(a, b) -> a + b);
Can anyone help me understand the above code snippet. My understanding is that the stream method will first iterate through the entire roster List and while it is iterating it will create a new List of the mapped objects with every person's age in it. Then it will finally call the reduce after the mapping is done (the reduce is only called at the end after mapping correct?). And in the reduce it starts of at 0, and in the first iteration of reduce on the newly mapped list a = 0 and b is equal to the first element in the List that was created from the mapping function. Then it will continue and add all the elements from the mapped list and return to you an integer with the sum of all the ages.
Each item in the stream will each be sent through all the steps one at a time. Here's some test code to help you see whats happening:
List<String> test = Arrays.asList("A","B");
System.out.println("END: " + test.stream()
.map(s -> {System.out.println("1 " + s); return s; })
.map(s -> {System.out.println("2 " + s); return s; })
.reduce("", (acc, s) -> {System.out.println("3 " + s); return acc + s; })
);
Output
1 A
2 A
3 A
1 B
2 B
3 B
END: AB
TL;DR
It sums all the ages from the Person's within the List.
stream() : Creates a stream from the Collection (List)
map() : Will make a mapping from the received object to another object (here from Person to Integer (getAge returns an Integer))
reduce(0,(a, b) -> a + b) : reduce is a reduction (it reduces all the objects received into one (here the action is to add them all together, a big addition). It takes the identity (first value to begin with) as first argument and the following lambda expression (BinaryOperator<Integer> or BiFunction<Integer, Integer, Integer>) presents the logic to apply for the reduction.
Example
List<Person> persons = Arrays.asList(new Person("John", 20),
new Person("Mike", 40),
new Person("Wayne", 30));
Integer totalAgeReduce = roster.stream()
.map(Person::getAge)
.reduce(0,(a, b) -> a + b);
System.out.println(totalAgeReduce); // 90
The thing is
(a, b) -> a + b);
is an accumulator, and if you look at it like a recursive function, it will be passing the result of the sum, for every element in the stream, as Andreas Point out is not a list, is a pipeline.
Just to point out lambda expressions is just passing an Argument which in fact is a function.
If you would use loops it would look like this:
List<Integer> ages = new ArrayList<>();
for (Person p : roster) {
ages.add(p.getAge());
}
int sum = 0;
for (Integer age : ages) {
sum += age;
}

Grouping two arrays into one array of common results

This is a logic problem I cant seem to wrap my head around.
What I wish to achieve is to match two different products from two different sets that have the same total value into packages.
for simplicity sake lets say we have two arrays with [id,price] pairs
array1 = [id=A,price=1],[id=B,price=2],[id=C,price=3]
array2 = [id=A,price=2],[id=B,price=1],[id=C,price=1]
the cheapest package I can make is for $2 by combining array1 [id A] and array2 [id B]
but I can also combine array1 [id A] and array2 [id C] to make a $2 package.
So what I am trying to get is a set of packages that could be grouped by total value. but only according to what the is selected in the first array.
eg : (purely for visualization purposes) :
package1 :
total : $2
1st option :
[id=A,price=1]
2nd option
[id=B,price=1]
[id=C,price=1]
package2 :
total : $3
1st option :
[id=A,price=1]
2nd option
[id=A,price=2]
package3 :
total : $3
1st option :
[id=B,price=2]
2nd option
[id=B,price=1]
[id=C,price=1]
etc.
Im assuming I will need to recursively iterate through the results, I just keep going down the wrong path and maybe someone could point me in the right direction.
Best option is to calculate result for all combinations, and store the options to a map structure at the time the results are calculated.
If you are using a custom object for your class (MyOption):
Map<Integer, Map<MyOption, List<MyOption>> result = new HashMap<>();
Or, if you're using object array:
Map<Integer, Map<Object[], List<Object[]>> result = new HashMap<>();
Maybe you can generate a [price] <--> [item list] map, the key is price, the value is a list of item with same price. e.g.
[1] -- [Item A, Item B], [2] -- [Item D, Item E, Item Z], then you can generated different package base on the map.
private void methodToGenerateCombinationOfItems {
Map<Integer, List<Item>> map = new HashMap<Integer, List<Item>>();
updateArrayDataToMap(map, array1);
updateArrayDataToMap(map, array2);
... ...
// now the map should contains [price X] <--> [list of items with price X]
// if you want to order by price, you may want to use TreeMap instead of HashMap.
}
private void updateArrayDataToMap(Map<Integer, List<Item>> map, List<Item> itemArr) {
for( Item item : itemArr) {
if( map.contains(item.getPrice()) ) {
map.get(item.getPrice()).add(item);
} else {
List<Item> listTmp = new ArrayList<Item>();
listTmp.add(item);
map.put(item.getPrice(), listTmp);
}
}
}
The pseudo code for this:
List<Package> results = new ArrayList<>();
for(Item item:array1) {
if (item.getPrice() < 2) {
addAllPackagesStartingWith(item, results);
}
}
// add all packages where an item from array 1 can be paired with an item in array 2
void addAllPackagesStartingWith(Item item, List<Package> results) {
for (Item array2Item:array2) {
if(item.getPrice() + array2Item.getPrice() < 2) {
results.add(new Package(item, array2Item));
}
}
}

Categories

Resources