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

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);
}
}

Related

Java Jackson- Compare two JSONs ignoring order of keys and elements in arrays

I have two JSON strings which I want to compare.
I want neither the order of the keys to matter or the order of elements in an array.
However I do want an extra field to be considered "not equal"
Non strict mode with JSONAssert seems like it fits the bill except for an extra field being considered equal "http://jsonassert.skyscreamer.org/cookbook.html"
If at all possible I would like to avoid pulling in extra dependancies. I already have jackson in my project
I have 2 ideas how to do it.
Is to write java objects and serialize it, and write own equals method.
Is to serialize it to Map<Object, Object> and compare 2 map.
String json1 = "{...}"
String json2= "{...}"
Object json1Object = objectMapper.readValue(json1, Object.class);
Object json2Object = objectMapper.readValue(json2, Object.class);
Assertions.assertEquals(json1Object, json2Object);
Assertions.assertTrue(json1Object.equals(json2Object));
So you probably have only one option. Write own comparator.
My quick solution:
#Test
public void comparingJsonTest4() throws JsonProcessingException {
String json1 = "{\"id\": 1, \"name\": \"test\", \"cars\": [\"Ford\", \"BMW\", \"Fiat\"]}";
String json2 = "{\"name\": \"test\", \"id\": 1, \"cars\": [\"BMW\", \"Ford\", \"Fiat\"]}";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode json1Node = objectMapper.readTree(json1);
JsonNode json2Node = objectMapper.readTree(json2);
Assertions.assertEquals(0, new ComparatorWithoutOrder().compare(json1Node, json2Node));
}
class ComparatorWithoutOrder implements Comparator<JsonNode> {
#Override
public int compare(JsonNode o1, JsonNode o2) {
if(o1 == o2) {
return 0;
}
if(o1.getClass() != o2.getClass()) {
return -1;
}
if(o1.getClass() == ObjectNode.class) {
List<String> o1FieldNames = new ArrayList<>();
o1.fieldNames().forEachRemaining(o1FieldNames::add);
List<String> o2FieldNames = new ArrayList<>();
o2.fieldNames().forEachRemaining(o2FieldNames::add);
if(o1FieldNames.size() != o2FieldNames.size()) {
return -1;
}
if(!o2FieldNames.containsAll(o1FieldNames) || !o1FieldNames.containsAll(o2FieldNames)) {
return -1;
}
for (String o1FieldName : o1FieldNames) {
if (!(compare(o1.get(o1FieldName), o2.get(o1FieldName)) == 0)) {
return -1;
}
}
return 0;
}
if(o1.getClass() == ArrayNode.class) {
List<JsonNode> o1Children = new ArrayList<>();
o1.elements().forEachRemaining(o1Children::add);
List<JsonNode> o2Children = new ArrayList<>();
o2.elements().forEachRemaining(o2Children::add);
if(o1Children.size() != o2Children.size()) {
return -1;
}
for (JsonNode c1 : o1Children) {
boolean found = false;
for (JsonNode c2 : o2Children) {
if (compare(c1, c2) == 0) {
found = true;
break;
}
}
if (!found) {
return -1;
}
}
return 0;
}
return o1.equals(o2) ? 0 : -1;
}
}
At the beginning I wanted to use something like this:
json1Node.equals(new ComparatorWithoutOrder(), json2Node);
but thre was a problem to propoer handle ArrayNode inside ObjectNode. So if you want, you could skip implements Comparator<JsonNode>, because finally I don't use this functionality.

How to do a DFS on a list of objects which also contains a list of objects

How can I recursively search an object which has a List of same objects and breaks when I find a specific object in it.
Example this is my object, and each object can go deeper with a list of its own
MyObject:
List<MyObject>
MyObject <- 2) Tag this and move onto next object
List<MyObject>
MyObject
List<MyObject>
MyObject <- 1) BOOM found what I want
List<MyObject>
MyObject
MyObject
MyObject
MyObject
MyObject
MyObject
MyObject
MyObject
I essentially want to do a DFS on my list. I've tried to recursively do it but I can't seem to exit it properly.
For your problem explained above , this solution might help you
private static boolean search(Object object, Object searchingObject) {
List<?> al = (ArrayList) object;
for (int index = 0; index < al.size(); index++) {
if (al.get(index) instanceof List) {
if(search(al.get(index), searchingObject)) {
return true;
}
} else {
Iterator<Object> itr = (Iterator<Object>) al.iterator();
Object o;
while (itr.hasNext()) {
o = itr.next();
if (o.equals(searchingObject)) {
return true;
}
}
}
}
return false;
}
main method for the abve code
public static void main(String[] args) {
ArrayList<ArrayList> o = new ArrayList<>();
ArrayList<Integer> al = new ArrayList<>();
ArrayList<ArrayList<Integer>> o1 = new ArrayList<>();
al.add(2);
al.add(3);
al.add(4);
o1.add(al);
o.add(o1);
Integer i = 4;//Object which has to be searched
System.out.println(search(o,i));//returning true
}

