I have a list of Student objects studentList as below.
Student1 = new Student("Student1", 10);
Student2 = new Student("Student2", 11);
Student {
String name;
int age;
}
I want to do a filter on the list and get the age of the particular Student.
I have tried the same as below using java 8 streams.
int age = studentList.stream()
.filter(x -> x.getName.equals("Student1"))
.findFirst()
.get()
.getAge();
But when I pass a student name which is not in the list, say Student5, it will throw a null pointer exception. For that I have given the null check as below
Student s = studentList.stream()
.filter(x -> x.getName.equals("Student1"))
.findFirst()
.get();
if(s != null){
age = s.getAge();
}
Instead of splitting this null check like this, is it possible to accomodate the null check in the stream().filter() code itself?
Stream#findFirst returns Optional<T> which value you can remap if present:
int age = studentList.stream()
.filter(x -> "Student1".equals(x.getName()))
.findFirst()
.map(Student::getAge)
.orElse(0);
First, reverse your test just in case that getName is returning a null. The equals method should check for null before doing the comparison. And then add a conditional in case there is not match for the student.
int age = studentList.stream()
.filter(x -> "Student1".equals(x.getName()))
.mapToInt(Student::getAge).findFirst().orElse(0);
Modified my answer based on an excellent suggestion by Holger to reduce boxing overhead.
Related
I'm looking for a solution on how to assign a random UUID to a key only on its first occurrence in a stream.
Example:
time key value assigned uuid
| 1 A fff17a1e-9943-11eb-a8b3-0242ac130003
| 2 B f01d2c42-9943-11eb-a8b3-0242ac130003
| 3 C f8f1e880-9943-11eb-a8b3-0242ac130003
| 1 X fff17a1e-9943-11eb-a8b3-0242ac130003 (same as above)
v 1 Y fff17a1e-9943-11eb-a8b3-0242ac130003 (same as above)
As you can see fff17a1e-9943-11eb-a8b3-0242ac130003 is assigned to key "1" on its first occurrence. This uuid is subsequently reused on its second and third occurrence. The order doesn't matter, though. There is no seed for the generated uuid either.
My idea was to use a leftJoin() with a KStream and a KTable with key/uuid mappings. If the right side of the leftJoin is null I have to create a new UUID and add it to the mapping table. However, I think this does not work when there are several new entries with the same key in a short period of time. I guess this will create several UUIDs for the same key.
Is there an easy solution for this or is this simply not possible with streaming?
I don't think you need a join in your use case because joins are to merge to different streams that arrive with equal IDs. You said that you receive just one stream of events. So, your use case is an aggregation over one stream.
What I understood of your question is that you receive events: A, B, C, ... Then you want to assign some ID. You say that the ID is random. So, this is very uncertain. If it is random how would you know that A -> fff17a1e-9943-11eb-a8b3-0242ac130003 and X -> fff17a1e-9943-11eb-a8b3-0242ac130003 (the same). I suppose that you might have a seed to generate this UUID. And then you create a key based also on this seed.
I suggest you start with this sample of word count. then on the first map:
.map((key, value) -> new KeyValue<>(value, value))
you replace it with your map function. Something like this:
.map((k, v) -> {
if (v.equalsIgnoreCase("A")) {
return new KeyValue<String, ValueWithUUID>("1", new ValueWithUUID(v));
} else if (v.equalsIgnoreCase("B")) {
return new KeyValue<String, ValueWithUUID>("2", new ValueWithUUID(v));
} else {
return new KeyValue<String, ValueWithUUID>("0", new ValueWithUUID(v));
}
})
...
class ValueWithUUID {
String value;
String uuid;
public ValueWithUUID(String value) {
this.value = value;
// generate your UUID based on the value. It is random, but as you show in your question it might have a seed.
this.uuid = generateRandomUUIDWithSeed();
}
public String generateRandomUUIDWithSeed() {
return "fff17a1e-9943-11eb-a8b3-0242ac130003";
}
}
Then you decide if you want to use a windowed aggregation, every 30 seconds for instance. Or a non-windowing aggregation that updates the results for every event that arrives. Here is one nice example.
You can aggregate the raw stream as ktable, in the processing, generate or reuse the uuid; then use the stream of ktable.
final KStream<String, String> streamWithoutUUID = builder.stream("topic_name");
KTable<String, String> tableWithUUID = streamWithoutUUID.groupByKey().aggregate(
() -> "",
(k, v, t) -> {
if (!t.startsWith("uuid:")) {
return "uuid:" + "call your buildUUID function here" + ";value:" + v;
} else {
return t.split(";", 2)[0] + ";value:" + v;
}
},
Materialized.<String, String, KeyValueStore<Bytes, byte[]>>as("state_name")
.withKeySerde(Serdes.String()).withValueSerde(Serdes.String()));
final KStream<String, String> streamWithUUID = tableWithUUID.toStream();
List<CustomerDetails> customerDetailsList = repo.getCustomerDetails();
Set<String> combinedNamesList = new HashSet<>();
customerDetailsList.forEach(i -> {
combinedNamesList .add((i.getFirstName() != null ? i.getFirstName().toLowerCase(): "") + (i.getLastName() != null ? i.getLastName().toLowerCase(): ""));
});
I would like to create the combinedNamesList in one operation using streams. Each CustomerDetails object has properties for a firstName and LastName. I would like to combine the two properties into a single String in an array such as:
{BobSmith, RachelSnow, DavidJohnson}
Stream the list and filter all customer objetcs having valid firstname and lastname, and then combine the name using String.format
List<String> combinedNamesList = repo.getCustomerDetails()
.stream()
.filter(cust->cust.getFirstName()!=null && cust.getLastName()!=null)
.map(cust->String.format("%s%s",cust.getFirstName(),cust.getLastName()))
.collect(Collectors.toList());
Adding to Deadpool's answer, thinking this might help someone too.
Person p = new Person("Mohamed", "Anees");
Person p1 = new Person("Hello", "World");
Person p2 = new Person("Hello", "France");
System.out.println(
Stream.of(p, p1, p2)
.map(person -> String.join(" ", person.getFirstName(), person.getLastName()))
.collect(Collectors.toSet()));
Here String.join() is used to concatenate names. And, this also produces a more sensible output than the one you are expecting
[Mohamed Anees, Hello World, Hello France]
If you really need names without space, you can replace " " in String.join() delimiter to ""
You can add filter() in the Stream for null checks before converting to lowercase.
Given a list of Person:
class Person {
private Integer id;
private Integer age;
private String name;
private Long lat;
private Long lont;
private Boolean hasComputer;
...
I'd like to return the top 5 persons given a set of Criterias such as Age between 30 and 32, that have a computer.
I'd like to try to match all the criterias first but if it doesn't work, try to match any of these.
I have in mind a similar way to do it such as a fulltextsearch would do with a ranking system? But I'm new to Stream so still looking for solution on how to do it.
List<Person> persons = service.getListPersons();
persons.stream().filter(p -> p.getAge.equals(age) && p.hasComputer().equals(true)).allMatch()
Any idea? Thanks!
EDIT:
Maybe I could create a predicate such as
Predicate predicate = p -> p.getAge() < 30 && e.name.startsWith("A");
and try first to match all the criteria and if not possible, try to match any:
Persons.steam().allMatch(predicate).limit(5);
Person.steam().anyMatch(predicate).limit(5);
Try this out,
List<Person> filteredPeople = persons.stream()
.filter(p -> p.getAge() > 30)
.filter(p -> p.getAge() < 32)
.filter(p -> p.getHasComputer())
.limit(5).collect(Collectors.toList());
Notice that you may add additional filter predicates as needed. This is just a template to get your work done.
Or else if you have some dynamic number of Predicates passed by some external client you can still do it like so.
Predicate<Person> ageLowerBoundPredicate = p -> p.getAge() > 30;
Predicate<Person> ageUpperBoundPredicate = p -> p.getAge() < 32;
Predicate<Person> hasComputerPred = p -> p.getHasComputer();
List<Predicate<Person>> predicates = Arrays.asList(ageLowerBoundPredicate, ageUpperBoundPredicate,
hasComputerPred);
List<Person> filteredPeople = persons.stream()
.filter(p -> predicates.stream().allMatch(f -> f.test(p)))
.limit(5).collect(Collectors.toList());
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));
in this example code
public Company GetCompanyById(Decimal company_id)
{
IQueryable<Company> cmps = from c in db.Companies
where c.active == true &&
c.company_id == company_id
select c;
return cmps.First();
}
How should I handle if there is no data in cmps?
cmps will never be null, so how can I check for non existing data in a LINQ Query?
so I can avoid this
'cmps.ToList()' threw an exception of type ... {System.NullReferenceException}
when transforming it into, for example, a List
GetCompanyById(1).ToList();
Do I always need to wrap it up in a try catch block?
You can use Queryable.Any() (or Enumerable.Any()) to see if there is a member in cmps. This would let you do explicit checking, and handle it however you wish.
If your goal is to just return null if there are no matches, just use FirstOrDefault instead of First in your return statement:
return cmps.FirstOrDefault();
What about applying .Any or .Count() ?
Here's an example on MSDN
List<int> numbers = new List<int> { 1, 2 };
bool hasElements = numbers.Any();
Console.WriteLine("The list {0} empty.",
hasElements ? "is not" : "is");
Or just use the ?: operator
return myExample.Any() ? myExample.First() : null;
This will return the first one if there is one, or null if there isn't:
return (from c in db.Companies
where c.active == true &&
c.company_id == company_id
select c).FirstOrDefault();
Try return cmps.Count()==0?null:cmp.First()
That way if it is null it will simply return a null Company and if its not then it will return the first one in the list.
Check out http://en.wikipedia.org/wiki/Ternary_operation
var context = new AdventureWorksLT2008Entities();
var cust = context.Customers.Where(c => c.CustomerID == 1);
if (cust.Any())
{
Customer c = cust.First();
}