Java 8 Grouping by Multiple Fields into Single Map - java

I have a class like this:
public class Foo {
private String a;
private String b;
private LocalDate c;
private int d;
}
I have a list of Foo objects that I want to group by a, b, and c and produce a map. Here's what I have so far:
Map<String, List<Foo>> test = foos.stream().collect(Collectors.groupingBy(Foo::getA, Collectors.collectingAndThen(Collectors.groupingBy(Foo::getB), Collections.unmodifiableList())));
But that itself is wrong. I don't know how to groupby multiple fields but still produce a Map<String, List<Foo>>. Any ideas what I'm doing wrong?
Edit 1: If I have the following Foo's:
{"Test", "Test", "10/02/2015", 5}
{"Test", "Test", "10/02/2015", 4}
{"Test", "Test", "10/02/2015", 3}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}
Then it should group to:
{"Test", "Test", "10/02/2015", [5, 4, 3]}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}
My original post was misleading in what exactly I wanted but basically it needs to group by a, b, d and produce a list of d. I know I'll probably have to create a new class to store them in like so:
public class FooResult {
private String a;
private String b;
private LocalDate c;
private List<Integer> d;
}
How can I group and map to a new class like shown above?

As a group by multiple fields is not implemented you have to use a composite key consisting of values from a, b and c. With that key the collect operation can be used like this with the Collector#of() factory method.
Map<String, List<Integer>> result = foos.stream().collect(Collector.of(
HashMap::new,
( map, foo ) -> {
map.compute(foo.a + "_" + foo.b + "_" + foo.c, (key,list) -> {
if(list == null){
list = new ArrayList<>();
}
list.add(foo.d);
return list;
});
},
( map1, map2 ) -> {
map2.forEach(( k, v ) -> {
map1.compute(k, (key, list) -> {
if(list == null){
list = v;
} else {
list.addAll(v);
}
return list;
});
});
return map1;
}
));

You can also use intermediate Map with a key that aggregates fields a, b and c from Foo class and List<Integer> value that collects all d field values.. In below example I have created MapKey class - a helper class that aggregates those fields and implements hashCode and equals methods so it can be used as a key in a HashMap.
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FooMain {
public static void main(String[] args) {
final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");
final List<Foo> foos = Arrays.asList(
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 5),
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 4),
new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 3),
new Foo("Test", "Test", LocalDate.parse("02/02/2015", dateFormat), 5),
new Foo("Test", "Potato", LocalDate.parse("02/02/2015", dateFormat), 5)
);
List<FooResult> result = foos.stream()
.collect(Collectors.groupingBy(foo -> new MapKey(foo.a, foo.b, foo.c), Collectors.mapping(Foo::getD, Collectors.toList())))
.entrySet()
.stream()
.map(entry -> new FooResult(entry.getKey().a, entry.getKey().b, entry.getKey().c, entry.getValue()))
.collect(Collectors.toList());
result.forEach(System.out::println);
}
public static final class Foo {
private final String a;
private final String b;
private final LocalDate c;
private final int d;
Foo(String a, String b, LocalDate c, int d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
int getD() {
return d;
}
}
public static final class FooResult {
private final String a;
private final String b;
private final LocalDate c;
private final List<Integer> d;
FooResult(String a, String b, LocalDate c, List<Integer> d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
#Override
public String toString() {
return "FooResult{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c=" + c +
", d=" + d +
'}';
}
}
public static final class MapKey {
private final String a;
private final String b;
private final LocalDate c;
MapKey(String a, String b, LocalDate c) {
this.a = a;
this.b = b;
this.c = c;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MapKey)) return false;
MapKey mapKey = (MapKey) o;
if (a != null ? !a.equals(mapKey.a) : mapKey.a != null) return false;
if (b != null ? !b.equals(mapKey.b) : mapKey.b != null) return false;
return c != null ? c.equals(mapKey.c) : mapKey.c == null;
}
#Override
public int hashCode() {
int result = a != null ? a.hashCode() : 0;
result = 31 * result + (b != null ? b.hashCode() : 0);
result = 31 * result + (c != null ? c.hashCode() : 0);
return result;
}
}
}
Then as you can see you can do your transformation is 6 lines of code. The output of this program is following:
FooResult{a='Test', b='Potato', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-10-02, d=[5, 4, 3]}
I've also made Foo, FooResult and MapKey immutable - this is always a good choice when you have to deal with stream transformations. You don't want to have any side effects during stream manipulation and immutable objects guarantee that.

