How to add String[] into Set avoiding exception.
I put array's size to 100, it doesn't need to be fixed size array but
I need to have access to array's members like x[] so i can insert array values.
Set<String[]> s = new TreeSet<String[]>();
String[] x = new String[100];
int i=0;
x[i++] = ...
x[i++] = ...
s.add(x);
Ljava.lang.String; cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1188)
at java.util.TreeMap.put(TreeMap.java:531)
at java.util.TreeSet.add(TreeSet.java:255)
Arrays don't have a "natural order", that's why you got this exception.
One way is to provide a custom comparator when constructing your TreeSet.
Set<String[]> s = new TreeSet<String[]>(new Comparator<String[]>() {
#Override
public int compare(String[] o1, String[] o2) {
//your logic here
}
});
But I don't think you should use a TreeSet for that sort of things because that will be hard to tell how to define an order for comparing your arrays.
IMO your best option is to create a wrapper class, overriding hashcode and equals and put those in an HashSet.
class WrapperStringArray {
private String[] arr;
//constructors, getters, setter and additional methods
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(arr);
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
WrapperStringArray other = (WrapperStringArray) obj;
return Arrays.equals(arr, other.arr);
}
}
Related
Suppose I have this array:
double[][] Q = new double[n1][n2];
I could index the values of Q by using int indexes, such as Q[2][1]. But in my case, n1 is a byte[], not an int. I still know the possible values of n1 (e.g, all possible combinations of the array values). What collection should I use instead of an array?
HashMap<byte[], Double>[] Q = new HashMap[n2];
This was my solution, but I'm not sure it is adequate. To index, I can do
byte[] n1 = {1,0,6,1,4,2,5,1};
Q[1].get(n1);
Is there a better way to do this? Something that is more performant? I think having an array of HashMaps is not ideal, but can I add that int to my key? How?
As stated in the comments,
The problem with using an array as a hashmap key (aside from the fact that it's mutable) is that arrays don't calculate their hashcode based on their contents; so you couldn't actually look up a value in the map unless you have the actual key instance.
So how do I use an array as an indexing key? A stupid solution would be to always convert it to String before using it, but I'm sure there are better and proper solutions.
I think the best way to go for your problem is to define a complex key class, which basically consists of your byte[] and int, write reliable hashCode and equals-Methods for this class, and use it as a key to the HashMap.
This way, you can use the runtime and memory efficiency of a hashmap and encapsulate the complexity of the key in a separate class.
Code to illustrate:
public static class MyKey {
private int i;
private byte[] b;
private MyKey(int i, byte[] b) {
this.i = i;
this.b = b;
}
public static MyKey of(int i, byte ... b) {
return new MyKey(i, b);
}
#Override
public int hashCode() { // Autogenerated from eclipse
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(b);
result = prime * result + i;
return result;
}
#Override
public boolean equals(Object obj) { // Autogenerated from eclipse
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
MyKey other = (MyKey) obj;
if(!Arrays.equals(b, other.b))
return false;
if(i != other.i)
return false;
return true;
}
}
public static void main(String[] args) {
HashMap<MyKey, Double> valueMap = new HashMap<>();
valueMap.put(MyKey.of(1, (byte)2, (byte)4, (byte)7), 0.1);
valueMap.put(MyKey.of(1, new byte[] { 3, 8, 14 }), 0.1);
}
This could be the entirely wrong usage for the datatype, but I'm trying to create a Map-Set of some sort, so before insertion into the map I'm trying to use
//Material is custom class
Map<Integer, Material> x = TreeMap();
if(!x.containsValue(new Material))
x.put(int val, new Material);
My expectation of this is that it would compare two objects generated from the same data would return true, but it appears that this is not the case. Why is that, and is there an existing solution other than iterating through the entire map to find out if any element contains a Material where all fields are the same as the incoming?
Having a well defined class Material
public class Material extends Comparable<Material> {
final int a;
final String b;
...
public Material(int a, String b, ...) {
this.a = a;
this.b = b;
}
#Override
int compareTo(Material other) {
int cmp = Integer.compare(a, other.a);
if (cmp == 0) {
cmp = b.compareTo(other.b);
}
...
return cmp;
}
#Override
public boolean equals(Object other) {
if (other == null || !(other instanceof Material)) {
return false;
}
return compareTo((Material) other) == 0;
}
#Override int hashCode() {
return ...;
}
A Set suffices:
Set<Material> materials = new HashSet<>(); // hashCode+equals
Set<Material> materials = new TreeSet<>(); // compareTo
Now I have made the fields of Material final so the object is immutable, as changing would play havoc with the sets ordering.
For a mutable quality, like quantity:
Map<Material, Integer> stock = new HashMap<>();
Material material = new Material(...);
stock.put(material, 100);
int quantity = stock.getOrDefault(material, 0); // Java 8
int quantity = 0; // Java 7
{
Integer q = stock.get(material);
if (q != null) {
quantity = q;
}
}
For Java practice, I am trying to create a method inside my EmployeesDirectory Class that:
Removes Duplicate entries from the array
The array should be the same length after removing duplicates
Non-Empty entries should be making a contiguous sequence at the beginning of the array - and the actualNum should keep a record of the entries
Duplicate Means: Same Name, Position and Salary
Here is my Current Code:
I am unsure on how to implement this - any help would be appreciated
class EmployeeDirectory {
private Employee dir[];
private int size;
private int actualNum;
public EmployeeDirectory(int n) {
this.size = n;
dir = new Employee[size];
}
public boolean add(String name, String position, double salary) {
if (dir[size-1] != null) {
dir[actualNum] = new Employee(name, position, salary);
actualNum++;
return true;
} else {
return false;
}
}
}
I'd rather you did not write a distinct method for removing duplicates. If I were you, I would search for duplicates in add method and then instantly decide whether I need to add Employee.
Also, why don't you use Sets (link for HashSet) instead of arrays for your purpose? Sets by their own definition disallow adding duplicates, so they seem to be appropriate as a solution
First of all, Override equals and hashCode methods in Employee class as follow
#Override
public boolean equals(Object other) {
if(this == other) return true;
if(other == null || (this.getClass() != other.getClass())){
return false;
}
Employee guest = (Employee) other;
return Objects.equals(guest.name, name)
&& Objects.equals(guest.position, position)
&& Objects.equals(guest.salary, salary);
}
#Override
public int hashCode() {
return Arrays.hashCode(new Object[] {
name,
position,
salary
});
}
Then you can use Stream API distinct method to remove duplicates
Returns a stream consisting of the distinct elements (according to
Object.equals(Object)) of this stream.
You can do it like so
Employee e1 = new Employee("John", "developer", 2000);
Employee e2 = new Employee("John", "developer", 2000);
Employee e3 = new Employee("Fres", "designer", 1500);
Employee[] allEmployees = new Employee[100];
allEmployees[0] = e1;
allEmployees[1] = e2;
allEmployees[2] = e3;
allEmployees = Arrays.asList(allEmployees).stream().distinct()
.toArray(Employee[]::new);
Arrays.asList(allEmployees).forEach(System.out::println);
Output: (keeping both empty and non-empty entries)
John developer 2000.0
Fres designer 1500.0
null
Unfortunately, I have not got the Employee class to verify my code, but try this:
void removeDuplicates() {
int length = dir.length;
HashSet set = new HashSet(Arrays.asList(dir));
dir = new Employee[length];
Employee[] temp = (Employee[]) set.toArray();
for (int index = 0; index < temp.length; index++)
dir[index] = temp[index];
}
The code must remain the size of array after deletion the duplicates. At the beginning of array there must be valid Employees, at the end - nulls.
And don't forget to add this at the beginning of your .java file
import java.util.Arrays;
import java.util.HashSet;
If your task states as "remove duplicates from array" (i. e. you cannot use ArrayList or control when adding items), you can use the following approach:
public void removeDuplicates() {
Set<Employee> d = new HashSet<>(); // here to store distinct items
int shift = 0;
for (int i = 0; i > dir.length; i++) {
if (d.contains(dir[i])) { // duplicate, shift += 1
shift++;
} else { // distinct
d.add(dir[i]); // copy to `d` set
dir[i - shift] = dir[i]; // move item left
}
}
for (int i = d.size(); i < dir.length; i++)
dir[i] = null; // fill rest of array with nulls
actualNum = d.size();
}
Here, shift variable stores number of duplicates found in the array so far. Every distinct item is moved to shift positions left in order to make sequence continuous while keeping initial ordering. Then remaining items are altered to nulls.
To make hash-based collections work with Employee instances correctly, you also need to override hashCode() and equals() methods as follows:
public class Employee {
//...
#Override
public int hashCode() {
return Objects.hash(name, position, salary);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!o.getType().equals(this.getType()) return false;
Employee e = (Employee) o;
return Objects.equals(e.name, name)
&& Objects.equals(e.position, position)
&& Objects.equals(e.salary, salary); // or e.salary == salary, if it primitive type
}
}
I have developed a garbage collector friendly String cache for my Android game. Its purpose is to handle Strings for ints. I made a silly mistake implementing it but the bug never disclosed itself in desktop. In Android, however, the cache started returning funny Strings at once:
class IntStringCache {
private final Map<IntStringCache.IntCacheKey, String> cachedStrings = new HashMap<IntStringCache.IntCacheKey, String>();
private final IntCacheKey tempIntCacheKey = new IntCacheKey(0);
public String getStringFor(int i) {
tempIntCacheKey.setIntValue(i);
String stringValue = cachedStrings.get(tempIntCacheKey);
if (stringValue == null) {
stringValue = String.valueOf(i);
// ERROR - putting the same object instead of new IntCachKey(i)
cachedStrings.put(tempIntCacheKey, stringValue);
}
return stringValue;
}
public int getSize() {
return cachedStrings.size();
}
private class IntCacheKey {
private int intValue;
private IntCacheKey(int intValue) {
this.intValue = intValue;
}
private void setIntValue(int intValue) {
this.intValue = intValue;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + intValue;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IntCacheKey other = (IntCacheKey) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (intValue != other.intValue)
return false;
return true;
}
private IntStringCache getOuterType() {
return IntStringCache.this;
}
}
And the tests all of which pass:
public class IntStringCacheTest {
private IntStringCache intStringCache = new IntStringCache();
#Test
public void shouldCacheString() {
// given
int i = 1;
// when
String s1 = intStringCache.getStringFor(i);
String s2 = intStringCache.getStringFor(i);
// then
assertThat(s1).isNotNull();
assertThat(s1).isEqualTo(String.valueOf(i));
assertThat(s1).isSameAs(s2);
}
#Test
public void shouldCacheTwoValues() {
// given
int i1 = 1;
int i2 = 2;
int expectedCacheSize = 2;
// when
String s1 = intStringCache.getStringFor(i1);
String s2 = intStringCache.getStringFor(i2);
// then
assertThat(intStringCache.getSize()).isEqualTo(expectedCacheSize);
assertThat(s1).isSameAs(intStringCache.getStringFor(i1));
assertThat(s2).isSameAs(intStringCache.getStringFor(i2));
}
}
Note:
assertThat(String.valueOf(1)).isSameAs(String.valueOf(1));
fails.
The fact that the second test passes is interesting as, with the bug, there should be one key in the map that gets updated. This may be explained with hashCode() that could make the same key go into two different buckets inside HashMap. But how is it possible that the same key (even if in two buckets) returns the same two Stings? It seems that even though there is a bug in the code the HashMap does the job correctly.
My Android Java implementation, on the other hand, returns wrong number Strings with this bug at once.
You should consider replacing this entire class with SparseArray or its Support Library equivalent SparseArrayCompat (if you need it on <3.0 devices) as they are specifically designed to map integers to objects in a memory efficient way.
I´m trying to write the code for the problem described in my previous topic. The suggested solution was to use hashmaps to find similar entries in multiple arrays (arrays have the same number of columns, but they might have different number of rows).
Below is my sample code based on a code snippet of the user John B provided here. For simplicity and for debugging purpose, I created just 3 different one-dimensional rows instead of two-dimensional arrays. Also, for simplicity, the function equalRows should return true or false instead of row indexes.
So, in the below code the function equalRows should return false, because array3 has {1,3,4} and it does have {1,2,3}. Instead the function returns true. Why does it happen?
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) {
int[] array1 = {1,2,3};
int[] array2 = {1,2,3};
int[] array3 = {1,3,4};
boolean answ = equalRows(array1,array2,array3);
System.out.println(answ);
}
static class Row extends Object {
private int value;
private volatile int hashCode = 0;
public Row(int val) {
this.value = val;
}
#Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if((obj == null) || (obj.getClass() != this.getClass()))
return false;
// object must be Row at this point
Row row = (Row)obj;
return (value == row.value);
}
#Override
public int hashCode () {
final int multiplier = 7;
if (hashCode == 0) {
int code = 31;
code = multiplier * code + value;
hashCode = code;
}
return hashCode;
}
}
private static Map<Row, Integer> map(int[] array) {
Map<Row, Integer> arrayMap = new HashMap<Row, Integer>();
for (int i=0; i<array.length; i++)
arrayMap.put(new Row(array[i]), i);
return arrayMap;
}
private static boolean equalRows(int[] array1, int[] array2, int[] array3){
Map<Row, Integer> map1 = map(array1);
Map<Row, Integer> map2 = map(array2);
for (int i=0; i<array3.length; i++){
Row row = new Row(array3[i]);
Integer array1Row = map1.get(row);
Integer array2Row = map2.get(row);
if (array1Row != null || array2Row != null) {
return false;
}
}
return true;
}
}
Edit#1
Code is updated subject to suggested solution.
Edit#2
I checked out the suggested solution, but the function returns false even for: int[] array1 = {1,2,3}; int[] array2 = {1,2,3}; int[] array3 = {1,2,3}, although it should be true. I think the problem is with the function hashcode. So, any solution?
This line is wrong, it immediately returns true:
if (array1Row != null && array2Row != null) {
return true;
}
What you must do is this (completely invert the logic):
if (array1Row == null || array2Row == null) {
return false;
}
It is only getting as far as testing the first element in each array and returning true because they match.
You need to return false if any fail to match and then return true if there are no failures.
I'd also put a test of the lengths at the start of the equalRows method.