Say I have a HashMap like this:
message.put(10, "Message 1");
message.put(20, "Message 2");
message.put(30, "Message 3");
message.put(40, "Message 4");
I would get a message comparing a given ID:
if( message.containsKey(sampleValue) ) {
return message.get(sampleValue);
}
But that's useless if sampleValue isn't contained in the message hashmap. Is there a way or function to get it by approximate ID value? For example, if sampleValue is 19, it would return "Message 2". How can I achieve this?
You could use a TreeMap for your task. It contains ceilingKey/floorKey methods returning closest keys from the right/left. So, these methods can be used for finding the closest key and retrieving its corresponding value in O(Log(N)).
class ClosestKeyTreeMap extends TreeMap<Integer, Object> {
public Object getClosestValue(int key) {
Integer leftKey = this.floorKey(key);
Integer rightKey = this.ceilingKey(key);
if (leftKey == null && rightKey == null) {
return null;
} else if (rightKey == null) {
return this.get(leftKey);
} else if (leftKey == null) {
return this.get(rightKey);
}
int leftDiff = key - leftKey;
int rightDiff = rightKey - key;
if (leftDiff < rightDiff) {
return this.get(leftKey);
} else {
return this.get(rightKey);
}
}
}
I believe you are trying to get the value of the closest to the input. Then I will suggest first get the closest integer from the KeySet. Once you get that you just use map.get(closet_int) to retrieve the value.
HashMap<Integer, String> message = new HashMap<>();
message.put(10, "Message 1");
message.put(20, "Message 2");
message.put(30, "Message 3");
message.put(40, "Message 4");
int input = 19; // change the input as you want, I have set it 19 for testing
Integer c = message.keySet().stream()
.min(Comparator.comparingInt(i -> Math.abs(i - input))).get(); // find the closest number to the input
String result = message.get(c);
System.out.println("result : "+result);
Output:
result : Message 2
int input = 19;
int roundInput = BigDecimal
.valueOf(input)
.divide(BigDecimal.TEN,0,BigDecimal.ROUND_HALF_UP)
.multiply(BigDecimal.TEN)
.intValue();
You can extends HashMap to add your specific desired behaviour:
public class ApproximateKeyMap<K, V> extends HashMap<K, V> {
#Override
public V get(Object key) {
// Map your key -> approximated key
K approximatedKey = ...
return super.get(approximatedKey);
}
}
Now use your ApproximateKeyMap instead of your HashMap.
To prevent misusage, you can add a helper function instead of overriding:
public class ApproximateKeyMap<K, V> extends HashMap<K, V> {
public V getApproximate(Object key) {
// Map your key -> approximated key
K approximatedKey = ...
return super.get(approximatedKey);
}
}
Related
I'm trying to create an implementation of a Map collection that stores a pair of key and value items.
The error occurs during runtime when I try to register a key-value pair and hit this line.
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
I'm running out of ideas on what could be the issue, any help is appreciated. Thanks.
//Driver class to test output
public class Driver{
public static void main(String[] args) {
MyMap<String, String> mapInstance = new MyMap<String, String>();
myMap.register("Key1", "Value");
System.out.println(myMap.get("Key1"));
}
}
public class MyMap<K, V> implements MapInterface<K, V>{
private EntryNode<K, V>[] mapEntryList;
private int mapSize = 0;
public MyMap(){
}
public MyMap(int capacity){
this.mapEntryList = new EntryNode[mapSize];
}
//static
public class EntryNode<K, V>{
K keyElement;
V valueElement;
EntryNode<K, V> nextMapEntry;
public EntryNode(K keyElement, V valueElement, EntryNode<K, V> nextMapEntry) {
this.keyElement = keyElement;
this.valueElement = valueElement;
this.nextMapEntry = nextMapEntry;
}
public K getKey() {
return keyElement;
}
public V getValue() {
return valueElement;
}
public EntryNode<K, V> getNextMapEntry() {
return nextMapEntry;
}
public final V setNewValue(V newValueElement) {
V oldValueElement = valueElement;
valueElement = newValueElement;
return oldValueElement;
}
public String toString() {
return "{" + keyElement + ", " + valueElement + "}";
}
}
public int size() {
return mapSize;
}
public V get(K keyElement) {
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
int count = mapSize;
boolean entryPresent = false;
EntryNode tempNode = firstEntry;
while (!entryPresent && mapEntry != null) {
if (keyElement == mapEntry.keyElement) {
entryPresent = true;
return mapEntry.valueElement;
}
else {
mapEntry = mapEntry.nextMapEntry;
}
}
return null;
}
public void register(K newKeyElement, V newValueElement) {
EntryNode<K, V> newEntry = new EntryNode(newKeyElement, newValueElement, null);
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
boolean entryPresent = false;
while (!entryPresent && mapEntry != null) {
if (newKeyElement == mapEntry.keyElement) {
entryPresent = true;
break;
}
else {
if(mapEntry.nextMapEntry == null){
break;
}
mapEntry = mapEntry.nextMapEntry;
}
}
if(!entryPresent){
mapEntry.nextMapEntry = newEntry;
mapSize++;
}
}
public void remove(K removeKeyElement) {
EntryNode previousEntry = null;
EntryNode<K, V> mapEntry = mapEntryList[mapSize];
while (mapEntry != null) {
if (removeKeyElement == mapEntry.keyElement) {
mapEntry.keyElement = null;
mapEntry.valueElement = null;
previousEntry.nextMapEntry = mapEntry.nextMapEntry;
break;
}
else {
previousEntry = mapEntry;
mapEntry = mapEntry.nextMapEntry;
}
}
}
private int getEntriesSize() {
return mapEntryList.length;
}
public String toString() {
StringBuilder stringOutput = new StringBuilder();
for (EntryNode entry : mapEntryList) {
stringOutput.append("[");
while (entry != null) {
stringOutput.append(entry);
if (entry.nextMapEntry != null) {
stringOutput.append(", ");
}
entry = entry.nextMapEntry;
}
stringOutput.append("]");
}
return "{" + stringOutput.toString() + "}";
}
}
Let's say I make a new map, and then call .get() on it.
MyMap<Integer, String> m = new MyMap<>();
m.get(5);
This constructor (the no-args one) means that mapEntryList is never set, which means it defaults to null. Thus, when the get is invoked, the first thing your get method does is dereference the mapEntryList field (the foo[idx] construct dereferences foo). Dereferencing a null value means: NullPointerException is thrown. Clearly the intent of your code is to return null if the key is not in the map, so, that part is broken.
Alternatively, I'll go with:
MyMap<Integer, String> m = new MyMap<>(10);
m.get(5);
This time, I call the second constructor. This constructor takes the 'capacity' value, tosses it in the trash, and makes a 0-sized array. Then, get is invoked, and your code runs:
mapEntry = mapEntryList[mapSize];
This cannot work; you cannot get anything out of a 0-length array. In fact, if you write:
int[] a = new int[5];
a[5];
(as in, you make a new array of size X and then ask for the element with index X), you always get an IndexOutOfBoundsException: in java all arrays are 0-indexed. a[0] is the first element, and new int[1] makes a 1-size int array, so doing a[1] (asking for the second element) on a 1-size array is not possible (it only has one element).
There are about 50 other issues with this code, you really need to take it one small step at a time and debug this code: Instead of just looking at all this code and going: Uh, it doesn't work - you need to debug it:
Write some code, then run it. As you run it, 'mentally run it': Take pen and paper if you have to, walk through the code line by line and figure out what each line should be doing, by hand. Then check what you think should happen vs. what actually happens, either by using a debugger or adding a ton of System.out statements if you have to. There where the code does something different from what you thought? You found a bug. Probably the first in quite a large list of them. Fix it, and keep going until the bugs are gone.
If the size of map is 1 then its key should be returned. If it's size is greater than 1 then iterate over the values in map and the key of that one should be returned which has a max value for a certain property. Below is my code snippet. I want to achieve the same with Java 8 streams api.
public MessageType getOrgReversalTargetMti(Map<MessageType, List<TVO>> map) {
MessageType targetMessageType = null;
if (1 == map.size()) {
targetMessageType = map.keySet().iterator().next();
}
else {
long maxNumber = 0;
for (final MessageType messageType : map.keySet()) {
List<TVO> list = map.get(messageType);
long trace = list.get(0).getTrace();
if (trace > maxNumber) {
maxNumber = trace;
targetMessageType = messageType;
}
}
}
return targetMessageType;
}
You can use a Stream with max() terminal operation:
public MessageType getOrgReversalTargetMti(Map<MessageType, List<TVO>> map) {
return map.entrySet()
.stream()
.max(Comparator.comparing(e -> e.getValue().get(0).getTrace()))
.map(Map.Entry::getKey())
.orElse(null);
}
This question already has answers here:
How do I efficiently iterate over each entry in a Java Map?
(46 answers)
Closed 3 years ago.
I'm trying to iterate through a Linked HashMap keySet however I am having difficulties in getting it to work.
Essentially I am searching the keySet to find a word, and another word. If the second word is immediately after the first word I wish to return true. This is the progress I have made so far.
for (String word : storedWords.keySet()) {
value0++;
if(word.equals(firstWord)){
value1 = value0;
}
if(word.equals(secondWord)){
value2 = value0;
}
int value3 = value2 - 1;
if(value1 == value3){
result = true;
break;
}
}
EDIT:
I've solved my problem and am thankful for all of those who helped. I apologise for making a post when there was a lot of information available on the website however I just lacked the understanding of the logic behind it.
You can avoid iterating over the whole keySet by storing the indices of each element in a separate map; then you can just test if both keys are present and have indices differing by 1. For convenience, encapsulate both maps into an object:
import java.util.*;
public class MapWithIndices<K, V> {
private final Map<K, V> map = new LinkedHashMap<>();
private final Map<K, Integer> indices = new HashMap<>();
public V get(K k) {
return map.get(k);
}
public V put(K k, V v) {
V old = map.put(k, v);
if(old == null) {
indices.put(k, indices.size());
}
return old;
}
public boolean areAdjacent(K k1, K k2) {
Integer i1 = indices.get(k1);
Integer i2 = indices.get(k2);
return i1 != null && i2 != null && i1 + 1 == i2;
}
}
You can add more Map methods (e.g. size) by delegating them to map. However, the remove method cannot be implemented efficiently since it requires recomputing all later indices. If removing from the map is required, an alternative data structure design should be considered; for example, indices can store the original insertion order of each key, and an order statistic tree can be used to count how many existing keys have a lower original-insertion-order.
Map<String, String> map = ...
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "/" + entry.getValue());
}
I think this is sort of in line with what you started with. You might want to test the performance though.
import java.util.LinkedHashMap;
import java.util.Map;
class Testing {
Map<String, Integer> storedWords = new LinkedHashMap<>();
{
storedWords.put("One",1);
storedWords.put("Two",2);
storedWords.put("Three",3);
storedWords.put("Four",4);
storedWords.put("Five",5);
}
public static void main(String[] args) {
Testing t = new Testing();
String firstWord;
String secondWord;
firstWord = "Three";
secondWord = "Five";
System.out.println(t.consecutive(firstWord, secondWord)); // false
firstWord = "Two";
secondWord = "Three";
System.out.println(t.consecutive(firstWord, secondWord)); // true
}
public boolean consecutive(String firstWord, String secondWord) {
boolean foundfirst = false;
for (String word : storedWords.keySet()) {
if (!foundfirst && word.equals(firstWord)){
foundfirst = true;
continue;
}
if (foundfirst) {
if(word.equals(secondWord)){
return true;
} else {
foundfirst = false; // reset to search for the first word again
}
}
}
return false;
}
}
Is there a smart way to check if all keys map to the same value? So the hash table will be as below:
a=>2;
b=>2;
c=>2;
d=>2;
So a,b,d,c and d all map to the same val. I am asking because I have to find the maximum occurrence of a number in a list but it no number is the clear max, I should just print "None". So if 2 number have the max, it means no number is the clear max in terms of occurrence. Also, how do I check if there's no clear max in the values.
Below is what I have so far but it always returns "None":
private static void getMaxOccrrance(String a) {
String[] sNew = a.split(",");
Hashtable<Integer,Integer> nums = new Hashtable<>();
for(String x : sNew) {
int num = Integer.parseInt(x);
if (!nums.containsKey(num)) {
nums.put(num, 1);
} else {
nums.put(num, nums.get(num) + 1);
}
}
int val = 0, max = 1;
for(int keys : nums.keySet()){
if(nums.get(keys) > max){
val = keys;
max = nums.get(keys);
}
}
boolean uniqueMax = true;
int count = 0;
for(int values : nums.values()){
if(val == values) {
count++;
if(count >= 2){
uniqueMax = false;
break;
}
}
}
if(uniqueMax){
System.out.println(val);
}else {
System.out.println("None");
}
}
If you work with Map in a single thread, much more efficient to keep&update this information directly on Map operations, if they are always non-decreasing by value put()s. E. g.
class MyMap<K> {
Map<K, Integer> impl;
K singleMaxKey;
int maxValue;
public void put(K key, int value) {
if (value > maxValue) {
maxValue = value;
singleMaxKey = key;
} else if (value == maxValue && !key.equals(singleMaxKey)) {
sibgleMaxKey = null;
}
impl.put(key, value);
}
}
If you have Java 8 you can do this fairly easily using streams. The following will return a map from each distinct value in a list to its frequency:
Map<Value, Integer> getFrequencyMap(Collection<Value> list) {
return list.stream().distinct()
.collect(Collectors.toMap(value -> value, value -> Collections.frequency(list, value));
}
You can call this with map.values() to get the frequency of all values in the your map.
If you wish to determine if there is more than one value that occurs the same, maximum number of times, you can check the new frequency map to see if the value occurs more than once:
Map<Key, Value> map;
Map<Value, Integer> valueFrequencies = getFrequencyMap(map.values());
int maxFrequency = valueFrequencies.values().stream().max().orElse(0);
if (Collections.frequency(valueFrequencies.values(), maxFrequency) > 1) {
// no clear max frequency
}
I'm trying Comparator to implement a sort in TreeMap according to a sequence.
final String sequence="People,Object,Environment,Message,Service";
Comparator<String> comparator = new Comparator<String>() {
#Override
public int compare(String key1, String key2) {
int returned = sequence.indexOf(key1) - sequence.indexOf(key2);
if (returned == 0 && !key1.contains(key2))
returned = -1;
return returned;
}
};
List<String> list=new ArrayList<String>();
Map<String,String> lhm = new TreeMap<String,String>(comparator);
// Put elements to the map
lhm.put("Object", "biu");
lhm.put("Message", "nuios");
lhm.put("Service", "sdfe");
lhm.put("People", "dfdfh");
lhm.put("Environment", "qwe");
lhm.put("Other", "names");
lhm.put("Elements", "ioup");
lhm.put("Rand", "uiy");
for(Entry<String, String> entry : lhm.entrySet()) {
System.out.println(entry.getKey());
}
The Output which I'm getting here is
Rand
Elements
Other
People
Object
Environment
Message
Service
The Elements in treemap which equals the sequence are ordered correctly but other elements which are not following the sequence should come after the sequence.My expectation is like following
People
Object
Environment
Message
Service
Rand
Elements
Other
How to implement this?
Suppose If I add some more words to the elements of my TreeMap means my Comparator doesn't even order the elements.Like this
lhm.put("Object IOn", "biu");
lhm.put("Message dfb", "nuios");
lhm.put("Serviceabc", "sdfe");
lhm.put("Peoplexxx", "dfdfh");
lhm.put("Environmentxxx", "qwe");
lhm.put("Other", "names");
lhm.put("Elements", "ioup");
lhm.put("Rand", "uiy");
My Output become
Rand
Elements
Other
Environmentxxx
Peoplexxx
Serviceabc
Message dfb
Object IOn
Somebody help me to rewrite my Comparator to fix this problem?
Here is some simple code that should to the task.
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
public class FixedOrderComparator implements Comparator<String> {
private final Map<String, Integer> index = new HashMap<>();
public FixedOrderComparator(String elements) {
String[] split = elements.split(",");
for (int i = 0; i < split.length; i++) {
index.put(split[i], i);
}
}
#Override
public int compare(String left, String right) {
Integer rankLeft = index.get(left);
Integer rankRight = index.get(right);
if (rankLeft != null && rankRight != null) {
return rankLeft.compareTo(rankRight);
}
if (rankLeft == null && rankRight == null) {
return left.compareTo(right);
}
return Boolean.compare(rankLeft == null, rankRight == null);
}
}
You have to correct your logic used in the comparator.
final String sequence="People,Object,Environment,Message,Service";
System.out.println(sequence.indexOf("People")); // 0
System.out.println(sequence.indexOf("Object")); // 7
System.out.println(sequence.indexOf("Message")); // 26
System.out.println(sequence.indexOf("Environment")); // 14
.indexOf(key1) returns the index of the first character of the String and not the String.
int returned = sequence.indexOf(key1) - sequence.indexOf(key2);
if(returned < 0){
// then it is sorted;
return 1;
}
else{ return -1; }