Related

Can we design a generic function where both integer and string addition to be done?

class generic<T> {
T a;
T b;
generic(T a, T b) {
this.a = a;
this.b = b;
}
public T sum() {
return (a+b);
}
}
//can er design this as it takes a input both as integer and string and //give the append result as the same return type.
Not like that but you could do something like this:
public class Adding{
public static void main(String []args){
String string = "12";
Integer integer = 20;
System.out.println(add(string, integer));
}
private static int add (Object a, Object b) {
return toInt(a) + toInt(b);
}
private static int toInt (Object obj) {
if (obj.getClass() == String.class) {
return Integer.parseInt((String)obj);
} else if (obj.getClass() == Integer.class) {
return (Integer) obj;
} else {
//throw an exception
return 0;
}
}
Edit: you could also use your generic types here too
You don't need to write one. Just use the existing one like so.
BiFunction<String, String, String> biStr = (s1, s2) -> s1 + s2;
BiFunction<Integer, Integer, Integer> biInt = (n1, n2) -> n1 + n2;
System.out.println(biStr.apply("One", "two"));
System.out.println(biInt.apply(10, 6));
You can use the instanceof operator.
You can check for the type of T by asking if instance variable a or b is an instance of String or Integer and make a decision accordingly.
class Generic<T>
{
T a;
T b;
Generic(T a,T b) {
this.a = a;
this.b = b;
}
public T sum() {
if (a instanceof String && b instanceof String) {
// string concatenation e.g. return a + b + "\n";
} else if (a instanceof Integer && b instanceof Integer) {
// integer addition e.g. return a + b;
}
return null;
}
}
Note that you'd have to use class types and not primitive types while creating an object of Generic
More notably, you may be able to design the components of your implementation in a better way than to use this Generic class. (Perhaps, inheritance?)

Fill objects fields from Stream<Integer>

I have class
#Data
Test{
private int[] a;
private int[] b;
}
And I have private Stream<Integer> stream with 1 000 000 integers.
How can I make the following:
iterate over all integers in stream
create new Test
if i!=0 add i to test.a else add i to test.b
At last I need a Test object with 2 arrays where a contains all non-zero elements and b contains all zeroes.
I need 1 Test object.
Maybe you could use 2 stream operations and on 1 instance of the test use setters for a and b.
public class Foo {
public static void main(String[] args) throws IOException {
List<Integer> myList = new ArrayList<>();
myList.add(1);
myList.add(0);
myList.add(1);
Test t = new Test();
t.setA(myList.stream().filter(x -> x != 0).mapToInt(x -> x).toArray());
t.setB(myList.stream().filter(x -> x == 0).mapToInt(x -> x).toArray());
System.out.println("t: " + t);
}
}
class Test {
private int[] a;
private int[] b;
public void setA(int[] array) {
a = array;
}
public void setB(int[] array) {
b = array;
}
#Override
public String toString() {
return "Test {a=" + Arrays.toString(a) + ", b=" + Arrays.toString(b) + "}";
}
}
If you know beforehand that you have 1,000,000 integers, there is no need to collect zeros at all:
int[] a = stream.mapToInt(Integer::intValue).filter(i -> i!=0).toArray();
int[] b = new int[1_000_000 - a.length];
Test test = new Test(a, b);
Here is a sample implementation that splits the Stream into two Streams
public class Main {
static class Test {
private int[] a;
private int[] b;
Test(int[] a, int[] b) {
this.a = a;
this.b = b;
}
}
public static void main(String[] args) {
IntStream.Builder zeroStreamBuilder = IntStream.builder();
IntStream.Builder nonZeroStreamBuilder = IntStream.builder();
IntStream intStream = IntStream.of(0, 0, 1, 2, 3, 4);
intStream.forEach(value -> {
if (value == 0)
zeroStreamBuilder.add(value);
else
nonZeroStreamBuilder.add(value);
}
);
int[] a = zeroStreamBuilder.build().toArray();
int[] b = nonZeroStreamBuilder.build().toArray();
Test result = new Test(a, b);
}
}
If you are willing to use a List, then it will be possible skip building arrays from streams .build().toArray() as you can directly add the values to the resulting lists.

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.

JAVA8 - Grouping with lambda

I have a collection with structure like this:
#Entity
public class RRR{
private Map<XClas, YClas> xySets;
}
and XClas has a field called ZZZ
my question is:
I would like to aggregate it with lambda to get a Map<ZZZ, List<RRR>>.
Is it possible? Now I'm stuck with:
Map xxx = rrrList.stream().collect(
Collectors.groupingBy(x->x.xySets().entrySet().stream().collect(
Collectors.groupingBy(y->y.getKey().getZZZ()))));
but it's Map<Map<ZZZ, List<XClas>>, List<RRR>> so it's not what I was looking for :)
Right now just to make it work, I did aggregation with two nested loops, but it would be so great, if you could help me make it done with lambdas.
EDIT
I post what I got by now, as asked.
I already left nested loops, and I manage to work my way up to this point:
Map<ZZZ, List<RRR>> temp;
rrrList.stream().forEach(x -> x.getxySetsAsList().stream().forEach(z -> {
if (temp.containsKey(z.getKey().getZZZ())){
List<RRR> uuu = new LinkedList<>(temp.get(z.getKey().getZZZ()));
uuu.add(x);
temp.put(z.getKey().getZZZ(), uuu);
} else {
temp.put(z.getKey().getZZZ(), Collections.singletonList(x));
}
}));
Thanks in advance
Something like that? :
Map<ZZZ, List<RRR>> map = new HashMap<>();
list.stream().forEach(rrr -> {
rrr.xySets.keySet().stream().forEach(xclas -> {
if (!map.containsKey(xclas.zzz))
map.put(xclas.zzz, new ArrayList<RRR>());
map.get(xclas.zzz).add(rrr);
});
});
Another way you could do this:
Map<Z, List<R>> map = rs.stream()
.map(r -> r.xys.keySet()
.stream()
.collect(Collectors.<X, Z, R>toMap(x -> x.z, x -> r, (a, b) -> a)))
.map(Map::entrySet)
.flatMap(Collection::stream)
.collect(Collectors.groupingBy(Entry::getKey,
Collectors.mapping(Entry::getValue, Collectors.toList())));
I have tried around a bit and found the following solution, posting it here just as another example:
rrrList.stream().map(x -> x.xySets).map(Map::entrySet).flatMap(x -> x.stream())
.collect(Collectors.groupingBy(x -> x.getKey().getZZZ(),
Collectors.mapping(Entry::getValue, Collectors.toList())));
The first line could also be written as rrrList.stream().flatMap(x -> x.xySets.entrySet().stream()) which might be found more readable.
Here is self-contained example code for those wanting to play around themselves:
public static void main(String[] args) {
List<RRR> rrrList = Arrays.asList(new RRR(), new RRR(), new RRR());
System.out.println(rrrList);
Stream<Entry<XClas, YClas>> sf = rrrList.stream().map(x -> x.xySets).map(Map::entrySet).flatMap(x -> x.stream());
Map<ZZZ, List<YClas>> res = sf.collect(Collectors.groupingBy(x -> x.getKey().getZZZ(), Collectors.mapping(Entry::getValue, Collectors.toList())));
System.out.println(res);
}
public static class RRR {
static XClas shared = new XClas();
private Map<XClas, YClas> xySets = new HashMap<>();
RRR() { xySets.put(shared, new YClas()); xySets.put(new XClas(), new YClas()); }
static int s = 0; int n = s++;
public String toString() { return "RRR" + n + "(" + xySets + ")"; }
}
public static class XClas {
private ZZZ zzz = new ZZZ();
public ZZZ getZZZ() { return zzz; }
public String toString() { return "XClas(" + zzz + ")"; }
public boolean equals(Object o) { return (o instanceof XClas) && ((XClas)o).zzz.equals(zzz); }
public int hashCode() { return zzz.hashCode(); }
}
public static class YClas {
static int s = 0; int n = s++;
public String toString() { return "YClas" + n; }
}
public static class ZZZ {
static int s = 0; int n = s++ / 2;
public String toString() { return "ZZZ" + n; }
public boolean equals(Object o) { return (o instanceof ZZZ) && ((ZZZ)o).n == n; }
public int hashCode() { return n; }
}

