You could do index manipulation. Basically, you could make a list which alternates between a positive and negative element.
If i
is an index in your application, you can calculate the actual index using i>=0 ? i*2 : i*2+1
. However, this would require extra memory for possible unused coordinates in the other direction. Also, you would need to store the size of both the positive and negative part distinctively:
public class BidirectionalList<T>{
private List<T> data=new ArrayList<>();
private int posSize=0;
private int negSize=0;
public void insert(T value, boolean positive){
if(positive){
if(posSize>negSize){
data.add(value);//add positive element
data.add(null);//add negative placeholder
}else{
data.set((posSize+1)*2, value);//set position
}
} else {
if(negSize>posSize){
data.add(null);//add positive placeholder
data.add(value);//add negative element
}else{
data.set((negSize+1)*2+1, value);//set position
}
}
}
public T get(int i){
if(!containsIndex(i)){
throw new IndexOutOfBoundsException();
}
return data.get(i>=0 ? i*2 : i*2+1);
}
public boolean containsIndex(int i){
return i>=0 ? posSize>=i : negSize> -i;
}
}
Note that this approach loses a bit of locality and uses unnecessary space in case the positive and negative size are different.
Alternatively, you could create your own data structure with an array growing in both directions. That would require you to save an offset. That approach would not waste as much space if there is a significant difference between the positive and negative size.
public class BidirectionalList<T>{
private static final int INITIAL_SIZE = 32;//how big the array is supposed to be by default
private static final int GROWTH_FACTOR = 1.25;//how much the array should be increased if it runs out of space
private T[] data = (T[])new Object[INITIAL_SIZE];
private int origin = data.length/2;//position of semantic index 0
private int positiveLength = 0;//number of positive elements
private int negativeLength = 0;//number of negative elements
public void insert(T value, boolean positive){
int i;
if(positive){
i = positiveLength + origin;
if(i>=data.length){//grow positively
data=Arrays.copyOf(data, (int)(data.length*GROWTH_FACTOR));//create copy with bigger size
}
positiveLength++;
}else{
i = origin - negativeLength - 1;
if(i<0){//grow negatively
T[] newData = (T[]) newObject[(int)(data.length*GROWTH_FACTOR)]
int lengthDiff = newData.length-data.length;
System.arraycopy(data, 0, newData, lengthDiff, data.length);//copy elements over
origin+=lengthDiff;
data = newData;
i += lengthDiff;
}
negativeLength++;
}
data[i]=value;
}
public T get(int i){
if(!containsIndex(i)){
throw new IndexOutOfBoundsException();
}
return data[i+origin];
}
public boolean containsIndex(int i){
return i>=0 ? i<positiveLength : -i<negativeLength;
}
}
This preserves locality of the references as neighbouring elements are always next to each other.
Since you want to implement a coordinate plane, I did not include inserting at arbitrary positions but only at the positive and negative ends. Also note that storing your whole coordinate plane might be memory intensive so you might only want to store the part you are currently interested in.