List<List<List<Object>>> or List[][]

Even more for the evening.

Since I can’t get type safety in ArrayList[][], I have to do ArrayList<ArrayList<ArrayList>>.
I think it’s because I can’t initialize all those arraylists with the amount of slots I want from the start like I can with ArrayList[][].

This throws the error:


map.getGrid().get(newCellX).get(newCellY).add(e);

Is there a way to initialize those with the amount of slots needed? Is it better if I just make an ArrayList[][] and cast to the type (theres only one type inside of it)? I feel that the arrays are better, because I don’t even want them to extrude nor shrink during runtime, but that means I have to use casting, which I’m told is avoided at all costs.

I think either way is bad, there are probably better solutions. I don’t know the range of your cells, but here’s an example of something that might work for you (although not really sure what you’re trying to do):

Map<Integer, Object> map = new HashMap<Integer, Object>();

...

int key = ((x & 0xFFFF) << 16) | (y & 0xFFFF);
Object object = map.get(key);

ArrayList<Entity>[][] grid= new ArrayList[rows][cols];

should just do it…


ArrayList<Item>[] grid= new ArrayList[rows * cols];
ArrayList<Item> cellItems = grid[y * cols + x];

Oddly, it seems I can actually return an ArrayList[][] when a class is asking for the grid.

I don’t quite understand how the key in that solution works. Could you explain it?

I understand that this can work, but why is this preferable over the 2D array?

Using bit shifting, you can write x as a short (65536 unique values, is that enough for you?) to the high order bits of an integer (left-most 16) and y as a short to the right 16 bits.

Basically these are the bits of an integer:

00000000 00000000 00000000 00000000

there are 32 total.

let’s say x is 107 and y is 10888. they both have to be shorts (signed or unsigned, doesn’t matter, but you have to stick with whichever you choose)

x (107) in bits is:

00000000 01101011

y (10888) in bits is:

00101010 10001000

now if we right x to the highorder 16 bits (left-most) and y to the loworder 16 bits(right-most) you get a unique key or integer which is:

00000000 01101011 00101010 10001000

(that is 7023240)

Once again, the limit in this approach is that x and y must be either unsigned/signed shorts, that is they must be either in the range of –32,768-32,767 or 0-65,535.

If you need to work with bigger numbers, you can swap out the integer as a key for a long (i’ll explain if you need)

A big pro of this is that you have O(1) speed getting/putting but you also have a lower memory footprint (as opposed to generating an array of size MAX_X * MAX_Y which might be very costly if those are large numbers)

Here’s a solution that will be faster than the one provided by counterp but uses the same semantics (there is no unboxing/boxing each read)


public class TileHashtable {

	public class Entry {
		Entry next;
		int x;
		int y;
		int hash;
		UserLayer.Tile value;

		public Entry(Entry next, int x, int y, int hash, Tile value) {
			this.next = next;
			this.x = x;
			this.y = y;
			this.hash = hash;
			this.value = value;
		}

		int hash() {
			return (x << 5) + y;
		}

		public int getHash() {
			return hash;
		}

		public Entry getNext() {
			return next;
		}

		public Tile getValue() {
			return value;
		}

		public int getX() {
			return x;
		}

		public int getY() {
			return y;
		}
	}
	
	int size;
	Entry [] table;

	public TileHashtable() {
		table = new Entry[64];
	}

    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

	static int getPointHash(int x, int y) {
		return hash((x & 0xFFFF) << 16) | (y & 0xFFFF);
	}

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

	public void clear() {
		for (int i = 0; i < table.length; i++) {
			table[i] = null;
		}
		size = 0;
	}

	public UserLayer.Tile lookup(int x, int y, boolean createIfNotExist) {
		int hash = getPointHash(x, y);
		Entry e;
		int index = indexFor(hash, table.length);
		for (e = table[index]; e != null; e = e.next) {
			if (e.hash == hash && e.x == x && e.y == y) {
				return e.value;
			}
		}

		if (createIfNotExist) {
			UserLayer.Tile val = new UserLayer.Tile();
			table[index] = new Entry(table[index], x, y, hash, val);

			size++;
			return val;
		}

		return null;

	}

