grouping objects according to some fields - java

i have a List of an Object, with the following characteristics:
Class myObject{
String gender;
String state;
int quantity;
int Salary;
}
List<myObject> myList=new ArrayList<Object>;
As input of the List, i have the following:
and as Output, i want to keep only one occurrence of the object with the same gender and the same state, in the same time sum the quantity and the salsary correspanding, like the following:
my question is how can i loop through myList, find objects with the same gender and the same state,keep only one occurence of them, and sum the quantity and the salary correspanding ??
in other words, for the first and second line (same gender, same state), keep only one line and sum the correspanding quantity and salary

private Collection<MyObject> aggregate(List<MyObject> objects) {
Map<String, MyObject> map = new HashMap<String, MyObject>();
for (MyObject current : objects) {
String key = String.format("%s:%s", current.gender, current.state);
MyObject aggregated = map.get(key);
if (aggregated == null) {
aggregated = new MyObject();
aggregated.gender = current.gender;
aggregated.state = current.state;
map.put(key, aggregated);
}
aggregated.quantity += current.quantity;
aggregated.salary += current.salary;
}
return map.values();
}

Equivalent with Java 8:
private static Collection<myObject> aggregate(List<myObject> objects) {
return objects.stream()
.collect(groupingBy(myObject::genderAndState, reducing(new myObject(), myObject::merge)))
.values();
}
private static myObject merge(myObject o1, myObject o2) {
myObject tmp = new myObject();
tmp.gender = o2.gender;
tmp.state = o2.state;
tmp.quantity= o1.quantity + o2.quantity;
tmp.salary = o1.salary + o2.salary;
return tmp;
}
private static String genderAndState(myObject o) {
return o.gender.concat(o.state);
}

Related

Grouping elements that each group contains only one object with specified field