Test content of a collection attribute from a collection of objects

Sorry for this title that I try to make as clear as possible. Don't hesitate to edit to impove it.
My problem is that I would like to test the content of this structure and more specialy the content of B objects and assure that it correspond to specific A values :
public class A {
String key;
List<B> bs;
}
And my data have this form
List<A> as = [
{
key : "KEY1",
bs: [
{val1:"val1", val2:"val2}
]
},
{
key : "KEY2",
bs: [
{val1:"val3", val2:"val4"},
{val1:"val5", val2:"val6"}
]
},
];
In the context of Unit testing with Mockito, I would like to be able to test this structure without doing a pre treatment to get the List of B. By testing this structure I would like to be sure that there are two B for KEY2 and that first B has val3 and val4, the second val5 and val6.
At the moment, I have to create a map by key and test every entry. I would like to it in a more straightforward way if any exist.
Here is my actual test.
List<A> as = captor.getAllValues();
assertThat(as)
.isNotNull()
.hasSize(2)
.extracting("key")
.containsOnlyOnce(
tuple("KEY1"),
tuple("KEY2")
);
Map<String, A> estimationParPlateforme = indexBy(as, new Indexer<String, A>() {
#Override
public String apply(A a) {
return a.getKey();
}
});
assertThat(as.get("KEY1").getBs())
.isNotEmpty()
.extracting(
"val1",
"val2"
)
.containsExactly(
tuple(
"val1",
"val2"
)
);
assertThat(as.get("KEY2").getBs())
.isNotEmpty()
.extracting(
"val1",
"val2"
)
.containsExactly(
tuple(
"val3",
"val4"
),
tuple(
"val5",
"val6"
)
);
I think that it is a bit long for a test, and I would like to find a way to improve this. Do you have any solution?
Thanks for any help
You have to define how your datastructure should look. At the moment you have all this information scattered in your test statements. That makes it very ugly as you allready figured. What i like to do is to have some datastructure, that i can fill with data, that i am expecting. Then i can use this, to test, whether my data is valid or not. An example:
static Map<String, B[]> expectedBs = new HashMap<String, B[]>();
public static void main(String[] args) {
//put the expected values into a map
expectedBs.put("KEY1", new B[] { new B("val1", "val2") });
expectedBs.put("KEY2", new B[] { new B("val3", "val4"), new B("val5", "val6") });
//build a valid datastructure
List<A> as = new ArrayList<A>();
List<B> bs1 = new ArrayList<B>();
bs1.add(new B("val1", "val2"));
List<B> bs2 = new ArrayList<B>();
bs2.add(new B("val3", "val4"));
bs2.add(new B("val5", "val6"));
as.add(new A("KEY1", bs1));
as.add(new A("KEY2", bs2));
//test validity of as
System.out.println(isValid(as)); //prints true
//modify the datastructure such that it is not valid anymore
bs1.get(0).val1 = "val2";
//test validity once more
System.out.println(isValid(as)); //prints false
}
static boolean isValid(List<A> as) {
for (A a : as) {
B[] expected = expectedBs.get(a.key);
if (!equals(a.bs, expected))
return false;
}
return true;
}
static boolean equals(List<B> x, B[] y) {
if (x == null)
return false;
if (y == null)
return false;
if (x.size() != y.length)
return false;
for (int i = 0; i < x.size(); i++)
if (!x.get(i).equals(y[i]))
return false;
return true;
}
A:
public class A {
String key;
List<B> bs;
public A(String key, List<B> bs) {
this.key = key;
this.bs = bs;
}
}
B:
public class B {
String val1, val2;
public B(String val1, String val2) {
this.val1 = val1;
this.val2 = val2;
}
#Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!(obj instanceof B))
return false;
B objB = (B) obj;
if (!objB.val1.equals(this.val1))
return false;
if (!objB.val2.equals(this.val2))
return false;
return true;
}
}
Sorry for the code beeing so long. Hope you get the idea. Whether this is more elegant or not is up to you to decide.

