Java stream operations - java-8

I have an object, "Item" with fields:
int: id
String: prices
The string prices contains one or more price values separated by comma.
Problem: Using stream operations I want a map of type Map<Integer,Set<Integer>> from List<Item> items
where key of Map is id and Set has value of prices as Integer extracted from the String prices.
There will be repeated ids in the List and can have different price strings.
I have come up with an approach which produces the following result:
Map<Integer, Set<List<Integer>>> itemStoresMapIntermediate = items.stream()
.collect(Collectors.groupingBy(Item::getItemId,Collectors.mapping(Item::getStoresAsIntList, Collectors.toSet())));
getStoresAsIntList() returns List of price values from String prices in the object.
The above is certainly not what I want.

If I understood correctly...
Item item1 = new Item(1, "22,23,24");
Item item2 = new Item(2, "33,34,35");
Item item3 = new Item(1, "22,57,58");
Map<Integer, Set<Integer>> map = Stream.of(item1, item2, item3)
.collect(Collectors.toMap(
Item::getId,
i -> new HashSet<>(i.getStoresAsIntList()),
(left, right) -> {
left.addAll(right);
return left;
}, HashMap::new));
System.out.println(map); // {1=[22, 23, 24, 57, 58], 2=[33, 34, 35]}

Related

How to sort string with numbers in it numerically?

So I am getting some json data and putting it inside of a Mutable List. I have a class with id, listId, and name inside of it. Im trying to sort the output of the list by listId which are just integers and then also the name which has a format of "Item 123". Im doing the following
val sortedList = data.sortedWith(compareBy({ it.listId }, { it.name }))
This sorts the listId correctly but the names is sorted alphabetically so the numbers go 1, 13, 2, 3. How am I able to sort both the categories but make the "name" also be sorted numerically?
I think
val sortedList = data.sortedWith(compareBy(
{ it.listId },
{ it.name.substring(0, it.name.indexOf(' ')) },
{ it.name.substring(it.name.indexOf(' ') + 1).toInt() }
))
will work but it is not computationally efficient because it will call String.indexOf() many times.
If you have a very long list, you should consider making another list whose each item has String and Int names.

Linq Query Filter from two lists where row differs

Not sure how to formulate this Linq query.
I have two lists, each of which contains HashCheck objects:
class HashCheck
{
public string Id {get; set;}
public string Hash {get; set;}
}
So, given
List<HashCheck> list1;
List<HashCheck> list2;
I need a query that will result in a list having rows where the Ids of the rows matches, but the Hash does not.
So for example
List1 =
{1, 12345,
2, 34323,
3, 34083,
4, 09887}
List2 =
{1, 00001, << matching id, not matching hash
2, 34323,
3, 11112, << matching id, not matching hash
4, 09887
5, 98845}
ResultList =
{1, 00001,
3, 11112}
NOTE: in List2, there is an extra row, it would be a bonus if this were included in the ResultList. But I know how to do that in a separate query if necessary.
Thanks for any help.
try this code:
var list3 = (from i in list1
from j in list2
where i.Id == j.Id && i.Hash != j.Hash
select new HashCheck() { Id = j.Id, Hash = j.Hash
}).ToList<HashCheck>();
You can use join. something like below code:
var list3 = (from i in list1
join j in list2 on i.Id equals j.Id
where i.Hash != j.Hash
select new HashCheck() { Id = j.Id, Hash = j.Hash
}).ToList<HashCheck>();
It looks like you want your result to contain the HashCheck objects from list2, which would simply mean:
var ans = list2.Where(hc2 => !list1.Any(hc1 => hc1.Id == hc2.Id && hc1.Hash == hc2.Hash));
e.g. return all list2 elements without a list1 element that matches in both Id and Hash.
If list1 (and/or list2) is very large and performance is a consideration, you can convert list1 to a Dictionary and do lookups against that:
var list1map = list1.ToDictionary(hc1 => hc1.Id, hc1 => hc1.Hash);
var ans2 = list2.Where(hc2 => !list1map.TryGetValue(hc2.Id, out var hash1) || hash1 != hc2.Hash);
Another alternative would be to implement Equals/GetHashCode for your class and then you can use LINQ Except.
Add the following methods to your class:
public override bool Equals(object other) => (other is HashCheck hco) ? Id == hco.Id && Hash == hco.Hash : false;
public override int GetHashCode() => (Id, Hash).GetHashCode();
Now the computation is simple:
var ans3 = list2.Except(list1);
NOTE: Implementing Equals/GetHashCode in this way can be problematic if your HashCode objects are not treated as immutable. Some collection classes really won't like it if the hash code of an object already stored in them changes.
Also, it would be best practice to implement operator== and operator!= as well and possibly IEquatable.

Using Java 8 streams for aggregating list objects

