Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
155 changes: 146 additions & 9 deletions src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,180 @@
package de.comparus.opensource.longmap;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;

public class LongMapImpl<V> implements LongMap<V> {

private static final int DEFAULT_CAPACITY = 16;

private static final double DEFAULT_LOAD_FACTOR = 0.75;

private final List<LinkedList<Entry<V>>> buckets;

private int capacity;

private int size;

private final double loadFactor;

public LongMapImpl() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public LongMapImpl(int capacity, double loadFactor) {
this.capacity = capacity;
this.buckets = new ArrayList<>(capacity);
this.loadFactor = loadFactor;
IntStream.range(0, capacity).forEach(i -> buckets.add(null));
}

@Override
public V put(long key, V value) {
return null;
if (size >= capacity * loadFactor) {
resize();
}

int index = getIndex(key);
LinkedList<Entry<V>> bucket = buckets.get(index);

if (bucket == null) {
bucket = new LinkedList<>();
buckets.set(index, bucket);
}

for (Entry<V> entry : bucket) {
if (entry.getKey() == key) {
entry.setValue(value);
return entry.value;
}
}

bucket.add(new Entry<>(key, value));
size++;
return bucket.stream()
.filter(entry -> entry.key == key)
.findFirst()
.map(entry -> entry.value)
.orElse(null);
}

@Override
public V get(long key) {
return null;
int index = getIndex(key);
LinkedList<Entry<V>> bucket = buckets.get(index);

if (bucket == null) {
return null;
}

Optional<Entry<V>> valueEntry = bucket.stream()
.filter(entry -> entry.getKey() == key)
.findFirst();

return valueEntry.map(Entry::getValue).orElse(null);
}

@Override
public V remove(long key) {
return null;
int index = getIndex(key);
LinkedList<Entry<V>> bucket = buckets.get(index);

if (bucket == null) {
return null;
}

V removedValue = null;
for (Entry<V> entry : bucket) {
if (entry.getKey() == key) {
bucket.remove(entry);
size--;
removedValue = entry.value;
}
}

return removedValue;
}

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

@Override
public boolean containsKey(long key) {
return false;
return get(key) != null;
}

@Override
public boolean containsValue(V value) {
return false;
return buckets.stream()
.filter(Objects::nonNull)
.anyMatch(bucket -> bucket.stream()
.anyMatch(entry -> entry.value == value)
);
}

@Override
public long[] keys() {
return null;
return buckets.stream()
.filter(Objects::nonNull)
.flatMapToLong(bucket -> bucket.stream().mapToLong(entry -> entry.key))
.distinct()
.toArray();
}

@Override
public V[] values() {
return null;
return (V[]) buckets.stream()
.filter(Objects::nonNull)
.flatMap(bucket -> bucket.stream().map(entry -> entry.value))
.toArray();
}

@Override
public long size() {
return 0;
return size;
}

public int getCapacity() {
return capacity;
}

@Override
public void clear() {
IntStream.range(0, buckets.size()).forEach(i -> buckets.set(i, null));
size = 0;
}

private int getIndex(long key) {
return Math.abs(Objects.hashCode(key)) % capacity;
}

private void resize() {
int newCapacity = capacity * 2;
int capacityDelta = newCapacity - capacity;
List<LinkedList<Entry<V>>> newReservedBuckets = new ArrayList<>(capacityDelta);

IntStream.range(0, capacityDelta).forEach(bucket -> newReservedBuckets.add(null));
buckets.addAll(newReservedBuckets);
capacity = newCapacity;
}

@Data
@AllArgsConstructor
private class Entry<T> {

private final long key;

private T value;

}

}
177 changes: 177 additions & 0 deletions src/test/java/de/comparus/opensource/longmap/LongMapImplTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package de.comparus.opensource.longmap;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;


class LongMapImplTest {

private LongMap<String> testMap;

@BeforeEach
void setUp() {
testMap = new LongMapImpl<>();
}

@Test
void testPut() {
// given
long[] expectedKeys = {1L, 2L, 3L};
String[] expectedValues = {"one", "two", "three"};

// when
IntStream.range(0, expectedKeys.length)
.forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));

// then
assertEquals(3, testMap.size());
assertArrayEquals(expectedKeys, testMap.keys());
assertArrayEquals(expectedValues, testMap.values());
}

@Test
void testPut_WhenEntryIsPresent() {
// given
long[] expectedKeys = {1L, 2L, 3L};
String[] expectedValues = {"one", "two", "three"};

// when
IntStream.range(0, expectedKeys.length)
.forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
testMap.put(expectedKeys[1], "twotwo");
expectedValues[1] = "twotwo";

// then
assertEquals(3, testMap.size());
assertArrayEquals(expectedKeys, testMap.keys());
assertArrayEquals(expectedValues, testMap.values());
}

@Test
void testGet() {
// given
long expectedKey = 1L;
String expectedValue = "one";

// when
testMap.put(expectedKey, expectedValue);
String actualValue = testMap.get(expectedKey);

// then
assertEquals(expectedValue, actualValue);
}

@Test
void testGet_WhenEntryIsNotFound() {
// when
String actualValue = testMap.get(1L);

// then
assertNull(actualValue);
}

@Test
void testRemove() {
// given
long expectedKey = 1L;
String expectedValue = "one";

// when
testMap.put(expectedKey, expectedValue);
String deletedValue = testMap.remove(expectedKey);

// then
assertEquals(expectedValue, deletedValue);
}

@Test
void testRemove_WhenEntryIsNotFound() {
// when
String deletedValue = testMap.remove(1L);

// then
assertNull(deletedValue);
}

@Test
void testIsEmpty() {
// given
long expectedKey = 1L;
String expectedValue = "one";

// when
testMap.put(expectedKey, expectedValue);
boolean isEmpty = testMap.isEmpty();

// then
assertFalse(isEmpty);
}

@Test
void testContainsKey() {
// given
long[] expectedKeys = {1L, 2L, 3L};
String[] expectedValues = {"one", "two", "three"};

// when
IntStream.range(0, expectedKeys.length)
.forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
boolean containsKey = testMap.containsKey(expectedKeys[1]);

// then
assertTrue(containsKey);
}

@Test
void testContainsValue() {
// given
long[] expectedKeys = {1L, 2L, 3L};
String[] expectedValues = {"one", "two", "three"};

// when
IntStream.range(0, expectedKeys.length)
.forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
boolean containsValue = testMap.containsValue(expectedValues[1]);

// then
assertTrue(containsValue);
}

@Test
void testClear() {
// given
long[] expectedKeys = {1L, 2L, 3L};
String[] expectedValues = {"one", "two", "three"};

// when
IntStream.range(0, expectedKeys.length)
.forEach(i -> testMap.put(expectedKeys[i], expectedValues[i]));
boolean nonEmptyBeforeClear = !testMap.isEmpty();
testMap.clear();
boolean emptyAfterClear = testMap.isEmpty();

// then
assertTrue(nonEmptyBeforeClear);
assertTrue(emptyAfterClear);
}

@Test
void testResize() {
LongMapImpl<String> testResizeMap = new LongMapImpl<>();
final int TEST_SIZE = 17;
final int EXPECTED_CAPACITY = 32;
IntStream.range(0, TEST_SIZE).forEach(i -> testResizeMap.put(++i, "test"));

assertEquals(TEST_SIZE, testResizeMap.size());
assertEquals(EXPECTED_CAPACITY, testResizeMap.getCapacity());
}

}