Skip to content

Commit

Permalink
Merge pull request #41960 from nipunayf/fix-hash-put
Browse files Browse the repository at this point in the history
Handle hash collisions in the put method in tables
  • Loading branch information
KavinduZoysa authored Mar 20, 2024
2 parents c7c5ee4 + 3672912 commit 6ef939c
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -571,22 +571,19 @@ public void addData(V data) {
maxIntKey = ((Long) TypeChecker.anyToInt(key)).intValue();
}

Map.Entry<K, V> entry = new AbstractMap.SimpleEntry(key, data);
Long hash = TableUtils.hash(key, null);

if (entries.containsKey(hash)) {
updateIndexKeyMappings(hash, key, data);
List<Map.Entry<K, V>> extEntries = entries.get(hash);
Map.Entry<K, V> entry = new AbstractMap.SimpleEntry(key, data);
extEntries.add(entry);
List<V> extValues = values.get(hash);
extValues.add(data);
return;
}

ArrayList<V> newData = new ArrayList<>();
newData.add(data);
Map.Entry<K, V> entry = new AbstractMap.SimpleEntry(key, data);
putData(key, data, newData, entry, hash);
putNewData(key, data, entry, hash);
}

public V getData(K key) {
Expand All @@ -603,9 +600,6 @@ public V getData(K key) {
}

public V putData(K key, V data) {
ArrayList<V> newData = new ArrayList<>();
newData.add(data);

Map.Entry<K, V> entry = new AbstractMap.SimpleEntry(key, data);
Object actualKey = this.keyWrapper.wrapKey((MapValue) data);
Long actualHash = TableUtils.hash(actualKey, null);
Expand All @@ -616,10 +610,15 @@ public V putData(K key, V data) {
ErrorHelper.getErrorDetails(ErrorCodes.KEY_NOT_FOUND_IN_VALUE, key, data));
}

return putData(key, data, newData, entry, hash);
if (entries.containsKey(hash)) {
return updateExistingEntry(key, data, entry, hash);
}
return putNewData(key, data, entry, hash);
}