We are using 3 lists ListA,ListB,ListC to keep the marks for 10 students in 3 subjects (A,B,C).
Subject B and C are optional, so only few students out of 10 have marks in those subjects
Class Student{
String studentName;
int marks;
}
ListA has records for 10 students, ListB for 5 and ListC for 3 (which is also the size of the lists)
Want to know how we can sum up the marks of the students for their subjects using java 8 steam.
I tried the following
List<Integer> list = IntStream.range(0,listA.size() -1).mapToObj(i -> listA.get(i).getMarks() +
listB.get(i).getMarks() +
listC.get(i).getMarks()).collect(Collectors.toList());;
There are 2 issues with this
a) It will give IndexOutOfBoundsException as listB and listC don't have 10 elements
b) The returned list if of type Integer and I want it to be of type Student.
Any inputs will be very helpful
You can make a stream of the 3 lists and then call flatMap to put all the lists' elements into a single stream. That stream will contain one element per student per mark, so you will have to aggregate the result by student name. Something along the lines of:
Map<String, Integer> studentMap = Stream.of(listA, listB, listC)
.flatMap(Collection::stream)
.collect(groupingBy(student -> student.name, summingInt(student -> student.mark)));
Alternatively, if your Student class has getters for its fields, you can change the last line to make it more readable:
Map<String, Integer> studentMap = Stream.of(listA, listB, listC)
.flatMap(Collection::stream)
.collect(groupingBy(Student::getName, summingInt(Student::getMark)));
Then check the result by printing out the studentMap:
studentMap.forEach((key, value) -> System.out.println(key + " - " + value));
If you want to create a list of Student objects instead, you can use the result of the first map and create a new stream from its entries (this particular example assumes your Student class has an all-args constructor so you can one-line it):
List<Student> studentList = Stream.of(listA, listB, listC)
.flatMap(Collection::stream)
.collect(groupingBy(Student::getName, summingInt(Student::getMark)))
.entrySet().stream()
.map(mapEntry -> new Student(mapEntry.getKey(), mapEntry.getValue()))
.collect(toList());
I would do it as follows:
Map<String, Student> result = Stream.of(listA, listB, listC)
.flatMap(List::stream)
.collect(Collectors.toMap(
Student::getName, // key: student's name
s -> new Student(s.getName(), s.getMarks()), // value: new Student
(s1, s2) -> { // merge students with same name: sum marks
s1.setMarks(s1.getMarks() + s2.getMarks());
return s1;
}));
Here I've used Collectors.toMap to create the map (I've also assumed you have a constructor for Student that receives a name and marks).
This version of Collectors.toMap expects three arguments:
A function that returns the key for each element (here it's Student::getName)
A function that returns the value for each element (I've created a new Student instance that is a copy of the original element, this is to not modify instances from the original stream)
A merge function that is to be used when there are elements that have the same key, i.e. for students with the same name (I've summed the marks here).
If you could add the following copy constructor and method to your Student class:
public Student(Student another) {
this.name = another.name;
this.marks = another.marks;
}
public Student merge(Student another) {
this.marks += another.marks;
return this;
}
Then you could rewrite the code above in this way:
Map<String, Student> result = Stream.of(listA, listB, listC)
.flatMap(List::stream)
.collect(Collectors.toMap(
Student::getName,
Student::new,
Student::merge));

Elasticsearch get elements fulfilling two conditions

I have in db elements with following structure:
{
"id": 324214,
"modDate": "2014-10-01",
"otherInfo": {
..
..
}
}
Let's suppose that I have list of pairs [id, modDate]:
Map<String, String> idAndModDate
which contains f.e (324214, "2014-10-01"), (3254757, "2015-10-04")..
Now, I would like to use Java Api Elasticsearch QueryBuilder to build Query which in result give me list of all "ids" which are present in system but for who modDate is different as given.
Suppose that I have in database elements with following id/date pairs:
id, date
1, 2015-01-01
2, 2014-03-02
3, 2000-01-22
4, 2020-09-01
Now, I want to create query for
Map with following data:
Map<String, String> idDataPairs =[
(1, 2015-01-01)
(2, 2014-03-03)
(3, 2000-01-22)
(7, 2020-09-01)]
now I want create function like
List<String> ids = search(Map<String, String>) {
QueryBuilder.(sth).(sth) <--- thats what I'm asking about
}
which will return ids: 1, 3 because those ids exist in DB and dates from query are equal to dates in db respectively.
This is what you are looking for, more or less.
//build the test data in the map
Map<String, String> idDataPairs = new HashMap<String, String>();
idDataPairs.put("1", "2015-01-01");
idDataPairs.put("2", "2014-03-03");
idDataPairs.put("3", "2000-01-22");
idDataPairs.put("4", "2020-09-01");
//construct the query
BoolQueryBuilder should = QueryBuilders.boolQuery();
for(String id : idDataPairs.keySet()){
BoolQueryBuilder bool = QueryBuilders.boolQuery();
bool.must(QueryBuilders.termQuery("id", id));
bool.must(QueryBuilders.termQuery("modDate", idDataPairs.get(id)));
should.should(bool);
}
should.minimumNumberShouldMatch(1);
What i am doing is this:
For each of the Pairs, i am constructing a BoleanQuery called bool. This boolean query has two must conditions, that both the id and the date MUST match the document.
After constructing one bool Boolean Query, I add it to a parent BooleanQuery as well. This time, i say that the inner bool query should match, but its not required to. The final line says that at least one of these queries should match, if we want the document to match.
This structure is easier to understand, because must functions like AND and should functions like OR, but another way to do this is to use a TermsQuery, where we construct several TermsQuerys, and then add them to another parent BooleanQuery using should.
So, for the data
id, date
1, 2015-01-01
2, 2014-03-02
3, 2000-01-22
4, 2020-09-01
the above code will return the documents with ids 1,2,3

How to get difference sum of between keys in a Dictionary object C#

I have one dictionary with key as some integer value which is an index of array and a string in value field.I need the sum of difference between the keys in the same dictionary
Dictionary<int, string> foo = new Dictionary<int, string>()
{
{0,"text1"},
{2,"text2"},
{6,"text3"},
{8,"text4"}
};
Output
(8-6)=2
(6-2)=4
(2-0)=2
Total: 2+4+2=8
Thus all other key values will be both added and subtracted during totals calculations, you need only max and min here:
var total = foo.Max(kvp => kvp.Key) - foo.Min(kvp => kvp.Key);
Should be the difference between the Max Key and Min Key.
int total = foo.Keys.Max() - foo.Keys.Min();

Resources