I have a list of items. I would like to group this list by 3 attributes (category, color, city).
Example :
ID;Category;Color;City;Name
1;1;Red,Rome,Shoe A
2;1;Red,Paris,Shoe C
3;1;Green,Rome,Scarf
4;1;Red,Rome,Shoe B
5;2;Red,Rome,Scarf
6;1;Red,Rome,Scarf
7;1;Green,Rome,Shoe
So that in the end I have a list like the following :
1;Red;Rome => ID (1,4, 6)
1;Red;Paris => ID(2)
1;Green,Rome => ID(3,7)
2;Red;Rome => ID(5)
I know how to group the list by an attribute :
Map<Category, List<Items>> _items = items.stream().collect(Collectors.groupingBy(item -> item.getCategory()));
But how do I do this with multiple attributes?
You need to create an object that will serve as a key that identifies uniquely a tuple made of the 3 values Category, Color and City. Something like this :
public class Key {
private final int category;
private final String color;
private final String city;
public Key(int category, String color, String city) {
this.category = category;
this.color = color;
this.city = city;
}
//Getter
public static Key fromItem(Item item) {
//create the key from the item
return new Key(item.getCategory(),...)
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
return category == key.category && color.equals(key.color) && city.equals(key.city);
}
#Override
public int hashCode() {
return Objects.hash(category, color, city);
}
}
You can then use your line of code with a simple modification :
Map<Key, List<Items>> _items = items.stream().collect(Collectors.groupingBy(Key::fromItem));
Related
We have two lists one is of type Question and the other of type Tag.
The Question class has those attributes
private String id;
private String header;
private String content;
private List<Tag> tags;
private Long timeStamp;
The Question list has all questions in it and the Tag list has all tags in it. We wanna check if One question contains any tag of the tag list. I want to do this for all questions.
With question.getTags, I get the list of tags.
The Tag class has an attribute called counter.
I will give some pseudocode to actually show you what I wanna do
List<Tag> allTags = ...
List<Question> allQuestions = ...
Map<Tag,Integer> map = new Hashmap<>();
if(one question contains any tag of allTags) {
tag.setCounter(counter+1);
map.put(tag,tag.getCounter);
}
In the end I wanna have a map where the key is the tag and the value the counter of that tag.
How can I actually do this?
EDIT here is my Tag.java
#NoArgsConstructor
#Data
#Getter
#AllArgsConstructor
#Setter
#Document(collection = "tag")
public class Tag {
private String id;
private String name;
private Long timeStamp;
public Tag(String name) {
this.id = UUID.randomUUID().toString();
this.name = name;
this.timeStamp = Instant.now().getEpochSecond()*1000;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(getId(), tag.getId());
}
#Override
public int hashCode() {
return Objects.hash(getId());
}
}
I think a counter attribute in the Tag class to count how often a Tag occurs in a list of questions is a design error. You don't need such a counter there.
A possibly plausible example: Imagine you have a class Student and a class Course. To keep track of how many students are in a course, there is no need for a counter in the Student class. In the same way a class Tag should only contain the attributes of a tag. That said, you can achieve your goal without the counter with either one of the two following approachs (provided you have java 8 or higher and your Tag class overrides the equals and hashcode methods):
Approach 1 using Streams & Collectors.groupingBy
Map<Tag,Long> map = allQuestions.stream()
.flatMap(q -> q.getTags().stream())
.filter(allTags::contains)
.collect(Collectors.groupingBy(Function.identity(),Collectors.counting()));
Approach 2 using List.forEach & Map.compute
Map<Tag, Integer> map2 = new HashMap<>();
allQuestions.forEach(q -> {
q.getTags()
.stream()
.filter(allTags::contains)
.forEach(t -> map2.compute(t, (k, v) -> v == null ? 1 : v + 1));
});
Or using a traditional for-loop and if-else block
Map<Tag, Integer> map3 = new HashMap<>();
for (Question q : allQuestions) {
for (Tag t : q.getTags()) {
if (allTags.contains(t)) {
if (map3.containsKey(t)) {
map3.put(t, map3.get(t) + 1);
} else {
map3.put(t, 1);
}
}
}
}
I think this will work perfectly
int count=0;
for(Question q : allQuestions){
for(Tag t : q.getTags()){
if(allTags.contains(t)){
count=map.containsKey(t) ? map.get(t)+1 : 0;
map.put(t,count);
}
}
}
I need to filter elements and then sort based on certain column. Post that I would need to find the unique entries based on combination of columns. Since it is file processing, pipe(|) is used as delimiter to denote the column value.
String s1= "12|Thor|Asgaurd|1000000|Avenger|Active"
String s2= "234|Iron man|New York|9999999|Avenger|Active"
String s3= "420|Loki|Asgaurd|||Inactive"
String s4= "12|Thor|Asgaurd Bank|1000000|Avenger HQ|Active"
Data first needs to be filtered based on the Active/Inactive status. Then it needs to be sorted based on 4th column. Lastly, the uniqueness needs to be maintained by combining column 1,2,3.
Expected Output =
"234|Iron man|New York|9999999|Avenger|Active"
"12|Thor|Asgaurd|1000000|Avenger|Active"
Creating a model class and parsing the string is the way to go, but if for some reaseon you don't want to do that you can do it this way:
import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.stream.Stream;
List<String> result = Stream.of(s1, s2, s3, s4)
.filter(s -> s.split("\\|")[5].equals("Active"))
.sorted(Comparator.comparing(e -> e.split("\\|")[4]))
.collect(Collectors.toList());
First of all you should create an Object which represents your String data. Something like this:
public class MyObject {
private int id;
private String name;
private String location;
private Integer value;
private String category;
private String state;
public MyObject(String entry) {
String[] parts = entry.split("\\|");
if (parts.length != 6) {
throw new IllegalArgumentException("entry has not 6 parts");
}
id = Integer.parseInt(parts[0]);
name = parts[1];
location = parts[2];
try {
value = Integer.parseInt(parts[3]);
} catch (NumberFormatException ignored) {
}
category = parts[4];
state = parts[5];
}
// getters
#Override
public String toString() {
return String.join("|", String.valueOf(id), name, location, String.valueOf(value), category, state);
}
}
With this you can create a Stream of objects from your Strings and to the filter, sort and distinct operations afterwards:
Collection<MyObject> result = Stream.of(s1, s2, s3, s4)
.map(MyObject::new)
.filter(o -> "Active".equals(o.getState()))
.sorted(Comparator.comparing(MyObject::getValue).reversed())
.collect(Collectors.toMap(o -> Arrays.asList(o.getId(), o.getName()),
Function.identity(), (o1, o2) -> o1, LinkedHashMap::new))
.values();
result.forEach(System.out::println);
After the map operation you filter the values by state and sort them by column 4 (value in my case). At the end you collect all the values in a map for the distinct operation. Add all values you need distinction for to the Arrays.asList(). As values the map takes all the original values (Function.identity()). For duplicates we keep the first value ((o1, o2) -> o1) and we are using a LinkedHashMap to keep the order of the items. At the end wee use only the values of the map.
If you need a List instead of a Collection use new ArrayList(result).
The result will be this:
234|Iron man|New York|9999999|Avenger|Active
12|Thor|Asgaurd|1000000|Avenger|Active
It seems like you're unable to filter while everything is string only.
Try this,
create a new model class which can hold your columns.
Ex:
class MyData{
private String name;
private String city;
private String distance;
private String organization;
private String status;
//And create Getter Setter method for all above fields.
}
Now came to your main class where you can play with your code stuff.
Map<MyData> map = new HashMap<MyData>();
MyData myData = new MyData();
myData.setName("Thor");
myData.setCity("Asgaurd");
myData.setDistance("1000000");
myData.setOrganization("Avenger");
myData.setStatus("Active");
map.put(12, myData);
//Same thing for all other data (note: use the loop for data insertion in map)
Map<String, MyData> sorted = map.entrySet().stream().sorted(comparingByValue()).collect(toMap(e -> e.getKey(), e -> e.getValue().getName(), (e1, e2) -> e2,LinkedHashMap::new));
System.out.println("map after sorting by values: " + sorted);
You can solve your task this way:
Firstly, just create POJO(Plain Old Java Object) and override the toString() method.
class MarvelPerson {
private Integer id;
private String name;
private String origin;
private Integer point = null;
private String faction;
private String status;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public Integer getPoint() {
return point;
}
public void setPoint(Integer point) {
this.point = point;
}
public String getFaction() {
return faction;
}
public void setFaction(String faction) {
this.faction = faction;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(id);
builder.append("|");
builder.append(name);
builder.append("|");
builder.append(origin);
builder.append("|");
if(point != null) {
builder.append(point);
}
builder.append("|");
if(faction != null) {
builder.append(faction);
}
builder.append("|");
builder.append(status);
return builder.toString();
}
}
Then, you should write the parser from string to MarvelPerson. Side note: Carefully, my implementation is pretty basic, and I suppose it should be modified because I may not have foreseen some corner cases.
class PersonParser {
static MarvelPerson parse(String data) {
MarvelPerson person = new MarvelPerson();
String[] array = data.split("\\|", -1);
person.setId(Integer.parseInt(array[0]));
person.setName(array[1]);
person.setOrigin(array[2]);
if(!array[3].isEmpty()) {
person.setPoint(Integer.parseInt(array[3]));
}
if(!array[4].isEmpty()) {
person.setFaction(array[4]);
}
person.setStatus(array[5]);
return person;
}
}
And then your solution:
public class Test {
public static void main(String[] args) {
List<MarvelPerson> list = new ArrayList<>();
list.add(PersonParser.parse("12|Thor|Asgaurd|1000000|Avenger|Active"));
list.add(PersonParser.parse("234|Iron man|New York|9999999|Avenger|Active"));
list.add(PersonParser.parse("420|Loki|Asgaurd|||Inactive"));
list.add(PersonParser.parse("12|Thor|Asgaurd Bank|1000000|Avenger HQ|Actie"));
list.stream()
.filter(marvelPerson -> marvelPerson.getStatus().equals("Active"))
.sorted((o1, o2) -> o1.getPoint() <= o2.getPoint() ? 1 : -1)
.forEach(marvelPerson -> {
System.out.println(marvelPerson.toString());
});
}
}
The output to be printed:
234|Iron man|New York|9999999|Avenger|Active
12|Thor|Asgaurd|1000000|Avenger|Active
I have a list of institutionUserConnections and I will have the users.
Therefore I iterate over the institutionUserConnections list.
It can be that a user is in more than one institution.
Therefore I have 2 different user object - but it is the same user.
My question now would be how to get a unique list of users?
final List<User> users = new ArrayList<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnections) {
final User user = institutionUserConnection.getUser();
users.add(user);
}
[EDIT]
Actually it is hard to explain. I have also used a Set but with no success.
Here is the whole code I use:
final List<InstitutionUserConnection> institutionUserConnectionsOfUser = institutionUserConnectionService
.getActiveInstitutionUserConnectionsByUser(foundedUser);
final List<InstitutionUserConnection> institutionUserConnections = new ArrayList<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnectionsOfUser) {
final Institution institution = institutionUserConnection.getInstitution();
institutionUserConnections
.addAll(institutionUserConnectionService.getActiveInstitutionUserConnectionsByInstitution(institution));
}
final List<User> users = new ArrayList<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnections) {
final User user = institutionUserConnection.getUser();
users.add(user);
}
Maybe someone have another hint how I can solve this issue.
as suggested in the comments user HashSet, which will retain only unique reference of the object.
I could not understand your question clearly. I have design a way to get around your issue I hope it helps
class InstituteUserConnection holds insitute name and a list of Users.
listOfInsituteUserConnection will hold InstituteUserConnection.
Iterate over the listOfInsituteUserConnection and get Users and put them into Set<User>. you can check the count in the User to
cross verify and print them out.
FYI HashSet works on the principle of hashing check link for info.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
class User {
String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public int hashCode() {
return this.name.hashCode();
}
#Override
public boolean equals(Object obj) {
if (!(obj instanceof User)) {
return false;
}
User objUser = (User) obj;
System.out.println("in equals");
return this.name.equals(objUser.getName());
}
#Override
public String toString() {
return this.name;
}
}
public class InstitutionUserConnections {
String institutionName;
List<User> users;
public InstitutionUserConnections(String institutionName, List<User> users) {
this.users = users;
this.institutionName = institutionName;
}
public void addUser(User user) {
this.users.add(user);
}
public List<User> getUsers() {
return this.users;
}
public static void main(String[] args) {
User user1 = new User("user1");
User user2 = new User("user2");
List<InstitutionUserConnections> listOfInstitutionUserConnections = new ArrayList<InstitutionUserConnections>();
List<User> institute1User = Arrays.asList(new User[] { user1, user2 });
InstitutionUserConnections institute1 = new InstitutionUserConnections(
"institute1", institute1User);
List<User> institute2User = Arrays.asList(new User[] { user1 });
InstitutionUserConnections institute2 = new InstitutionUserConnections(
"institute1", institute2User);
listOfInstitutionUserConnections.add(institute1);
listOfInstitutionUserConnections.add(institute2);
Set<User> listOfUniqueUsers = new HashSet<User>();
for (InstitutionUserConnections institute : listOfInstitutionUserConnections) {
listOfUniqueUsers.addAll(institute.getUsers());
}
System.out.println("No of User in Set<User> : "
+ listOfUniqueUsers.size());
for (User user : listOfUniqueUsers) {
System.out.println(user);
}
}
}
Try this
final HashSet<User> users = new HashSet<>();
for (final InstitutionUserConnection institutionUserConnection : institutionUserConnections) {
final User user = institutionUserConnection.getUser();
users.add(user);
}
// iterate users to check unique
I would suggest to override your Equals method and use data structure you want to use,if you are eager to use List/ ArrayList or any set as well. Apache Commons Collection provide a great one line code to provide unique list.
Add Maven dependency or download jar :
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
Then simply use below line of code:
SetUniqueList uniqueList = SetUniqueList.decorate(<<Your list>>);
and override Equals and HashCode:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
YourClass tester = (YourClass) o;
if (user!= null ? !user.equals(tester.user) : tester.user!= null) return false;
return true;
}
#Override
public int hashCode() {
int result = user != null ? user.hashCode() : 0;
result = 31 * result + (anotherField!= null ? anotherField.hashCode() : 0);
return result;
}
I have a Person class:
class Person {
private String name;
private String job;
... ...
}
I have a person list :
personList.put(new Person("Pete","doctor"))
personList.put(new Person("pete","doctor"))
personList.put(new Person("Aaron","doctor"))
personList.put(new Person("Vivian","doctor"))
personList.put(new Person("Mary","teacher"))
I want to display the person list grouping by the job and both name and job are in alphanumeric order(case insensitive) as the following data formatting.
doctor
>>Aaron
>>Pete
>>pete
>>Vivian
teacher
>>Mary
Currently, I'm doing this:
public enum PersonComparator implements Comparator<Person> {
NAME_JOB_INSENSTIVE_ASC {
#Override
public int compare(final Person obj1, final Person obj2) {
String compareObj1 = obj1.getName();
String compareObj2 = obj2.getName();
int compareValue = compareObj1.compareTo(compareObj2);
int compareValueIgnoreCase = compareObj1.compareToIgnoreCase(compareObj2);
if(compareValueIgnoreCase == 0) {
return compareValue >= 0 ? 1 : -1;
} else {
return compareValueIgnoreCase>= 0 ? ++compareValueIgnoreCase
: --compareValueIgnoreCase;
}
}
}
}
ListMultimap<String, Person> personTableList = Multimaps.index(personList,
new Function<Person, String>() {
#Override
public String apply(Person person) {
return person.getJob();
}
});
TreeMultimap<String, Person> personTableTree = TreeMultimap.create(Ordering.natural(),
PersonComparator.NAME_JOB_INSENSTIVE_ASC);
personTableTree.putAll(personTableList);
model.addAttribute(RequestParameter.Person_TABLE, personTableTree.asMap());
I think the PersonComparator is not easy to read and understand. Any better idea by directly using Guava API? Thanks.
You might like to use a ComparisonChain
From what I gather, you want to first compare case-insensitively, and then if that is equal, perform the comparison in a case-sensitive way.
public int compare(final Person p1, final Person p2)
{
return ComparisonChain.start()
.compare(p1.getName().toLowerCase(), p2.getName().toLowerCase())
.compare(p1.getName(), p2.getName())
.result();
}
I have a table named Employee which has a complex primary key i.e. a combination of 3 of its column
firstName : String
SecondName : String
bossId : foreingKey of other table named Boss ( auto generated database sequence)
And here is my code:
#Entity
#Table(name = "Employee")
#org.hibernate.annotations.Entity(optimisticLock = OptimisticLockType.ALL, dynamicUpdate = true)
public class Employee {
private EmployeePk employeePk;
private int age;
private String status;
#EmbeddedId
public EmployeePk getEmployeePk() {
return employeePk;
}
public void setEmployeePk(EmployeePk employeePk) {
this.employeePk = employeePk;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null ||
!(o instanceof Employee)) {
return false;
}
Employee other
= (Employee)o;
// if the id is missing, return false
if (getEmployeePk() == null) return false;
if( getEmployeePk().equals(other.getEmployeePk())) {
return true;
} else {
return false;
}
}
#Override
public int hashCode() {
if (getEmployeePk() != null) {
return getEmployeePk().hashCode();
} else {
return super.hashCode();
}
}
}
#Embeddable
public class EmployeePk implements Serializable{
private static final long serialVersionUID = -7827123517392541880L;
private String firstName;
private String secondName;
private Boss boss;
#ManyToOne
#JoinColumn(name = "boss_id",insertable= false, updatable= false)
public Boss getBoss() {
return boss;
}
public void setBoss(
Boss boss) {
this.boss = boss;
}
/* setters and getters of all with #column annotation */
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof EmployeePk)) {
return false;
}
EmployeePk other = (EmployeePk) obj;
if( getfirstname() != null &&
getFirstName().equals(other.getFirstName()) &&
getSecondName() !=null &&
getSecondName().equals(other.getSecondName()) &&
getBoss() != null &&
getBoss().getId() == other.getBoss().getId())
return true;
return false;
}
#Override
public int hashCode() {
if (getFirstName() != null &&
getSecondName() != null &&
getBoss() != null) {
return getFirstName().hashCode() +
getSecondName().hashCode();
} else {
return super.hashCode();
}
}
}
Now , everything is running fine and I am able to create/update/delete the database rows in Employee table.
But when i am trying to update same rows in one single transaction I am getting this exception :
org.hibernate.event.def.AbstractFlushingEventLis
tener: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1792)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2435)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:64)
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:996)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1141)
at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
at org.hibernate.impl.AbstractQueryImpl.uniqueResult(AbstractQueryImpl.java:835)
I had similar issue with another table named Contractor but was able to solve this issue by overriding its equals and hashCode methods.
The thing is that in that table there was only one primary key named "id" which was a database auto generated sequence and hence there
was no concept of EmbeddedId there.
I am not sure where I am going wrong here. I have spent several days in fixing this issue and followed several links for example: http://onjava.com/pub/a/onjava/2006/09/13/dont-let-hibernate-steal-your-identity.html?page=1
A couple of thoughts:
1) Using optimisticLock = OptimisticLockType.ALL makes me suspicious. I've never used this myself, but I suspect it might have something to do with your error.
2) I don't think the equals method handles the case where any of the attributes are null. It will always return false, even if both instances are identical.
3) Hibernate really prefers when you use dedicated id (and version if required) columns rather than natural keys. Support for composite/natural keys seems to be cumbersome and buggy in my experience.