Generic list of lists Java - java

i'm trying to make a generic function in Java to find out the maximum similarity between an ArrayList and a list from an ArrayList of ArrayLists.
public static int maxSimilarity(ArrayList<?> g,
ArrayList<ArrayList<?>> groups){
int maxSim = 0;
for(ArrayList<?> g2:groups){
int sim = similarity(g, (ArrayList<?>) g2);
if(sim > maxSim)
maxSim = sim;
}
return maxSim;
}
However, when i try to call it in my main function, it show an incompatible error
ArrayList<ArrayList<Points>> cannot be converted to ArrayList<ArrayList<?>>
I don't understand, i tought all objects can be represented by the ? sign. Also, it works in my similarity function, between two ArrayLists:
public static int similarity(ArrayList<?> g1, ArrayList<?> g2){
int total = 0;
for(Object o1:g1){
for(Object o2:g2){
if(o1.equals(o2))
total++;
}
}
return total;
}

Instead of a wildcard, declare a generic value:
public <T> static int maxSimilarity(List<T> g, List<? extends List<T>> gs);

Change your method signature to:
public static int maxSimilarity(ArrayList<?> g, ArrayList<? extends ArrayList<?>> groups)
And in general prefer using interface types, instead of actual implementations (more flexible, less code):
public static int maxSimilarity(List<?> g, List<? extends List<?>> groups)
[edit] Based on the suggestion with the type variables, to make this super-generic, it should be:
public static <T> int maxSimilarity(List<? extends T> g, List<? extends List<? extends T>> groups)
Notice that ? extends T. This way, you can use e.g.
List<List<StringBuilder>> groups = // ...
List<String> g = // ...
maxSimilarity(g, groups);
(StringBuilder and String are a CharSequence, so they can be compared).