Java double entry table

Does anyone know a double entry table implementation in Java I can download ?
I need to do something like this
1 2 3
_______
a| x y z
b| h l m
c| o a k
table.get(a,1) would return x
Of course, it should use any Object as key, value, etc
There are two basic approaches, depending on your needs.
One is to make a Hashtable (or similar) of Hashtables.
Hashtable<Integer, Hashtable<String, String>> = ...;
Another approach is to build your own datatype that represents an (Integer, String) pair, so you can do:
Hashtable<YourFancyDatatype, String>
The answer to your question partially lies in a previous questions on SO:
Java generics Pair<String, String> stored in HashMap not retrieving key->value properly
import java.lang.*;
import java.util.*;
public class Pair<TYPEA, TYPEB> implements Comparable< Pair<TYPEA, TYPEB> > {
protected final TYPEA Key_;
protected final TYPEB Value_;
public Pair(TYPEA key, TYPEB value) {
Key_ = key;
Value_ = value;
}
public TYPEA getKey() {
return Key_;
}
public TYPEB getValue() {
return Value_;
}
public String toString() {
System.out.println("in toString()");
StringBuffer buff = new StringBuffer();
buff.append("Key: ");
buff.append(Key_);
buff.append("\tValue: ");
buff.append(Value_);
return(buff.toString() );
}
public int compareTo( Pair<TYPEA, TYPEB> p1 ) {
System.out.println("in compareTo()");
if ( null != p1 ) {
if ( p1.equals(this) ) {
return 0;
} else if ( p1.hashCode() > this.hashCode() ) {
return 1;
} else if ( p1.hashCode() < this.hashCode() ) {
return -1;
}
}
return(-1);
}
public int hashCode() {
int hashCode = Key_.hashCode() + (31 * Value_.hashCode());
System.out.println("in hashCode() [" + Integer.toString(hashCode) + "]");
return(hashCode);
}
#Override
public boolean equals(Object o) {
System.out.println("in equals()");
if (o instanceof Pair) {
Pair<?, ?> p1 = (Pair<?, ?>) o;
if ( p1.Key_.equals( this.Key_ ) && p1.Value_.equals( this.Value_ ) ) {
return(true);
}
}
return(false);
}
public static void main(String [] args) {
HashMap< Pair<String, int>, String> table = new HashMap<Pair<String,int>, String>();
table.put(new Pair<String, int>("a", 1), "x");
table.put(new Pair<String, int>("a", 2), "y");
table.put(new Pair<String, int>("a", 3), "z");
table.put(new Pair<String, int>("b", 1), "h");
table.put(new Pair<String, int>("b", 2), "l");
table.put(new Pair<String, int>("b", 3), "m");
table.put(new Pair<String, int>("c", 1), "o");
table.put(new Pair<String, int>("c", 2), "a");
table.put(new Pair<String, int>("c", 3), "k");
String val = table.get(new Pair<String, int>("a", 1)); //val is x for this input pair
}
}
I'm assuming you have an array of characters/objects and a quantity and want cross each other in your table. You could map each character to a number between 0 .. qtyOfCharacters and simply create a bidimensional array Object[][] table = new Object[A][B], where A is the quantity of characters/object just mapped, and B is the quantity of columns.
To map the characters/object to numbers you should use a HashMap/HashTable.
The idea is that if you access the element at "a,3" you should write table[ charmap.get("a")][ 3]

Categories