	public int size() {
		return size;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	public Iterator<Entry> entryIterator() {
		return new EntryIterator();
	}

	public Iterator<UserLayer.Tile> valueIterator() {
		return new ValueIterator();
	}

	public Iterator<Void> keyIterator() {
		return new KeyIterator();
	}

    private abstract class HashIterator<E> implements Iterator<E> {
        Entry next;	// next entry to return
        int index;		// current slot
        Entry current;	// current entry

        HashIterator() {
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry nextEntry() {
            Entry e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }

        public void remove() {
			throw new UnsupportedOperationException("My Dick");
        }

    }

    private final class ValueIterator extends HashIterator<UserLayer.Tile> {
        public UserLayer.Tile next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<Void> {
        public void next(Point p) {

        }

		public Void next() {
			throw new UnsupportedOperationException("My Dick");
		}
    }

    private final class EntryIterator extends HashIterator<Entry> {
        public Entry next() {
            return nextEntry();
        }
    }


}


replace UserLayer.Tile with whatever obj type you need

HTH

  • David

Thanks for your replies! It made me rethink how I should do this. I have some questions to the code Dx4 and counterp provided.


public class TileHashtable {

	public class Entry {
		Entry next;
		int x;
		int y;
		int hash;
		UserLayer.Tile value;

		public Entry(Entry next, int x, int y, int hash, Tile value) {
			this.next = next;
			this.x = x;
			this.y = y;
			this.hash = hash;
			this.value = value;
		}

		int hash() {
			return (x << 5) + y;
		}

		public int getHash() {
			return hash;
		}

		public Entry getNext() {
			return next;
		}

		public Tile getValue() {
			return value;
		}

		public int getX() {
			return x;
		}

		public int getY() {
			return y;
		}
	}
	
	int size;
	Entry [] table;

	public TileHashtable() {
		table = new Entry[64];
	}

    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

	static int getPointHash(int x, int y) {
		return hash((x & 0xFFFF) << 16) | (y & 0xFFFF);
	}

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

	public void clear() {
		for (int i = 0; i < table.length; i++) {
			table[i] = null;
		}
		size = 0;
	}

	public UserLayer.Tile lookup(int x, int y, boolean createIfNotExist) {
		int hash = getPointHash(x, y);
		Entry e;
		int index = indexFor(hash, table.length);
		for (e = table[index]; e != null; e = e.next) {
			if (e.hash == hash && e.x == x && e.y == y) {
				return e.value;
			}
		}

		if (createIfNotExist) {
			UserLayer.Tile val = new UserLayer.Tile();
			table[index] = new Entry(table[index], x, y, hash, val);

			size++;
			return val;
		}

		return null;

	}

	public int size() {
		return size;
	}

	public boolean isEmpty() {
		return size == 0;
	}

	public Iterator<Entry> entryIterator() {
		return new EntryIterator();
	}

	public Iterator<UserLayer.Tile> valueIterator() {
		return new ValueIterator();
	}

	public Iterator<Void> keyIterator() {
		return new KeyIterator();
	}

    private abstract class HashIterator<E> implements Iterator<E> {
        Entry next;	// next entry to return
        int index;		// current slot
        Entry current;	// current entry

        HashIterator() {
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry nextEntry() {
            Entry e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }

        public void remove() {
			throw new UnsupportedOperationException("My Dick");
        }

    }

    private final class ValueIterator extends HashIterator<UserLayer.Tile> {
        public UserLayer.Tile next() {
            return nextEntry().value;
        }
    }

    private final class KeyIterator extends HashIterator<Void> {
        public void next(Point p) {

        }

		public Void next() {
			throw new UnsupportedOperationException("My Dick");
		}
    }

    private final class EntryIterator extends HashIterator<Entry> {
        public Entry next() {
            return nextEntry();
        }
    }


}



static int getPointHash(int x, int y) {
		return hash((x & 0xFFFF) << 16) | (y & 0xFFFF);
	}

Why is it that we have to do that AND operation on 0xFFF? To me, it looks like it will return the same, if we exclude that. Also, I don’t see a change if I exchange the OR operation with a “+”. Can you please explain this?


static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

This also I can’t seem to get much meaning out of. I’m unsure what this ensures, and I’d very much like a more in-depth explanation that the comment provided :clue:


int hash() {
			return (x << 5) + y;
		}

This again makes me feel like I don’t really know what the hashes do. I though we’d use the last 16 bits to define y, and the first 16 ones to define x. Also, what happended to the AND-operation with the 0xFFFF byte?

Thank you very much for your replies guys, I will be sure to Appriciate after I just hit post :slight_smile:

Assuming that most queries will be local and near neighbors then you can improve cache performance by mapping via either a Z or Hilbert curve. Hilbert is better but more expensive to compute so it’s difficult to say which will be better overall. Of course this is only interesting if cache misses are significant.

Well I’ll answer your first question.

It’s called the bitwise AND operator and using the bitmask 0xFFFF ensures that the value will be within the range of a short.

So if your bits are:

00000000 00011000 00001000 00001000

after applying the bitmask it will become:

00000000 00000000 00001000 00001000

(effectively cutting off all high order bits that aren’t within the range of a short which is the low order 16 bits)

As for the reason, if a number goes over the allowed bits (16) it ends up overwriting other bits (this depends on if it’s x or y, and the values of each), and you end up with a lot of inconsistent overwriting.

However, if you know for a fact that x and y will never be above the or below the min and max values of a short, you don’t need to include the masking.

Note I fixed an error in my original post.

Look up is very cheap: 1 multiply, 1 add, and 1 array index. It also uses less memory, with a 2D array each element in the 2nd dimension is another object (which you also have to initialize).