If you want to compare lists of similar objects, you should introduce a method type parameter
public static <T> int maxSimilarity(List<T> g, List<List<T>> groups) {
because it's nearly useless comparing completely different objects.

Try declaring your method:
public static <T>int maxSimilarity(ArrayList<T> g, ArrayList<ArrayList<T>> groups)
Hope it helps.

Related

Accept any enum as parameter

public static <E extends Enum<E>> List<E> enumHeadAsList(final Class<E> val, final E topValue)
{
// My code
}
I want to make a method that accept any enum i give and i set a default value of that enum to do some order check and then return all values as list. Basically i want set head of an enum and return values. So far i managed to do this but i don't know how to continue.
I want call the following method like:
enumHeadAsList(BuffType.class, BuffType.SELF);
This should do the trick:
public static <E extends Enum<E>> List<E> enumHeadAsList(final E topValue)
{
E[] values = topValue.getDeclaringClass().getEnumConstants();
if (topValue.ordinal() != 0) {
E oldTop = values[0];
values[0] = topValue;
values[topValue.ordinal()] = oldTop;
}
return List.of(values);
}
This code assumes that you don't care too much about the order of the remaining elements (as the top element is simply swapped with the previously first one).
If you want to keep the relative order of the other values intact, then you'll have to tweak replace the if above with this code block:
final int topValueIndex = topValue.ordinal();
if (topValueIndex != 0) {
System.arraycopy(values, 0, values, 1, topValueIndex);
values[0] = topValue;
}
Note that a dedicated Class<E> parameter is not needed if topValue is not allowed to be null, because we can get the enum class from topValue.
If using a Java version before Java 9, then you can replace List.of() with Arrays.asList() with the difference that the resulting List would be mutable.
EnumSet makes this easy:
public static <E extends Enum<E>> List<E> enumHeadAsList(final Class<E> val, final E topValue)
{
List<E> values = new ArrayList<>();
values.add(topValue);
values.addAll(EnumSet.complementOf(EnumSet.of(topValue)));
return values;
}
complementOf returns a collection of all enum values except those present in the argument. So, complementOf(EnumSet.of(topValue)) is all values except topValue.

Passing a specific class argument to a Java Generic Function

So, I'm new to Java Generics. I'm building a graph and an adjacency list using hashMap and hashSet. I've written the class and its methods using Generics. I've also extended T to Node.
For printing the adjacency list,
public class Graph<T extends Node>{
public static int time;
private HashMap<T, HashSet<T>> adjacencyList;
public Graph(){
this.adjacencyList = new HashMap<>();
}
void printAdjacencyList(HashMap<T,HashSet<T>> adj){
for(T n : adj.keySet()){
System.out.print("Key: " + n.getValue()+"\t");
Iterator<T> i = adj.get(n).iterator();
while(i.hasNext()){
System.out.print(" "+i.next().getValue());
}
System.out.println();
}
}
}
public static void main(String[] args){
Graph<Node> G = new Graph<>();
// have added vertices and edges
printAdjacencyList(G.adjacencyList);
}
Here,G.adjacencyList is of the type HashMap<Node,HashSet< Node >> .
Node is another class with a few members.
But , I'm encountering compilation error:
The method printAdjacencyList(HashMap<T,HashSet < T >>) in the type Graph is not applicable for the arguments (HashMap<Node,HashSet < Node >>)
So, as far as I can understand it's saying me to explicitly write a function with HashMap<Node, HashSet < Node > > as arguments.
Is there any way that I can use the generic function somehow (instead of writing a new function with required arguments).
The problem is that you want to pass a HashMap<Node, Set> but it want a HastMap<Node, HastSet>.
Every HashSet also fulfill the requirement of Set but not vice versa.
Solution 1:
void printAdjacencyList(HashMap<T,HashSet<T>> adj){ =>
void printAdjacencyList(HashMap<T,Set<T>> adj){
Solution 2:
private HashMap<T, Set<T>> adjacencyList; => private HashMap<T, HashSet<T>> adjacencyList;

Custom Comparator sort with multiple fields

Because this question is related to my last one, I will link it here.
Suppose I have a class TestB with two integers. I would be able to sort List<TestB> list on a and then on b like this:
list.sort(Comparator.comparing(TestB::getA).thenComparing(TestB::getB));
Now I want to know how to do that with the custom comparator in the last answer.
The custom Comparator version of list.sort(Comparator.comparing(TestB::getA).thenComparing(TestB::getB)); is:
list.sort(new Comparator<>() {
#Override
public int compare(TestB b1, TestB b2) {
int cmp = b1.getA().compareTo(b2.getA());
if (cmp == 0)
cmp = b1.getB().compareTo(b2.getB());
return cmp;
}
});
One option is to use what I call a custom generic multi-comparator:
list2.sort(getComparator( p -> p.getTestB().getA(),
p -> p.getTestB().getB() ));
private <T> Comparator<T> getComparator( Function<T, ? extends Comparable<?>>... functions ) {
return new Comparator<T>() {
#Override
public int compare(T obj1, T obj2) {
for (Function<T, ? extends Comparable<?>> function : functions) {
Comparable<T> res1 = (Comparable<T>) function.apply(obj1);
Comparable<T> res2 = (Comparable<T>) function.apply(obj2);
int result = res1.compareTo((T) res2);
if ( result != 0 ) {
return result;
}
}
return 0;
}
};
}
It will sort from left to right regarding the order which function parameters are placed. Warnings will be raised although. Because it's very generic.
Keep in mind that the types of the final values to be compared must implement Comparator (which primitive types like Integer already do) and you should deal with null problems (I didn't do it here to keep it short).

How do I create any Collection given the collection name(ArrayList, LinkedList etc) and items in the collection?

I am trying to create a method that accepts the type of collection and the type of items inside the collection.
ArrayList<Integer> ints = getCollection(ArrayList.class, Integer.class);
How do I write the getCollection so that, given any Collection subclass and any Number subclass, the method should create a collection and populate it with random elements of Number or subtype of Number?
If you can, it would be better to pass in a Supplier for the collection, and a Function to convert from Number to the sub-type, such as:
private static final SecureRandom RND = new SecureRandom();
private static final int COUNT_MAX = 100;
public static void main(String[] args) {
ArrayList<Integer> ints = getCollection(ArrayList::new, Number::intValue);
System.out.println(ints);
Set<Double> doubles = getCollection(HashSet::new, Number::doubleValue);
System.out.println(doubles);
}
private static <T extends Collection<U>, U extends Number> T getCollection(
Supplier<T> supplier, Function<Number, U> fn) {
T collection = supplier.get();
int count = RND.nextInt(COUNT_MAX);
for (int i = 0; i < count; i++)
collection.add(fn.apply(RND.nextInt()));
return collection;
}
This way, you won't need any casting.
Update using streams:
private static <T extends Collection<U>, U extends Number> T getCollection(
Supplier<T> supplier, Function<Number, U> fn) {
int count = RND.nextInt(COUNT_MAX);
return RND.ints().limit(count).boxed().map(fn)
.collect(Collectors.toCollection(supplier));
}

Use generic enum classes in maps

I have a map of class names to their enum class and I have method that parses a string like "SomeEnum.FIRST" into the actual object. But Enum.valueOf doesn't accept Class<? extends Enum<?>> while the map cannot store Class<T extends Enum<T>>.
For the code, the map looks something like this:
private static final HashMap<String, Class<? extends Enum<?>>> enumsMap;
static {
enumsMap = new HashMap<>();
// These are two DIFFERENT enum classes!
registerEnum(SomeEnum.class);
registerEnum(AnotherEnum.class);
}
private static void registerEnum(Class<? extends Enum<?>> enumClass) {
enumsMap.put(enumClass.getSimpleName(), enumClass);
}
And here is the parser (removed unnecessary code):
public <T extends Enum<T>> Object[] parse(List<String> strParameters) {
Object[] parameters = new Object[strParameters.size()];
for (int i = 0; i < parameters.length; i++) {
String strParameter = strParameters.get(i);
int delim = strParameter.lastIndexOf('.');
String className = strParameter.substring(0, delim - 1);
String enumName = strParameter.substring(delim + 1);
Class<T> enumClass = (Class<T>) enumsMap.get(className);
parameters[i] = Enum.valueOf(enumClass, enumName);
}
return parameters;
}
And now if I call this parse, my IDE (Android Studio) tells me, that "Unchecked method 'parse(List)' invocation", and afaik this is because of that generic type. If I remove it in parse, it wouldn't compile but the warning disappears. Is there a good way around it?
If you have enums like:
enum Foo {
A, B, C
}
enum Bar {
D, E, F
}
Then you can implement the kind of map you're talking about with the following code.
class MyEnums {
private final Map<String, Class<? extends Enum<?>>> map = new HashMap<>();
public void addEnum(Class<? extends Enum<?>> e) {
map.put(e.getSimpleName(), e);
}
private <T extends Enum<T>> T parseUnsafely(String name) {
final int split = name.lastIndexOf(".");
final String enumName = name.substring(0, split);
final String memberName = name.substring(split + 1);
#SuppressWarnings("unchecked")
Class<T> enumType = (Class<T>) map.get(enumName);
return Enum.valueOf(enumType, memberName);
}
public Object parse(String name) {
return parseUnsafely(name);
}
public Object[] parseAll(String... names) {
return Stream.of(names)
.map(this::parse)
.collect(toList())
.toArray();
}
}
This does not get around an unchecked cast, though; it only hides it from you temporarily. You can see where where SuppressWarnings is used to muffle the warning about enumType. It's generally good practice to apply the warning suppression in as limited a scope as possible. In this case, it's for that single assignment. While this could be a red flag in general, in the present case we know that the only values in the map are, in fact, enum classes, since they must have been added by addEnum.
Then, it can be used as:
MyEnums me = new MyEnums();
me.addEnum(Foo.class);
me.addEnum(Bar.class);
System.out.println(me.parse("Foo.A"));
System.out.println(me.parse("Bar.E"));
System.out.println(Arrays.toString(me.parseAll("Foo.B", "Bar.D", "Foo.C")));
which prints:
A
E
[B, D, C]
You'll notice that I broke parseUnsafely and parse into separate methods. The reason that we don't want to expose parseUnsafely directly is that it makes a guarantee by its return type that we cannot actually enforce. If it were exposed, then we could write code like
Bar bar = me.parseUnsafely("Foo.B");
which compiles, but fails at runtime with a cast class exception.
There is no safe way to have Map values whose generic type depends on the corresponding key.
You can, however, store the enum constants yourself:
private static final Map<String, Map<String, ?>> enumsMap;
static {
enumsMap = new HashMap<>();
// These are two DIFFERENT enum classes!
registerEnum(SomeEnum.class);
registerEnum(AnotherEnum.class);
}
private static <T extends Enum<T>> void registerEnum(Class<T> enumClass) {
Map<String, ?> valuesByName =
EnumSet.allOf(enumClass).stream().collect(
Collectors.toMap(Enum::name, Function.identity()));
enumsMap.put(enumClass.getSimpleName(), valuesByName);
}
public Object[] parse(List<String> strParameters) {
Object[] parameters = new Object[strParameters.size()];
for (int i = 0; i < parameters.length; i++) {
String strParameter = strParameters.get(i);
int delim = strParameter.lastIndexOf('.');
String className = strParameter.substring(0, delim);
String enumName = strParameter.substring(delim + 1);
Map<String, ?> enumValues = enumsMap.get(className);
parameters[i] = enumValues.get(enumName);
if (parameters[i] == null) {
throw new IllegalArgumentException("Class " + className
+ " does not contain constant " + enumName);
}
}
return parameters;
}
What I’ve changed:
enumsMap is now Map<String, Map<String, ?>>. Each value is a Map of enum constants keyed by constant name. ? is sufficient; there is no benefit to remembering that the constant values are enums, since parse returns Object[].
registerEnum has a generic type, to guarantee its argument is a valid enum type. Instead of storing the class argument, it stores that enum’s constants.
parse doesn’t need a generic type, since it returns Object[].
parse does not use any methods of Enum, so generic type safety is no longer a concern.
I fixed a bug: strParameter.substring(0, delim); instead of delim - 1. You want the entire substring up to but not including the period.

Categories

Resources