grouping objects according to some fields

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);
}

How to build an ArrayList according to the position of each data item

Let's say there is a collection of data of type: {position: i, object}
How do I build an ArrayList which has the data in the order determined by position but without gaps in the ArrayList?
For example, if the data looks like this:
[{position: 3, object3}, {position: 5, object5}, {position: 2, object2}]
I would like an ArrayList like this:
arrayList[0] == object2;
arrayList[1] == object3;
arrayList[2] == object5;
Sort the collection by position, then iterate over it and add each item to an ArrayList.
In addition to sort and/or TreeSet which might be the best choices, you may create a method to insert in order:
public static void add( List<YourClass> list, YourClass o ) {
for ( int i = 0 ; i < list.size(); i++ ) {
if ( list.get(i).position() > o.position() ) {
list.add(i, o);
return;
}
}
list.add(o);
}
Full demo program:
import java.util.List;
import java.util.ArrayList;
class YourClass {
private int position;
private String data;
public YourClass( int position, String data ) {
this.position = position;
this.data = data;
}
public int position() {
return position;
}
public String toString() {
return String.format("{position: %d %s}", position, data);
}
}
class Main {
public static void main( String ... args ) {
List<YourClass> list = new ArrayList<>();
add( list, new YourClass(3, "object 3"));
add( list, new YourClass(5, "object 5"));
add( list, new YourClass(2, "object 2"));
add( list, new YourClass(1, "object 1"));
System.out.println(list);
}
public static void add( List<YourClass> list, YourClass o ) {
for ( int i = 0 ; i < list.size(); i++ ) {
if ( list.get(i).position() > o.position() ) {
list.add(i, o);
return;
}
}
list.add(o);
}
}
Output:
java Main
[{position: 1 object 1}, {position: 2 object 2}, {position: 3 object 3}, {position: 5 object 5}]
You call do this with a TreeSet and a Comparator:
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
class KeyPair<T> {
private final int position;
private final T value;
public KeyPair(int position, T value) {
this.position = position;
this.value = value;
}
public int getPosition() {
return position;
}
public T getValue() {
return value;
}
#Override
public String toString() {
return position + " => " + value;
}
}
public class Main {
public static void main(String[] args) {
TreeSet<KeyPair> set = new TreeSet<>((o1, o2) -> o1.getPosition() - o2.getPosition());
set.add(new KeyPair<>(5, "x"));
set.add(new KeyPair<>(1, "y"));
set.add(new KeyPair<>(2, "z"));
set.add(new KeyPair<>(7, "a"));
set.add(new KeyPair<>(6, "b"));
set.add(new KeyPair<>(0, "c"));
set.stream().forEach(System.out::println);
}
}
Output:
0 => c
1 => y
2 => z
5 => x
6 => b
7 => a
And if you need an ArrayList instead of a TreeSet you can do this:
List<KeyPair> list = new ArrayList<>();
list.addAll(set);
The good thing about this approach is that at any point in time you have the collection sorted by position if compared with the approach of only sorting in the end.
Great question. Assuming Comparable is implementable, you can use the compareTo method (just like in Strings).
So, for example, your custom comparator could look like the following:
public class CustomComparator implements Comparator<MyObject> {
#Override
public int compare(MyObject o1, MyObject o2) {
if (o1.position < o2.position)
return -1;
return 0;
}
}
And to sort your ArrayList, you'll need to call:
Collections.sort(arrayList, new CustomComparator());
If your data is really position: i then first repopulate a new HashMap in the form of {i, object}.
You can then put your hashmap entries inside a TreeMap, this will automaticly sort them. After you just iterate over the TreeMap and put the now sorted objects inside an array.
Example:
Map<Integer, Object> map = new TreeMap<>(yourMap);
Object[] sortedArray = new Object[map.size()];
int i = 0;
for(Map.Entry<Integer,Object> entry : map.entrySet()) {
Object value = entry.getValue();
sortedArray[i]=value;
i++;
}

Categories