private V putData(K key, V value, List<V> data, Map.Entry<K, V> entry, Long hash) {
private V putNewData(K key, V value, Map.Entry<K, V> entry, Long hash) {
List<V> data = new ArrayList<>();
data.add(value);
updateIndexKeyMappings(hash, key, value);
List<Map.Entry<K, V>> entryList = new ArrayList<>();
entryList.add(entry);
Expand All @@ -632,13 +631,30 @@ public V putData(V data) {
MapValue dataMap = (MapValue) data;
checkInherentTypeViolation(dataMap, tableType);
K key = this.keyWrapper.wrapKey(dataMap);

ArrayList<V> newData = new ArrayList<>();
newData.add(data);

Map.Entry<K, V> entry = new AbstractMap.SimpleEntry<>(key, data);
Long hash = TableUtils.hash(key, null);
return putData((K) key, data, newData, entry, hash);

if (entries.containsKey(hash)) {
return updateExistingEntry(key, data, entry, hash);
}
return putNewData((K) key, data, entry, hash);
}

private V updateExistingEntry(K key, V data, Map.Entry<K, V> entry, Long hash) {
updateIndexKeyMappings(hash, key, data);
List<V> valueList = values.get(hash);
List<Map.Entry<K, V>> entryList = entries.get(hash);
for (Map.Entry<K, V> extEntry: entryList) {
// Handle hash-collided entries
if (TypeChecker.isEqual(key, extEntry.getKey())) {
entryList.remove(extEntry);
valueList.remove(extEntry.getValue());
break;
}
}
entryList.add(entry);
values.get(hash).add(data);
return data;
}

public V remove(K key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,6 @@ public void testGetValue() {
BRunUtil.invoke(compileResult, "testGetValue");
}

@Test
public void testHashCollisionHandlingScenarios() {
BRunUtil.invoke(compileResult, "testHashCollisionHandlingScenarios");
}

@Test
public void testHashCollisionInQuery() {
BRunUtil.invoke(compileResult, "testHashCollisionInQuery");
}

@Test
public void testGetKeysOfHashCollidedKeys() {
BRunUtil.invoke(compileResult, "testGetKeysOfHashCollidedKeys");
}

@Test
public void testGetKeyList() {
Object result = BRunUtil.invoke(compileResult, "testGetKeyList");
Expand Down Expand Up @@ -602,4 +587,20 @@ public void testTableIterationAfterPut() {
BRunUtil.invoke(compileResult, "testTableIterationAfterPut3");
BRunUtil.invoke(compileResult, "testTableIterationAfterPut4");
}

@Test(dataProvider = "functionsToTestHashCollisionInTable")
public void testHashCollisionInTable(String function) {
BRunUtil.invoke(compileResult, function);
}

@DataProvider
public Object[] functionsToTestHashCollisionInTable() {
return new String[]{
"testHashCollisionHandlingScenarios",
"testHashCollisionInQueryWithAdd",
"testHashCollisionInQueryWithPut",
"testHashCollisionInFilter",
"testGetKeysOfHashCollidedKeys"
};
}
}
68 changes: 65 additions & 3 deletions langlib/langlib-test/src/test/resources/test-src/tablelib_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -355,36 +355,98 @@ function testHashCollisionHandlingScenarios() {

}

function testHashCollisionInQuery() {
function testHashCollisionInQueryWithAdd() {
table<record {readonly int|string|float? k;}> key(k) tbl1 = table [{k: "10"}];
table<record {readonly int|string|float? k;}> tbl2 = table [{k: 0}];
table<record {readonly int|string|float? k;}> key(k) tbl3 = table [
{k: "10"}, {k: 5}, {k: ()}, {k: -31}, {k: 0}, {k: 100.05}, {k: 30}];

tbl1.add({k: 5});
tbl1.add({k: ()});
tbl1.add({k: -31});
tbl1.add({k: 0});
tbl1.add({k: 100.05});
tbl1.add({k: 30});
table<record {|readonly int|string|float? k; anydata...;|}> tbl3 =
assertEquals(tbl3, tbl1);

table<record {|readonly int|string|float? k; anydata...;|}> tbl4 =
from var tid in tbl1
where tid["k"] == 0
select tid;
assertEquals(tbl2, tbl3);
assertEquals(tbl2, tbl4);

_ = tbl1.remove(());
table<record {|readonly int|string|float? k; anydata...;|}> tbl5 =
from var tid in tbl1
where tid["k"] == 0
select tid;
assertEquals(tbl2, tbl5);
}

function testHashCollisionInQueryWithPut() {
table<record {readonly int|string|float? k; int v;}> key(k) tbl1 = table [{k: "10", v: 1}];
table<record {readonly int|string|float? k; int v;}> tbl2 = table [{k: 0, v: 2}];
table<record {readonly int|string|float? k; int v;}> key(k) tbl3 = table [
{k: "10", v: 1}, {k: 5, v: 2}, {k: 0, v: 2}, {k: (), v: 3}, {k: -31, v: 4}, {k: 100.05, v: 1}, {k: 30, v: 2}];

tbl1.put({k: 5, v: 1});
tbl1.put({k: (), v: 1});
tbl1.put({k: -31, v: 4});
tbl1.put({k: 0, v: 2});
tbl1.put({k: 5, v: 2});
tbl1.put({k: 100.05, v: 1});
tbl1.put({k: 30, v: 2});
tbl1.put({k: (), v: 3});
assertEquals(tbl3, tbl1);

table<record {|readonly int|string|float? k; anydata...;|}> tbl4 =
from var tid in tbl1
where tid["k"] == 0
select tid;
assertEquals(tbl2, tbl4);

_ = tbl1.remove(());
table<record {|readonly int|string|float? k; anydata...;|}> tbl5 =
from var tid in tbl1
where tid["k"] == 0
select tid;
assertEquals(tbl2, tbl5);
}

function testHashCollisionInFilter() {
table<record {readonly int|string|float? k;}> key(k) tbl1 = table [{k: "10"}];
table<record {readonly int|string|float? k;}> key(k) tbl2 = table [{k: ()}, {k: 0}];

tbl1.put({k: 5});
tbl1.put({k: ()});
tbl1.put({k: -31});
tbl1.put({k: 0});
tbl1.put({k: 100.05});
tbl1.put({k: 30});

table<record {readonly int|string|float? k;}> tbl4 = tbl1.filter(
function(record {readonly int|string|float? k;} tid) returns boolean
=> tid["k"] == 0 || tid["k"] == ());
assertEquals(tbl2, tbl4);
}
public function testGetKeysOfHashCollidedKeys() {
table<record {readonly int? k;}> key(k) tbl1 = table [
{k: 5}, {k: 0}, {k: ()}, {k: 2}
];

assertEquals(tbl1.keys(), [5, 0, (), 2]);

table<record {readonly int? k;}> key(k) tbl2 = table [
{k: 5}, {k: 0}, {k: 2}
];
tbl2.add({k: ()});
assertEquals(tbl2.keys(), [5, 0, 2, ()]);

table<record {readonly int? k;}> key(k) tbl3 = table [
{k: 5}, {k: 0}, {k: 2}
];
tbl3.put({k: ()});
assertEquals(tbl3.keys(), [5, 0, 2, ()]);
}

function testGetKeyList() returns any[] {
Expand Down

0 comments on commit 6ef939c

Please sign in to comment.