I have a problem with grouping java objects. Let's look at example object:
public class MyObject {
private String field1;
public MyObject(String field1) {
this.field1 = field1;
}
}
What i want to achieve is grouping MyObject's in such a way that each group contains only one object with specified field1 value. For example, for such list of elements:
public static void main(String[] args) {
MyObject o1 = new MyObject("1");
MyObject o2 = new MyObject("1");
MyObject o3 = new MyObject("1");
MyObject o4 = new MyObject("2");
MyObject o5 = new MyObject("2");
MyObject o6 = new MyObject("3");
List<MyObject> list = Arrays.asList(o1, o2, o3, o4, o5, o6);
List<List<MyObject>> listsWithUniqueField1Values = new ArrayList<>();
I want to get listsWithUniqueField1Values looks like that:
[
[
MyObject{field1='1'},
MyObject{field1='2'},
MyObject{field1='3'}
],
[
MyObject{field1='1'},
MyObject{field1='2'}
],
[
MyObject{field1='1'}
]
]
I've tried to acheive it in effective way with using java.util.stream.Collectors.groupingBy method, but i faild.
I don't think you can do with it with groupingBy. Here is my solution - I also added an autogenerated equals, hashCode, and toString
public class SO67140234 {
public static void main(String[] args) {
MyObject o1 = new MyObject("1");
MyObject o2 = new MyObject("1");
MyObject o3 = new MyObject("1");
MyObject o4 = new MyObject("2");
MyObject o5 = new MyObject("2");
MyObject o6 = new MyObject("3");
List<MyObject> list = Arrays.asList(o1, o2, o3, o4, o5, o6);
List<Set<MyObject>> listsWithUniqueField1Values = new ArrayList<>();
outer:
for (MyObject obj : list) {
for (Set<MyObject> bucket : listsWithUniqueField1Values) {
if (bucket.add(obj)) {
continue outer;
}
}
listsWithUniqueField1Values.add(new HashSet<>(Collections.singleton(obj)));
}
System.out.println(listsWithUniqueField1Values);
}
}
class MyObject {
private final String field1;
public MyObject(String field1) {
this.field1 = field1;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyObject myObject = (MyObject) o;
return Objects.equals(field1, myObject.field1);
}
#Override
public int hashCode() {
return Objects.hash(field1);
}
#Override
public String toString() {
return "MyObject{" +
"field1='" + field1 + '\'' +
'}';
}
}
In order to group by instances of MyObject, this class needs to implement equals and hashCode methods, also field1 should be final to avoid corruption of hashCode upon changing its value.
public class MyObject {
private final String field1;
public MyObject(String field1) {
this.field1 = field1;
}
public String getField1() {return this.field1;}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (null == o || !(o instanceof MyObject)) return false;
MyObject that = (MyObject) o;
return Objects.equals(this.field1, that.field1);
}
#Override
public int hashCode() {
return Objects.hash(this.field1);
}
#Override
public String toString() {
return "field1=" + this.field1;
}
}
Collectors.groupingBy cannot be used to obtain the required result, but a custom Stream::collect operation may be applied to create a list of sets of unique MyObject instances (somewhat reminding #Rubydesic's solution but without nested loop).
List<MyObject> list = Arrays.asList(o1, o4, o5, o2, o6, o3);
List<Set<MyObject>> result = list.stream()
.collect(
ArrayList::new, // `Supplier<ArrayList<Set<>>>`
(lst, x) -> { // accumulator
for (Set<MyObject> set : lst) {
if (set.add(x)) {
return; // found a bucket to place MyObject instance
}
}
// create new bucket
Set<MyObject> newSet = new HashSet<>();
newSet.add(x);
lst.add(newSet);
},
(lst1, lst2) -> {} // empty combiner
);
System.out.println(result);
Output :
[[field1=1, field1=2, field1=3], [field1=1, field1=2], [field1=1]]
Assuming MyObject has a getter, one of the easiest way I can think of is to combine
Collectors.collectingAndThen
Collectors.groupingBy
A LinkedList
A method popping items from the LinkedList and inserting them inside of the result
List<List<MyObject>> finalResult = list.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(MyObject::getField1, Collectors.toCollection(LinkedList::new)),
map -> {
List<List<MyObject>> result = new ArrayList<>();
Collection<LinkedList<MyObject>> values = map.values();
while (!values.isEmpty()) {
List<MyObject> subList = values.stream()
.map(LinkedList::pop)
.toList();
result.add(subList);
values.removeIf(LinkedList::isEmpty);
}
return result;
}));
The result is
[
[
MyObject{field1='1'},
MyObject{field1='2'},
MyObject{field1='3'}
],
[
MyObject{field1='1'},
MyObject{field1='2'}
],
[
MyObject{field1='1'}
]
]
You could do this using a groupingBy itself. (Without the need of equals or hashCode)
First group using field1. This would give a map as:
{ 1 : [1,1,1], 2 : [2,2], 3 : [3] }
Now for each of these keys, iterate their respective lists and add each MyObject to a different list in listsWithUniqueField1Values.
a. First processing for key 1, the list becomes [[1]] -> [[1], [1]] -> [[1], [1], [1]].
b. Then key 2, the list becomes [[1,2], [1], [1]] -> [[1,2], [1,2], [1]].
c. The for key 3, the list becomes [[1,2,3], [1,2], [1]].
Code :
List<List<MyObject>> uniqueList = new ArrayList<>();
list.stream()
.collect(Collectors.groupingBy(MyObject::getField1))
.values()
.stream()
.forEach(values -> addToList(uniqueList, values));
return uniqueList;
The below method addToList is where the unique list is populated.
ListIterator is used over Iterator in this case, as add method is available in ListIterator.
private static void addToList(List<List<MyObject>> uniqueList, List<MyObject> values) {
ListIterator<List<MyObject>> iterator = uniqueList.listIterator();
for (MyObject o : values) {
List<MyObject> list;
if (!iterator.hasNext()) {
// the object needs to be added to a new list.
list = new ArrayList<>();
iterator.add(list);
} else {
list = iterator.next();
}
list.add(o);
}
}

How to get the index of object by its property in Java list

