# Index Mapping (or Trivial Hashing) With Negatives allowed in Java

Index mapping, also known as trivial hashing, is a technique used to map an array element to an index in a new array. This can be used to efficiently perform operations such as finding duplicates or counting occurrences of elements in an array. One common implementation of index mapping is to use an array where the indices correspond to the elements of the original array, and the values represent the number of occurrences of each element. This approach is efficient, as accessing an element in an array by index takes constant time.

However, when the elements of the array can be negative, an index mapping approach becomes more challenging. In this case, we need to ensure that the negative indices are mapped to positive indices in the new array. One way to achieve this is by adding a fixed offset value to the indices. For example, if the array contains negative values and the minimum value is -10, we can add 10 to every index to obtain a valid range of indices from 0 to (max-min)+10.

## Handling negative values in trivial hashing

### Using Boolean Array

When dealing with negative numbers in index mapping or trivial hashing in Java, we can use an offset value to shift the range of possible input values to start from zero. It can be achieved by finding the minimum negative value in the input array and adding its absolute value to all elements.

For example, consider the input array {-3, -1, 2, 5, -4}. The minimum negative value in this array is -4. Adding the absolute value of -4 (which is 4) to all elements in the array gives us {1, 3, 6, 9, 0}. We can now create an index mapping array of size 10 (the maximum value in the array plus 1) and use this offset value of 4 to map the values in the input array to indices in the mapping array.

When accessing an element in the mapping array, we can simply subtract the offset value to retrieve the original value. For example, if we want to retrieve the value at index 2 (which corresponds to the original value 2), we would access the mapping array at index 6 (2 + 4) and then subtract the offset value of 4 to obtain the original value 2.

In summary, when handling negative numbers in index mapping or trivial hashing in Java, we can use an offset value to shift the range of possible input values to start from zero, and then subtract this offset value when retrieving the original value from the mapping array.

a) Initialize a hash matrix with all values set to zero.

b) Traverse through the given array.

1. If the element is non-negative, set the corresponding hash value in the matrix as 1 at [ele].
2. However, if the element is negative, take its absolute value and set the corresponding hash value as 1 at [ele].

c) To search for a specific element in the array:

1. Check if the given element, X, is non-negative.
2. If X is non-negative, check if the value at [X] is 1 or not.
3. If it is 1, then the element is present in the array.
4. Otherwise, it is not present.

d) However, if X is negative:

1. Take the absolute value of X.
2. Check if the value at [X] is 1 or not.
3. If it is 1, then the element is present in the array.
4. Otherwise, it is not present.

### Implementation:

Filename: IndexMapping.java

Output:

```Present
```

Explanation:

This Java program implements direct index mapping with negative values allowed. The program initializes a hash matrix with all values set to false. It then traverses through the given array and checks whether each element is negative or non-negative. If the element is non-negative, the corresponding hash value in the matrix is set as true at [ele]. However, if the element is negative, the absolute value of the element is taken and the corresponding hash value is set as true at [ele]. To search for a specific element in the array, the program checks if the given element, X, is non-negative or negative. If X is non-negative, it checks if the value at [X] is true or not. If it is true, then the element is present in the array, otherwise, it is not present. Similarly, if X is negative, it takes the absolute value of X and checks if the value at [X] is true or not.

Complexity Analysis:

The time complexity of inserting elements into the hash matrix is O(n), where n is the number of elements in the given array. The time complexity of searching for an element in the hash matrix is O(1) because we directly access the matrix using the index of the element. Therefore, the overall time complexity of the algorithm is O(n) for the insertion step and O(1) for the search step.

The space complexity of the algorithm is O(MAX+1), which is the size of the hash matrix. However, since the size of the matrix is fixed to 1001, the space complexity can be considered constant.

In summary, the time complexity of the algorithm is O(n) for the insertion step and O(1) for the search step, and the space complexity is O(1).

### Using Modulo Operator

To implement trivial hashing in Java, you can use the built-in modulo operator (%) to calculate the hash code of a given key.

Filename: TrivialHashing.java

Output:

```12 is not present in the hash table.
```

Explanation: The above Java program implements the Index Mapping (or Trivial Hashing) technique to insert and search elements in a hash table. The program initializes the hash table with all elements set to -1, and uses a hash function that maps an element to an array index by taking the modulus of the element with the table size. To handle negative keys, the hash function uses an additional step of adding the table size to the result of the modulus operation and then taking the modulus again to ensure that the result is always non-negative. The insert() function inserts an element into the hash table by finding an empty slot using linear probing, while the search() function searches for an element in the hash table by probing until it either finds the element or encounters an empty slot. The main() function tests the program by inserting an array of both negative and positive keys, and searching for the key 12.

Complexity Analysis:

The time complexity of the Trivial Hashing algorithm depends on the distribution of keys and the size of the hash table. In the worst case, where all the keys collide, the time complexity of both insert and search operations will be O(n), where n is the size of the hash table. However, with a well-distributed set of keys, the expected time complexity of both operations will be O(1), as most collisions will be resolved through linear probing. In terms of space complexity, the hash table requires O(n) space, where n is the size of the table. In this implementation, the table size is fixed to 20, which is relatively small, so the algorithm will likely perform well as long as the input is well-distributed.

## Performance considerations of trivial hashing

Trivial hashing is a simple and efficient way to store and retrieve data in Java, but there are some performance considerations that we need to keep in mind.

One of the main considerations is the size of the array. If the array is bigger, there will be a lot of collisions, leading to slower retrieval times. On the other hand, if the array is smaller, there will be a lot of wasted space, leading to higher memory usage.

Another consideration is the distribution of the data elements. If the data elements are not distributed evenly, there will be a lot of collisions, leading to slower retrieval times. For example, if we want to store students' grades and all the students have student ids that are multiples of 10, there will be a lot of collisions, leading to slower retrieval times.

1. Collisions: Trivial hashing is prone to collisions, where different keys may be mapped to the same hash value. This can lead to degraded performance and increased lookup time. To mitigate this, techniques such as chaining or open addressing can be used.
2. Distribution: The distribution of keys can affect the performance of trivial hashing. If the keys are not evenly distributed, some hash values may be more frequently accessed than others, leading to increased collisions and slower lookup times. To address this, techniques such as universal hashing can be used.
3. Table size: The hash table size can affect the performance of trivial hashing. A table that is too small may result in a high collision rate, while a table that is too large may result in wasted memory. Choosing an appropriate table size based on the expected number of keys can improve performance.
4. Key size: The size of the keys can also affect the performance of trivial hashing. If the keys are smaller, the modulo operation can become affordable, leading to slower lookup times. Techniques such as a smaller hash function or a different hashing method can address this.
5. Load factor: The load factor is the ratio of the number of keys to the hash table size. Choosing an appropriate load factor based on the expected number of keys can improve performance. A high load factor can lead to increased collisions and slower lookup times, while a low load factor can result in wasted memory.
6. Rehashing: As the number of keys increases, it may be necessary to rehash the table to improve performance. Rehashing involves creating a larger table and moving the keys to new locations. This can be an expensive operation, but it may be necessary to maintain good performance over time.
7. Security: Trivial hashing is insecure and vulnerable to birthday and hash flooding attacks. If security is a concern, a more secure hashing method such as SHA-2 or SHA-3 should be used.

### Feedback   