I would like to get the index of an object in a list by its property in Java.
Example:
List<MyObj> list = new ArrayList<>();
list.add(new MyObj("Ram");
list.add(new MyObj("Girish");
list.add(new MyObj("Ajith");
list.add(new MyObj("Sai");
public class MyObj {
public String name;
public MyObj(String name){
this.name=name;
}
}
Now, I would like to the get the index of an Object which contains the name as "Girish". Please do let me know the code in JAVA.
If you want a solution with stream use this one:
int index = IntStream.range(0, list.size())
.filter(i -> list.get(i).name.equals(searchName))
.findFirst()
.orElse(-1);
In case you have a List, all you can do is to iterate over each element and check required property. This is O(n).
public static int getIndexOf(List<MyObj> list, String name) {
int pos = 0;
for(MyObj myObj : list) {
if(name.equalsIgnoreCase(myObj.name))
return pos;
pos++;
}
return -1;
}
In case you want to increase performance. Then you could implement your own data structure. Note, that key feature is that your key property should be a key of a HashMap and value of HashMap should be index. Then you get O(1) performance.
public static final class IndexList<E> extends AbstractList<E> {
private final Map<Integer, E> indexObj = new HashMap<>();
private final Map<String, Integer> keyIndex = new HashMap<>();
private final Function<E, String> getKey;
public IndexList(Function<E, String> getKey) {
this.getKey = getKey;
}
public int getIndexByKey(String key) {
return keyIndex.get(key);
}
#Override
public int size() {
return keyIndex.size();
}
#Override
public boolean add(E e) {
String key = getKey.apply(e);
if (keyIndex.containsKey(key))
throw new IllegalArgumentException("Key '" + key + "' duplication");
int index = size();
keyIndex.put(key, index);
indexObj.put(index, e);
return true;
}
#Override
public E get(int index) {
return indexObj.get(index);
}
}
Demo:
IndexList<MyObj> list = new IndexList<>(myObj -> myObj.name);
list.add(new MyObj("Ram"));
list.add(new MyObj("Girish"));
list.add(new MyObj("Ajith"));
list.add(new MyObj("Sai"));
System.out.println(list.getIndexByKey("Ajith")); // 2
indexOf() will work if you change the .equals function
I'd suggest just iterating through
int getIndex(String wanted){
for(int i = 0; i<list.size(); i++){
if(list.get(i).name.equals(wanted)){
return i;
}
}
}
indexOf() will return the index of the first occurrence of a value. For example:
int myIndex = list.indexOf("Ram")
(Note though that your arraylist doesn't contain "Ram", it contains an object of type MyObj with a name of "Ram")
Bear in mind ArrayLists start at 0 not one.

Build a sorted list based on multiple parameters?

I have three arrays
String[] persons = {"jack","james","hill","catnis","alphonso","aruba"};
int[] points = {1,1,2,3,4,5};
int[] money = {25,66,24,20,21,22};
The nth position in all three arrays belong to the same entity, for eg:-
persons[0] == points[0] == money[0] i.e jack has 1 point and 25 bucks.
I want to build a list that sorts person alphabetically(ascending) , if the starting letter is same , then it should check points(descending) and if those are same too then it must check the money(descending).
The final list after sorting should be {aruba , alphonso , catnis , hill , james , jack}.
So I think you want something like this:
public class Person {
String name;
int points;
int money;
public Person(String name, int points, int money) {
this.name = name;
this.points = points;
this.money = money;
}
// getters
}
Then create a List<Person> with the data you have (e.g., new Person("jack", 1, 25)). And then sort them:
Collections.sort(persons, (person1, person2) -> {
// could be written more concisely, but this should make things clear
char letter1 = person1.getName().charAt(0);
char letter2 = person2.getName().charAt(0);
if (letter1 != letter2) {
return letter1 - letter2;
}
int points1 = person1.getPoints();
int points2 = person2.getPoints();
if (points1 != points2) {
return points2 - points1; // notice the order is reversed here
}
int money1 = person1.getMoney();
int money2 = person2.getMoney();
if (money1 != money2) {
return money2 - money1;
}
return 0; // unless you want to do something fancy for tie-breaking
});
That will give you a sorted List<Person> according to your criteria.
If you're up to something quick and dirty:
Comparator<Integer> cName = (i, j) -> Character.compare( persons[i].charAt(0), persons[j].charAt(0));
Comparator<Integer> cPoints = (i, j) -> Integer.compare( points[i], points[j]);
Comparator<Integer> cMoney = (i, j) -> Integer.compare( money[i], money[j]);
List<String> l =
IntStream.range(0, persons.length).boxed()
.sorted( cName.thenComparing(cPoints.reversed()).thenComparing(cMoney.reversed()) )
.map( i -> persons[i] )
.collect(Collectors.toList());
System.out.println(l);
The first 3 lines use lambdas to define comparators based on arrays indexes.
The following line uses streams:
Create an int stream of indexes from 0 to persons.length-1
Sort indexes of the stream based on the sequence of comparators
Map sorted indexes to person names
Collect it into a List
Ain't lambda and streams cool?
If you can have a Person model:
final class Person {
private final String name;
private final int points;
private final int money;
public Person(final String name, final int points, final int money) {
this.name = name;
this.points = points;
this.money = money;
}
// getters and setters (if you want)
#Override
public String toString() {
final StringBuffer sb = new StringBuffer("Person {")
.append("name=")
.append(name)
.append(", points=")
.append(points)
.append(", money=")
.append(money)
.append('}');
return sb.toString();
}
}
Then you could do something like this:
public static void main(final String... args) throws Exception {
Person[] persons = new Person[6]; // Use a List (if you can)
persons[0] = new Person("jack", 1, 25);
persons[1] = new Person("james", 1, 66);
persons[2] = new Person("hill", 2, 24);
persons[3] = new Person("catnis", 3, 20);
persons[4] = new Person("alphonso", 4, 21);
persons[5] = new Person("aruba", 5, 22);
System.out.printf("persons = %s%n%n", Arrays.toString(persons));
System.out.printf("Person[0] = %s%n%n", persons[0]);
Collections.sort(Arrays.asList(persons), (c1, c2) -> {
final int charComp = Character.compare(c1.name.charAt(0), c2.name.charAt(0));
if (0 == charComp) {
final int pointsComp = Integer.compare(c2.points, c1.points);
if (0 == pointsComp) { return Integer.compare(c2.money, c1.money); }
return pointsComp;
}
return charComp;
});
// The collection was modified at this point because of the "sort"
System.out.printf("persons = %s%n", Arrays.toString(persons));
}
Results:
persons = [Person {name=jack, points=1, money=25}, Person {name=james,
points=1, money=66}, Person {name=hill, points=2, money=24}, Person
{name=catnis, points=3, money=20}, Person {name=alphonso, points=4,
money=21}, Person {name=aruba, points=5, money=22}]
Person[0] = Person {name=jack, points=1, money=25}
persons = [Person {name=aruba, points=5, money=22}, Person
{name=alphonso, points=4, money=21}, Person {name=catnis, points=3,
money=20}, Person {name=hill, points=2, money=24}, Person {name=james,
points=1, money=66}, Person {name=jack, points=1, money=25}]
A more compact sort (but a little bit less efficient since you have to run all comparisons upfront):
Collections.sort(Arrays.asList(persons), (c1, c2) -> {
final int names = Character.compare(c1.name.charAt(0), c2.name.charAt(0));
final int points = Integer.compare(c2.points, c1.points);
final int money = Integer.compare(c2.money, c1.money);
return (0 == names) ? ((0 == points) ? money : points) : names;
});
Similar to EvanM's answer, you should group the three pieces of data into a single class.
public class Person {
private String name;
public String getName() { return name; }
private int points;
public int getPoints() { return points; }
private int money;
public int getMoney() { return money; }
}
Then you could sort them like so:
List<Person> persons = ...;
persons.sort(Comparator
.comparing(p -> p.getName().charAt(0))
.thenComparing(Comparator.comparing(Person::getPoints).reversed())
.thenComparing(Comparator.comparing(Person::getMoney) .reversed())
);

Is there a way to compare two objects in a List and combine their values that is most optimal?(Java)

Quick question. Suppose I have a function total (List list) and I have a MyObject class that have a String and an int displayed below and I want to compare two different object Strings in my total method. If they are the same, add the value on both of them. Otherwise, do nothing.
For example data is
{[Johanna, 200], [Jack, 205], [Johanna, 100], [Jack, 50]};
The output should look like
{[Johanna, 300], [Jack, 255]};
public static class MyObject {
int value;
String name;
public MyObject(String nm, int val)
{
name = nm;
value = val;
}
}
public void total(List<MyObject> list) {
List<MyObject> newList = new ArrayList<MyObject>();
Collections.sort(list);
Iterator<Order> ItrL = list.iterator();
int index = 0;
while(ItrL.hasNext())
{
MyObject compare = ItrL.next();
Iterator<MyObject> ItrR = list.listIterator(index);
index++;
while (cmp.name.equals(ItrR.next().name)))
newList.add(new MyObject(cmp.name, cmp.value + ItrR.value));
}
}
You can do summing and comparisons in parallel with no need to sort first using streams.
List<MyObject> newList = Arrays.asList(
new MyObject("Johanna", 200),
new MyObject("Jack", 205),
new MyObject("Johanna", 100),
new MyObject("Jack", 50)
);
Map<String,Integer> map =
newList.stream().parallel()
.collect(Collectors.groupingBy(mo -> mo.name,
Collectors.summingInt(mo -> mo.value)));
System.out.println("map = " + map);
There is no method that is "most optimal" as it depends on how big the data is. The problem seems suitable for map-reduce, but if you have like only 4 elements, then the overhead cost doesn't justify a real map reduce algorithm.
So anyway, here's one alternative that is pre-Java 8 (list doesn't need to be sorted first):
public static Map<String, Integer> total(List<MyObject> list) {
Map<String, Integer> result = new HashMap<String, Integer>();
for (MyObject myObject : list) {
Integer prevValue = result.get(myObject.name);
if (prevValue == null) {
result.put(myObject.name, myObject.value);
} else {
result.put(myObject.name, myObject.value + prevValue);
}
}
return result;
}
You can reduce from n^2 to n*(n/2) by using
for(int i = 0 ...
for(int j = i + 1 ...

Searching extracted data from a file, stored into an array for answers. IO

I have successfully extracted data from 2 files and stored them in array objects in their respective classes. This is some of my data that i have stored in array objects:
2005,Arsenal,ManU,Arsenal,WestHam,Y,2,3,40000
2006,ManU,Chelsea,ManU,WestHam,N
2007,ManU,Arsenal,ManU,WestHam,Y,1,0,260000
2008,Chelsea,ManU,Chelsea,Swansea,Y,4,0,285630
2009,Chelsea,ManCity,Chelsea,Swansea,N
Each comma represents a different column. This is is my code to store it:
try{
Scanner kd = new Scanner(file);
while(kd.hasNext()){
String data = kd.nextLine();
String [] values = data.split(",");
String year = values[0];
String wScore = "N/A" , lScore = "N/A", crowd = "N/A";
if(inputYears.equals(year))
{
g.setPremier(values[1]);
g.setLosing(values[2]);
I did as suggested by some of the users but the only problem now is im getting the winner as blank.For example:
Year: 2005
Premier:
It should be:
Premier: Arsenal
Anyway i can fix that?
Well, if the users are going to query only by the year, then you should have a map with the year as a key.
In the init phase, try doing something like this:
Map<String, ArrayList<String>> map= new HashMap<String, ArrayList<String>>();
while(kd.hasNext()){
String data = kd.nextLine();
String [] values = data.split(",");
String year = values[0];
String premiers = values[1];
//String otherStuff= values[2]; and so on
ArrayList<String> temp= new ArrayList<String>();
temp.add(premiers);
//temp.add(otherStuff);
map.put(year, temp);
}
Later on, you can query like this:
ArrayList<String> values= map.get(userInputYear);
String winner= values.get(0);
No collections? Tough teacher you got there....
In the absence of a Map you could try creating a separate getPremiershipChampion method.
Like this:
public PremiershipYear getPremiershipChampion (String userYear, String[] kd) {
while(kd.hasNext()){
String data = kd.nextLine();
String [] values = data.split(",");
String year = values[0];
if (userYear.equals(year)) {
PremiershipYear py = new PremiershipYear();
py.setWinner(values[1]);
py.setRunnerUp(values[2]);
// Set whatever you need to here...
return py;
}
}
return null;
}
...then call it like this:
PremiershipYear year = getPremiershipChampion (userYear, kd);
// Get the winner
if (year != null) {
System.out.println("Winner for "+userYear+"="+year.getWinner());
}
Here is the PremiershipYear class:
public class PremiershipYear {
private String winner;
private String runnerUp;
private String third;
private String fourth;
private String faCupWinner;
private int someScore;
public String getWinner() {
return winner;
}
public void setWinner(String winner) {
this.winner = winner;
}
public String getRunnerUp() {
return runnerUp;
}
public void setRunnerUp(String runnerUp) {
this.runnerUp = runnerUp;
}
public String getThird() {
return third;
}
public void setThird(String third) {
this.third = third;
}
public String getFourth() {
return fourth;
}
public void setFourth(String fourth) {
this.fourth = fourth;
}
public String getFaCupWinner() {
return faCupWinner;
}
public void setFaCupWinner(String faCupWinner) {
this.faCupWinner = faCupWinner;
}
public int getSomeScore() {
return someScore;
}
public void setSomeScore(int someScore) {
this.someScore = someScore;
}
}
You can muck about with this to make it more efficient e.g. make kd class level, but this should give you the general idea!
Use a Map.
If you only have one year per line, use a HashMap<String, String> with the year as key and the team as value.
If you have many lines with identical years, use a HashMap<String, ArrayList<String>> with the year as key and the team*s* as values.
In the latter case, you must ensure the value is initialized. If not, you initialize it and ad d the current team. If it was already initialized, you get the value by year and add the current team to it.
Once you have the user input, you can query your Map's key set...
to verify it contains the user input, and if so...
to display the team / enumerate every team listed for that year
you could make a class to represent a record of your data
class Data
{
private String year;
private String winner;
...
Data(String year, String winner) { ... }
}
then you can fill a Map which associates a year with some data which makes looking-up the year a bit easier.
Map<String, Data> m = new HashMap<String, Data>();
while(kd.hasNext())
{
String[] arr = kd.nextLine().split(",");
Data d = new Data(arr[0], arr[1])
m.put(arr[0], d);
}
but even if you just had a List<Data> or Data[] etc you could still do something like
for (Data d : listOfData)
{
if (d.getYear().equals(yearToFind)) return d;
}
to find the year

Categories