How to change pairs in a map? - java

I have a Map<Integer, Character> alphabetMap, which contains links from number to alphabet.
For example: [1: 'A', 2: 'B', ... , 26: 'Z']
I have a rotate() method, which should put the entries with changing links
After first using of the method my map should be [1: 'Z', 2: 'A', 3: 'B', ... , 26: 'Y']
Here is my current realisation:
public void rotate() {
final Map<Integer, Character> tempMap = new HashMap<>();
alphabetMap.forEach((key, value) -> tempMap.put(key == 26 ? 1 : key + 1, value));
alphabetMap = tempMap;
}
Is there any another way/algorhitm to "rotate" my entries quicker?

For "faster" rotation the map could be replaced with a list and then method Collections.rotate could be used for this purpose. Then the list elements may be accessed by index in range [0..25].
Or a small wrapper class may be implemented:
static class MyCharMap {
private List<Character> chars = IntStream
.rangeClosed('A', 'Z')
.mapToObj(c -> (char)c)
.collect(Collectors.toList());
public void rotate() {
Collections.rotate(chars, 1);
}
public Character get(Integer i) {
assert(1 <= i && i <= 26);
return chars.get(i - 1);
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder(4 * chars.size() + 2);
sb.append('{');
for (int i = 0, n = chars.size(); i < n; i++) {
if (i > 0) sb.append(", ");
sb.append(i + 1).append(':').append(chars.get(i));
}
sb.append('}');
return sb.toString();
}
}
Test:
MyCharMap chars = new MyCharMap();
chars.rotate();
chars.rotate();
System.out.println(chars);
System.out.println(chars.get(1));
Output:
{1:Y, 2:Z, 3:A, 4:B, 5:C, 6:D, 7:E, 8:F, 9:G, 10:H, 11:I, 12:J, 13:K, 14:L, 15:M, 16:N, 17:O, 18:P, 19:Q, 20:R, 21:S, 22:T, 23:U, 24:V, 25:W, 26:X}
Y

There is nothing wrong with your approach if using a map. But since rotations must be relative to some order and maps are unordered, you may want to use a List as suggested by Alex Rudenko.
Here is another alternative using maps. It permits left or right rotation by any amount (based on the sign) for a supplied map that has sequential integer keys starting at 1. It also adjusts for counts exceeding the size by using the remainder operator. For left or right rotation offsets are simply calculated and the map altered and returned for subsequent processing.
BiFunction<Map<Integer, Character>, Integer, Map<Integer, Character>> rotate =
(mp, cnt) -> {
int size = mp.values().size();
int count = cnt < 0 ? size + (cnt % size) - 1 :
cnt - 1;
return mp.entrySet().stream()
.map(e -> new AbstractMap.SimpleEntry<>(
(e.getKey() + count) % size + 1,
e.getValue()))
.collect(Collectors.toMap(e -> e.getKey(),
e -> e.getValue()));
};
System.out.println(map); // original map - 10 elements
map = rotate.apply(map,1); // right one - starting at J
System.out.println(map);
map = rotate.apply(map,-2); // left two, skipping A, going to B
System.out.println(map);
map = rotate.apply(map, -21);// Essentially left one going to C
System.out.println(map);
map = rotate.apply(map, 22); // Essentially right two going to A
System.out.println(map);
prints
{1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J}
{1=J, 2=A, 3=B, 4=C, 5=D, 6=E, 7=F, 8=G, 9=H, 10=I}
{1=B, 2=C, 3=D, 4=E, 5=F, 6=G, 7=H, 8=I, 9=J, 10=A}
{1=C, 2=D, 3=E, 4=F, 5=G, 6=H, 7=I, 8=J, 9=A, 10=B}
{1=A, 2=B, 3=C, 4=D, 5=E, 6=F, 7=G, 8=H, 9=I, 10=J}
The lambda version could easily be replaced by a regular method that takes a single rotate value and works on a fixed map.

As stated by #code-apprentice in a comment
Since the keys are in numerical order, I suggest using an array or a
list instead of a map.
If in case you still want to return a map, you could use the Collections.rotate(...) method in conjunction with reconstructing the expected map.
BiFunction<List<?>, Integer, Map<Integer, ?>> rotate =
(list, distance) -> {
Function<List<?>, Map<Integer, ?>> setMap = (arrayList) -> IntStream.range(0, arrayList.size())
.boxed()
.collect(Collectors.toMap(arrayList::get, Function.identity()))
.entrySet()
.stream()
.peek(e -> e.setValue(e.getValue() + 1))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
Collections.rotate(list, distance);
return setMap.apply(list);
};
System.out.println(rotate.apply(Arrays.asList('A', 'B', 'C', 'D'), 1)); // {1=D, 2=A, 3=B, 4=C}
System.out.println(rotate.apply(Arrays.asList("Peter", "James", "Sam", "Tiffany", "Mathew"), -3)); // {1=Tiffany, 2=Mathew, 3=Peter, 4=James, 5=Sam}

Based on Alex Rudenko's answer, you can create a custom map that uses Collections.rotate(...) behind the scenes.
Custom map:
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
class MyMap {
private static int counter = 0;
private final Map<Integer, ?> initMap;
private final List<?> list;
private Map<Integer, ?> rotatedMap;
public MyMap(List<?> list) {
this.list = list;
this.initMap = setMap(this.list);
this.rotatedMap = cloneMap(this.initMap);
}
private Map<Integer, ?> setMap(List<?> list) {
return IntStream.range(0, list.size())
.boxed()
.collect(Collectors.toMap(list::get, Function.identity()))
.entrySet()
.stream()
.peek(e -> e.setValue(e.getValue() + 1))
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
}
private Map<Integer, ?> cloneMap(Map<Integer, ?> map) {
return map.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
public Map<Integer, ?> get() {
counter = 0;
return rotatedMap;
}
public MyMap reset() {
counter = 0;
this.rotatedMap = cloneMap(this.initMap);
return this;
}
public MyMap rotate() {
return rotateMap(++counter);
}
public MyMap rotate(int distance) {
return rotateMap(distance);
}
private List<?> cloneList(List<?> list) {
return list.stream().map(e -> e).collect(Collectors.toList());
}
private MyMap rotateMap(int distance) {
List<?> list = cloneList(this.list);
Collections.rotate(list, distance);
this.rotatedMap = setMap(list);
return this;
}
public String toString() {
return rotatedMap.toString();
}
}
Example 1 (Rotate twice, Reset rotation, Rotate twice.):
// Rotate twice, Reset rotation, Rotate twice.
MyMap myCharMap1 = new MyMap(Arrays.asList('A', 'B', 'C', 'D'));
System.out.println(myCharMap1
.rotate(2)
.reset()
.rotate().rotate()
.get()); // {1=C, 2=D, 3=A, 4=B}
Example 2 (Rotate thrice. Can rotate a String list):
//Rotate thrice.
MyMap myMap = new MyMap(Arrays.asList("Peter", "James", "Sam", "Tiffany", "Mathew"));
System.out.println(myMap.rotate(3).get()); // {1=Sam, 2=Tiffany, 3=Mathew, 4=Peter, 5=James}
Example 3 (Rotate once. Can rotate a numeric list as well):
// Rotate once. Can rotate a numeric list as well.
MyMap myDigitMap3 = new MyMap(Arrays.asList(38, 56, 98, 160));
System.out.println(myDigitMap3.rotate(1).get()); // {1=160, 2=38, 3=56, 4=98}
Example 4 (Can accept negative rotations):
//Can accept negative rotations.
MyMap myMap = new MyMap(Arrays.asList("Peter", "James", "Sam", "Tiffany", "Mathew"));
System.out.println(myMap.rotate(-3).get()); // {1=Tiffany, 2=Mathew, 3=Peter, 4=James, 5=Sam}

Related

Is there a more efficient way to write multiple if else? [duplicate]

This question already has answers here:
How to have a range of numbers in an if/else statement Java
(6 answers)
How can I include range in a conditional (if/then) statement?
(3 answers)
Closed 3 years ago.
I need to match the average score to a letter grade. That means
if (90 < avg && avg < 100) {
return 'A';
}
and so on until 'F', with 5 if-else statements.
That's a lot of repetition, and the ranges I'm matching to are of the same length.
Is there a more efficient way to do this? I don't want to repeat the if-else statement 5 times.
I like to use an enum for this kind of problem.
You simply define your ranges in the enum values and use a predicate.
import java.util.*;
public class MyClass {
public static void main(String args[]) {
System.out.println(Letter.getValue(13)); // B
System.out.println(Letter.getValue(28)); // C
System.out.println(Letter.getValue(43)); // empty
}
}
enum Letter {
A(1, 10), B(11, 20), C(21, 30);
int min;
int max;
Letter (int min, int max) {
this.min = min;
this.max = max;
}
boolean inRange(int v) {
return v >= min && v <= max;
}
static Optional<Letter> getValue(int i) {
return Arrays.stream(values())
.filter(v -> v.inRange(i))
.findAny();
}
}
You may find this approach verbose and overengineered. Yes, it's.
Though, I like the way the grading system is defined.
class Test {
public static void main(String[] args) {
Map<String, Predicate<Integer>> gradingSystem = new HashMap<>();
gradingSystem.put("A", mark -> mark >= 90 && mark <= 100);
gradingSystem.put("B", mark -> mark >= 80 && mark < 90);
translateScoreIntoGrade(86, gradingSystem); // B
translateScoreIntoGrade(95, gradingSystem); // A
}
public static String translateScoreIntoGrade(int score, Map<String, Predicate<Integer>> gradingSystem) {
return gradingSystem.entrySet().stream()
.filter(gradePredicate -> gradePredicate.getValue().test(score))
.findFirst()
.map(Map.Entry::getKey)
.orElseThrow(() -> new IllegalArgumentException("This grade isn't valid for this system!"));
}
}
You could do it like this example:
public static String avgValuetoGrade(int value) {
int index = value/10;
int gradeIndex = Math.max(0, index - 4);
return (char)('F'-gradeIndex );
}
First divide you avg by 10, you get the number 0-9 (integer division). Next you have to reduce the numbers 0-4 somehow to one number -4 gives you the numbers 0-5 and maps the 0-4 to 0. Then you can return the char (thanks to #Carlos Heuberger).
This would be an example of the same thing written in a functional style. There is no repetition, but it's quite verbose.
I wouldn't use it unless the bands were different sizes (where you can exploit integer division)
Map<Range<Integer>, Character> rangeToGrade = Map.of(
Range.between(90, 100), 'A',
Range.between(80, 90), 'B'
//... the rest
);
int mark = 50;
char grade = rangeToGrade.entrySet().stream()
.filter(e -> e.getKey().contains(mark))
.map(Map.Entry::getValue)
.findFirst()
.orElse('?'); // or throw an exception
Map.of from Java 9. Range from e.g. Apache Commons
You can map it to an array and avoid the branch prediction rules, thereby making it more efficient and also saving the if/else stuff.
class StackOverflow
{
public static void main(String args[])
{
char grades[]={'A','B','C','D','E','F'};
convert(grades,95);
convert(grades,90);
convert(grades,110);
convert(grades,123);
convert(grades,150);
}
static void convert(char grades[],int marks)
{
if(marks<=90||marks>=150)
{
System.out.println("Invalid marks");
return;
}
int val=marks/10-9;
System.out.println(grades[val]);
}
}
Note: This assumes that 91-99 is A, 100-109 is B, 110-119 is C and so on. If you want to avoid the numbers 100,110 etc. just add the rule ||marks%10==0 in the if statement above.
Hope this is helpful :)
Note: This is made using Java 8 and does not have access to MapOf and does not use external libraries. Also did not use Streams to show other options.
I made a GradeBook class that when it is instantiated, a class field for a Map is filled with the keys used to find the letter grade. You can then just call .getGrade(int) from your GradeBook object with will also handle cases of negative input and input above 100 with a return of N. Otherwise it will return the correct grade from the Map.
This is more of an Object Oriented approach rather than using a static method call:
public class GradeBook {
private Map<Integer, Character> map;
public GradeBook() {
constructMap();
}
public char getGrade(int grade) {
if (grade >= 0 && grade <= 100) {
return map.get(Math.max(0, grade/10 - 4));
}
else {
return 'N';
}
}
public void constructMap() {
//You can use MapOf to add all of them at once in Java9+
map = new HashMap<>();
map.put(0, 'F');
map.put(1, 'E');
map.put(2, 'D');
map.put(3, 'C');
map.put(4, 'B');
map.put(5, 'A');
map.put(6, 'A');
}
public static void main(String [] args) {
GradeBook grades = new GradeBook();
//Testing different values
System.out.println(grades.getGrade(100));
System.out.println(grades.getGrade(75));
System.out.println(grades.getGrade(80));
System.out.println(grades.getGrade(91));
System.out.println(grades.getGrade(45));
System.out.println(grades.getGrade(2));
System.out.println(grades.getGrade(-1));
}
}
Output:
A
C
B
A
F
F
N
The downside to this implementation is it is slightly long to implement the first time, but the upside is it is very easy to reuse as you just need to create a new GradeBook() anywhere you need it.
Just to show off, in Java version 12 (preview enabled), using the new Switch Expressions:
String grade = switch(avg/10) {
case 9,10 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
case 5 -> "E";
default -> "F";
};
or, very flexible, if not being to lazy:
String grade = switch(avg) {
case 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 -> "A";
case 80, 81, 82, 83, 84, 85, 86, 87, 88, 89 -> "B";
// you got the idea, I AM lazy
Real (?) solution: use NavigableMap (example TreeMap) with its floorEntry() or lowerEntry() methods:
NavigableMap<Integer, String> grades = new TreeMap<>();
grades.put(90, "A");
grades.put(80, "B");
...
// usage
String grade = grades.floorEntry(avg).getValue();
values in map must eventually be adjusted

Find number of elements in range from map object

Map structure and data is given below
Map<String, BigDecimal>
A, 12
B, 23
C, 67
D, 99
Now i want to group values in range, output has range as key and number of elements there as value. Like below:
0-25, 2
26-50, 0
51-75, 1
76-100, 1
How can we do this using java streams ?
You can do it like that:
public class MainClass {
public static void main(String[] args) {
Map<String, BigDecimal> aMap=new HashMap<>();
aMap.put("A",new BigDecimal(12));
aMap.put("B",new BigDecimal(23));
aMap.put("C",new BigDecimal(67));
aMap.put("D",new BigDecimal(99));
Map<String, Long> o = aMap.entrySet().stream().collect(Collectors.groupingBy( a ->{
//Do the logic here to return the group by function
if(a.getValue().compareTo(new BigDecimal(0))>0 &&
a.getValue().compareTo(new BigDecimal(25))<0)
return "0-25";
if(a.getValue().compareTo(new BigDecimal(26))>0 &&
a.getValue().compareTo(new BigDecimal(50))<0)
return "26-50";
if(a.getValue().compareTo(new BigDecimal(51))>0 &&
a.getValue().compareTo(new BigDecimal(75))<0)
return "51-75";
if(a.getValue().compareTo(new BigDecimal(76))>0 &&
a.getValue().compareTo(new BigDecimal(100))<0)
return "76-100";
return "not-found";
}, Collectors.counting()));
System.out.print("Result="+o);
}
}
Result is : Result={0-25=2, 76-100=1, 51-75=1}
I couldn't find a better way to do that check for big decimals but you can think about how to improve it :) Maybe make an external method that does that trick
You may use a solution for regular ranges, e.g.
BigDecimal range = BigDecimal.valueOf(25);
inputMap.values().stream()
.collect(Collectors.groupingBy(
bd -> bd.subtract(BigDecimal.ONE).divide(range, 0, RoundingMode.DOWN),
TreeMap::new, Collectors.counting()))
.forEach((group,count) -> {
group = group.multiply(range);
System.out.printf("%3.0f - %3.0f: %s%n",
group.add(BigDecimal.ONE), group.add(range), count);
});
which will print:
1 - 25: 2
51 - 75: 1
76 - 100: 1
(not using the irregular range 0 - 25)
or a solution with explicit ranges:
TreeMap<BigDecimal,String> ranges = new TreeMap<>();
ranges.put(BigDecimal.ZERO, " 0 - 25");
ranges.put(BigDecimal.valueOf(26), "26 - 50");
ranges.put(BigDecimal.valueOf(51), "51 - 75");
ranges.put(BigDecimal.valueOf(76), "76 - 99");
ranges.put(BigDecimal.valueOf(100),">= 100 ");
inputMap.values().stream()
.collect(Collectors.groupingBy(
bd -> ranges.floorEntry(bd).getValue(), TreeMap::new, Collectors.counting()))
.forEach((group,count) -> System.out.printf("%s: %s%n", group, count));
0 - 25: 2
51 - 75: 1
76 - 99: 1
which can also get extended to print the absent ranges:
Map<BigDecimal, Long> groupToCount = inputMap.values().stream()
.collect(Collectors.groupingBy(bd -> ranges.floorKey(bd), Collectors.counting()));
ranges.forEach((k, g) -> System.out.println(g+": "+groupToCount.getOrDefault(k, 0L)));
0 - 25: 2
26 - 50: 0
51 - 75: 1
76 - 99: 1
>= 100 : 0
But note that putting numeric values into ranges like, e.g. “0 - 25” and “26 - 50” only makes sense if we’re talking about whole numbers, precluding values between 25 and 26, raising the question why you’re using BigDecimal instead of BigInteger. For decimal numbers, you would normally use ranges like “0 (inclusive) - 25 (exclusive)” and “25 (inclusive) - 50 (exclusive)”, etc.
If you have a Range like this:
class Range {
private final BigDecimal start;
private final BigDecimal end;
public Range(BigDecimal start, BigDecimal end) {
this.start = start;
this.end = end;
}
public boolean inRange(BigDecimal val) {
return val.compareTo(start) >= 0 && val.compareTo(end) <= 0;
}
#Override
public String toString() {
return start + "-" + end;
}
}
You can do this:
Map<String, BigDecimal> input = new HashMap<>();
input.put("A", BigDecimal.valueOf(12));
input.put("B", BigDecimal.valueOf(23));
input.put("C", BigDecimal.valueOf(67));
input.put("D", BigDecimal.valueOf(99));
List<Range> ranges = new ArrayList<>();
ranges.add(new Range(BigDecimal.valueOf(0), BigDecimal.valueOf(25)));
ranges.add(new Range(BigDecimal.valueOf(26), BigDecimal.valueOf(50)));
ranges.add(new Range(BigDecimal.valueOf(51), BigDecimal.valueOf(75)));
ranges.add(new Range(BigDecimal.valueOf(76), BigDecimal.valueOf(100)));
Map<Range, Long> result = new HashMap<>();
ranges.forEach(r -> result.put(r, 0L)); // Add all ranges with a count of 0
input.values().forEach( // For each value in the map
bd -> ranges.stream()
.filter(r -> r.inRange(bd)) // Find ranges it is in (can be in multiple)
.forEach(r -> result.put(r, result.get(r) + 1)) // And increment their count
);
System.out.println(result); // {51-75=1, 76-100=1, 26-50=0, 0-25=2}
I also had a solution with the groupingBy collector, but it was twice as big and couldn't deal with overlapping ranges or values that weren't in any range, so I think a solution like this will be better.
You can also use a NavigableMap:
Map<String, BigDecimal> dataSet = new HashMap<>();
dataSet.put("A", new BigDecimal(12));
dataSet.put("B", new BigDecimal(23));
dataSet.put("C", new BigDecimal(67));
dataSet.put("D", new BigDecimal(99));
// Map(k=MinValue, v=Count)
NavigableMap<BigDecimal, Integer> partitions = new TreeMap<>();
partitions.put(new BigDecimal(0), 0);
partitions.put(new BigDecimal(25), 0);
partitions.put(new BigDecimal(50), 0);
partitions.put(new BigDecimal(75), 0);
partitions.put(new BigDecimal(100), 0);
for (BigDecimal d : dataSet.values()) {
Entry<BigDecimal, Integer> e = partitions.floorEntry(d);
partitions.put(e.getKey(), e.getValue() + 1);
}
partitions.forEach((k, count) -> System.out.println(k + ": " + count));
// 0: 2
// 25: 0
// 50: 1
// 75: 1
// 100: 0
If only RangeMap from guava had methods like replace of computeIfPresent/computeIfAbsent like the additions in java-8 Map do, this would have been a breeze to do. Otherwise it's a bit cumbersome:
Map<String, BigDecimal> left = new HashMap<>();
left.put("A", new BigDecimal(12));
left.put("B", new BigDecimal(23));
left.put("C", new BigDecimal(67));
left.put("D", new BigDecimal(99));
RangeMap<BigDecimal, Long> ranges = TreeRangeMap.create();
ranges.put(Range.closedOpen(new BigDecimal(0), new BigDecimal(25)), 0L);
ranges.put(Range.closedOpen(new BigDecimal(25), new BigDecimal(50)), 0L);
ranges.put(Range.closedOpen(new BigDecimal(50), new BigDecimal(75)), 0L);
ranges.put(Range.closedOpen(new BigDecimal(75), new BigDecimal(100)), 0L);
left.values()
.stream()
.forEachOrdered(x -> {
Entry<Range<BigDecimal>, Long> e = ranges.getEntry(x);
ranges.put(e.getKey(), e.getValue() + 1);
});
System.out.println(ranges);
Here is the code which you can use:
public static void groupByRange() {
List<MyBigDecimal> bigDecimals = new ArrayList<MyBigDecimal>();
for(int i =0; i<= 10; i++) {
MyBigDecimal md = new MyBigDecimal();
if(i>0 && i<= 2)
md.setRange(1);
else if(i>2 && i<= 5)
md.setRange(2);
else if(i>5 && i<= 7)
md.setRange(3);
else
md.setRange(4);
md.setValue(i);
bigDecimals.add(md);
}
Map<Integer, List<MyBigDecimal>> result = bigDecimals.stream()
.collect(Collectors.groupingBy(e -> e.getRange(),
Collector.of(
ArrayList :: new,
(list, elem) -> {
if (list.size() < 2)
list.add(elem);
},
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)));
for(Entry<Integer, List<MyBigDecimal>> en : result.entrySet()) {
int in = en.getKey();
List<MyBigDecimal> cours = en.getValue();
System.out.println("Key Range = "+in + " , List Size : "+cours.size());
}
}
class MyBigDecimal{
private int range;
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public int getRange() {
return range;
}
public void setRange(int range) {
this.range = range;
}
}
This will give you a similar result.
public static void main(String[] args) {
Map<String, Integer> resMap = new HashMap<>();
int range = 25;
Map<String, BigDecimal> aMap=new HashMap<>();
aMap.put("A",new BigDecimal(12));
aMap.put("B",new BigDecimal(23));
aMap.put("C",new BigDecimal(67));
aMap.put("D",new BigDecimal(99));
aMap.values().forEach(v -> {
int lower = v.divide(new BigDecimal(range)).intValue();
// get the lower & add the range to get higher
String key = lower*range + "-" + (lower*range+range-1);
resMap.put(key, resMap.getOrDefault(key, 0) + 1);
});
resMap.entrySet().forEach(e -> System.out.println(e.getKey() + " = " + e.getValue()));
}
Though there are some differences from what you have asked
Ranges are inclusive in this; 0-24 instead of 0-25, so that 25 is included in 25-50
Your range 0-25 contains 26 possible values in between, while all other ranges contain 25 values. This implementations output has ranges of size 25 (configurable via range variable)
You can decide on the range
Output (you may want to iterate the map's key better to get the output in a sorted order)
75-99 = 1
0-24 = 2
50-74 = 1
Assuming your range has the value BigDecimal.valueOf(26), you can do the following to get a Map<BigDecimal, Long> where each key represents the group id (0 for [0-25], 1 for [26, 51], ...), and each corresponding value represents the group count of elements.
content.values()
.stream()
.collect(Collectors.groupingBy(n -> n.divide(range, BigDecimal.ROUND_FLOOR), Collectors.counting()))

In hashmap how to assign previous key to the next if duplicate values are exist

Ii'm trying to assigning the previous key to the next in case of duplicate are present in hashmap.
for example, duplicate entries in hashmap:
5-->51
6-->51
I want output as below:
5-->51
5-->51
Below is my code:
HashMap<Integer, Integer> map = new HashMap();
map.put(1, 99);
map.put(2, 120);
map.put(3, 89);
map.put(4, 71);
map.put(5, 51);
map.put(6, 51);
map.put(7, 77);
map.put(8, 44);
map.put(9, 22);
Set set = new HashSet<>();
List list = new ArrayList<>();
for (Entry<Integer, Integer> mapVal : map.entrySet()) {
if (!set.add(mapVal.getValue())) {
list.add(mapVal.getValue());
} else {
set.add(mapVal.getValue());
}
}
for (Entry<Integer, Integer> mapVal : map.entrySet()) {
if (list.contains(mapVal.getValue())) {
System.out.println(mapVal.getKey() + "-->" + mapVal.getValue());
}
}
Would it be possible in hashmap case? If so, what algorithm or code should I use?
If I got your question right. It's not possible to have duplicate keys in a map whichever map it is. You want for two value same key which isn't possible.
i want output as below:
5-->51
5-->51
You could not get such a thing with a Map as keys are unique.
As workaround to get that result in the output, you should use a Map implementation that maintains the insertion order of the elements: LinkedHashMap.
HashMap doesn't maintain it.
Then the idea is rather simple :
Create a list from map keys and create another list from map values.
Iterate values list and update elements of the keys list which the value associated contains a value already encountered.
output the lists
Here is a sample code :
public class Foo {
public static void main(String[] args) {
HashMap<Integer, Integer> map = new LinkedHashMap<>();
map.put(1, 99);
map.put(2, 120);
map.put(3, 89);
map.put(4, 71);
map.put(5, 51); // 51 value
map.put(6, 51); // 51 value
map.put(7, 77);
map.put(8, 51); // 51 value
map.put(9, 22);
List<Integer> keys = new ArrayList<>(map.keySet());
List<Integer> values = new ArrayList<>(map.values());
Set<Integer> valuesWithKeyUpdated = new HashSet<>();
for (int i = 0; i < values.size() - 1; i++) {
final Integer oldestKeyForCurrentValue = keys.get(i);
final Integer currentValue = values.get(i);
if (valuesWithKeyUpdated.contains(currentValue)) {
continue;
}
for (int j = 1; j < values.size(); j++) {
if (currentValue == values.get(j)) {
keys.set(j, oldestKeyForCurrentValue);
}
}
valuesWithKeyUpdated.add(values.get(i));
}
for (int i = 0; i < keys.size(); i++) {
System.out.println("key=" + keys.get(i) + ", value="+ values.get(i));
}
}
}
key=1, value=99
key=2, value=120
key=3, value=89
key=4, value=71
key=5, value=51 // value 51 with key 5 in the original map
key=5, value=51 // value 51 : display 5 instead of 6
key=7, value=77
key=5, value=51 // value 51 : display 5 instead of 8
key=9, value=22
As pointed out by #AliOmar62 you can not have duplicate keys in map.
What you can do is inverse key with value and collect all values to collection e.g.
Map<Integer, List<Integer>> sameValueToDiffKeys = map.entrySet()
.stream()
.map(e -> new SimpleImmutableEntry<>(e.getValue(), e.getKey()))
.collect(Collectors.toMap(SimpleImmutableEntry::getKey, e -> new ArrayList<>(Collections.singleton(e.getValue())), (first, second) -> {
first.addAll(second);
return first;
}));
The output with your data will look in console like:
{51=[5, 6], 99=[1], 22=[9], 71=[4], 120=[2], 89=[3], 44=[8], 77=[7]}
Please take a look at merge function, which is the third argument to Collectors.toMap.
This function resolves all duplications for one key by merging them into one collection.

Optimizing Opportunities with Java Streams

I was looking through some code and came across this method that takes an HTML Header value (i.e. Content-Disposition=inline;filename=foo.bar) and parses it into a map separated by the semi-colon's into key=value pairs. At first it looked like a good candidate for optimization using a stream, but after I implemented it, the fact that I can't reuse the computed String.indexOf('=') value means the string must be scanned 3 times, which is actually less optimal than the original. I'm perfectly aware that there are many instances where Streams aren't the right tool for the job, but I was wondering if I had just missed some technique that could allow the Stream to be as performant/more performant than the initial code.
/**
* Convert a Header Value String into a Map
*
* #param value The Header Value
* #return The data Map
*/
private static Map<String,String> headerMap (String value) {
int eq;
Map<String,String> map = new HashMap<>();
for(String entry : value.split(";")) {
if((eq = entry.indexOf('=')) != -1) {
map.put(entry.substring(0,eq),entry.substring(eq + 1));
}
}
return map;
return Stream.of(value.split(";")).filter(entry -> entry.indexOf('=') != -1).collect(Collectors.));
} //headerMap
My attempt at Streaming it:
/**
* Convert a Header Value String into a Map
*
* #param value The Header Value
* #return The data Map
*/
private static Map<String,String> headerMap (String value) {
return Stream.of(value.split(";")).filter(entry -> entry.indexOf('=') != -1).collect(Collectors.toMap(entry -> entry.substring(0,entry.indexOf('=')),entry -> entry.substring(entry.substring(entry.indexOf('=') + 1))));
} //headerMap
This solution looks for '=' only once:
private static Map<String, String> headerMap(String value) {
return Stream.of(value.split(";"))
.map(s -> s.split("=", 2))
.filter(arr -> arr.length == 2)
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1]));
}
Note that here the fast-path for String.split is used, thus regular expression is not actually created.
Note that using Guava you can do this in quite clean way even prior to Java-8:
private static Map<String, String> headerMap(String value) {
return Splitter.on( ';' ).withKeyValueSeparator( '=' ).split( value );
}
In general I would advise you against manual parsing of HTTP headers. There are many caveats there. See, for example, how it's implemented in Apache HTTP library. Use libraries.
I came up with the following code:
private static Map<String, String> headerMap(String value) {
return Stream.of(value.split(";"))
.filter(entry -> entry.indexOf('=') != -1)
.map(entry -> {
int i = entry.indexOf('=');
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
It only scans for the entry two times, by storing the key and value inside an array of size 2. I'm not sure it will be as performant as the for loop since we are creating another Object to serve just as a holder.
Another solution that scans the entry only one time is this, although I'm not very found of it:
private static Map<String, String> headerMap(String value) {
return Stream.of(value.split(";"))
.map(entry -> {
int i = entry.indexOf('=');
if (i == -1) {
return null;
}
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
I realized a JMH benchmark to test this. Following is the benchmark code:
#Warmup(iterations = 5, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
#Measurement(iterations = 10, time = 1000, timeUnit = TimeUnit.MILLISECONDS)
#BenchmarkMode(Mode.AverageTime)
#OutputTimeUnit(TimeUnit.MICROSECONDS)
#Fork(3)
#State(Scope.Benchmark)
public class StreamTest {
private static final String VALUE = "Accept=text/plain;"
+ "Accept-Charset=utf-8;"
+ "Accept-Encoding=gzip, deflate;"
+ "Accept-Language=en-US;"
+ "Accept-Datetime=Thu, 31 May 2007 20:35:00 GMT;"
+ "Cache-Control=no-cache;"
+ "Connection=keep-alive;"
+ "Content-Length=348;"
+ "Content-Type=application/x-www-form-urlencoded;"
+ "Date=Tue, 15 Nov 1994 08:12:31 GMT;"
+ "Expect=100-continue;"
+ "Max-Forwards=10;"
+ "Pragma=no-cache";
#Benchmark
public void loop() {
int eq;
Map<String, String> map = new HashMap<>();
for (String entry : VALUE.split(";")) {
if ((eq = entry.indexOf('=')) != -1) {
map.put(entry.substring(0, eq), entry.substring(eq + 1));
}
}
}
#Benchmark
public void stream1() {
Stream.of(VALUE.split(";"))
.filter(entry -> entry.indexOf('=') != -1)
.map(entry -> {
int i = entry.indexOf('=');
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
#Benchmark
public void stream2() {
Stream.of(VALUE.split(";"))
.map(entry -> {
int i = entry.indexOf('=');
if (i == -1) {
return null;
}
return new String[] { entry.substring(0, i), entry.substring(i + 1) };
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(array -> array[0], array -> array[1]));
}
public static void main(String[] args) throws Exception {
Main.main(args);
}
}
and this is the result (Code i5 3230M CPU # 2.60 GHz, Windows 10, Oracle JDK 1.8.0_25):
Benchmark Mode Cnt Score Error Units
StreamTest.loop avgt 30 1,541 ± 0,038 us/op
StreamTest.stream1 avgt 30 1,633 ± 0,042 us/op
StreamTest.stream2 avgt 30 1,604 ± 0,058 us/op
What this demonstrates is that both the streams solution and the for loop are actually equivalent in terms of performance.

HashMap Keys and Value comparison [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I'm comparing HashMap value against the keys and replace if key is larger. The purpose is to update Finger Table of Chord System simulation.
I have HashMap data:
Key 0 with Values: [1,2,4,8,16]
Key 3 with Values: [4,5,7,11,19]
Key 7 with Values: [8,9,11,15,23]
The desired outcome is such that for each key 0, 3 and 7, its values will be compared with the next key value.
Example:
For key 0, compare its values with next key value which is 3.
3 compare with [1,2,4,8,16] and replace 1,2 with 3 because 3 > 1 and 2
For key 3, compare its values with next key value which is 7.
7 compare with [4,5,7,11,19] and replace 4,5 with 7 because 7 > 4 and 5
The code written below does the following:
For the first set of values [1,2,4,8,16]
1 compare with [0]
2 compare with [0]
4 compare with [0]
etc.. and it moves on to another set of values
[4,5,7,11, 19]
4 compare with [3]
5 compare with [3]
7 compare with [3]
How can I amend the code to achieve the desired outcome mentioned above?
public void updateFingerTable() {
chordSize = chord.initChordSize;
for (Map.Entry<Integer, node> m : peerList.entrySet()) {
for (int i=0; i<chordSize; i++) {
System.out.println("Successor i: " + m.getValue().successor[i]);
System.out.println ("Key: " + m.getKey());
if (m.getValue().successor[i] < m.getKey()) {
m.getValue().successor[i] = m.getKey();
//System.out.println(m.getValue().successor[i]);
}
}
First of all, you seem to make the (wrong!) assumption that a HashMap is ordered.
This is not the case!
If you want a map that is ordered by the value of its keys, then you have to use a TreeMap. (Alternatively, you could collect all entries from the map, and put them into a list, which you then could sort based on their key).
Apart from that, determining a "next" key in a map is not so simple. (In fact, for a TreeMap, you could use the higherKey method, but this should not be necessary here).
You can simply walk though the entries, and always refer to the previous entry for which you update the list (based on the key of the current entry).
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
public class KeyValueListTest
{
public static void main(String[] args)
{
Map<Integer, IntArrayNode> map = new TreeMap<Integer, IntArrayNode>();
map.put(0, new IntArrayNode(new int[]{1,2,4,8,16}));
map.put(3, new IntArrayNode(new int[]{4,5,7,11,19}));
map.put(7, new IntArrayNode(new int[]{8,9,11,15,23}));
System.out.println("Before update:");
for (Map.Entry<Integer, IntArrayNode> e : map.entrySet())
{
System.out.println(e);
}
update(map);
System.out.println("After update:");
for (Map.Entry<Integer, IntArrayNode> e : map.entrySet())
{
System.out.println(e);
}
}
private static void update(IntArrayNode node, int minValue)
{
for (int i=0; i<node.getNumElements(); i++)
{
node.setElement(i, Math.max(minValue, node.getElement(i)));
}
}
public static void update(Map<Integer, IntArrayNode> map)
{
Map.Entry<Integer, IntArrayNode> previous = null;
for (Map.Entry<Integer, IntArrayNode> e : map.entrySet())
{
if (previous != null)
{
update(previous.getValue(), e.getKey());
}
previous = e;
}
}
}
class IntArrayNode
{
private final int elements[];
IntArrayNode(int elements[])
{
this.elements = elements.clone();
}
int getNumElements()
{
return elements.length;
}
int getElement(int index)
{
return elements[index];
}
void setElement(int index, int value)
{
elements[index] = value;
}
#Override
public String toString()
{
return Arrays.toString(elements);
}
}
For your example input, this will print
Before update:
0=[1, 2, 4, 8, 16]
3=[4, 5, 7, 11, 19]
7=[8, 9, 11, 15, 23]
After update:
0=[3, 3, 4, 8, 16]
3=[7, 7, 7, 11, 19]
7=[8, 9, 11, 15, 23]
you can consider value as dynamic array of Integer that can be implemented using ArrayList and key as Integer.You can code like this for your purpose:
List<Integer> value=new ArrayList<>();
for (Map.Entry pair : map.entrySet())
{
Integer key=(Integer)pair.getKey();
for(Integer x:(ArrayList<Integer>)(pair.getValue()))
{
if(key>x)
{
value.add(key);
}
else
{
value.add(x);
}
}
map.put(key, (ArrayList<Integer>) value);
System.out.println(value);
value=new ArrayList<>();
}
I see two issues from first glance that seems to violate the logic of your examples.
According to your examples, map values of current element must be compared to map key of next element, not current.
First, line with for (int i=0; i<chordSize; i++) {: I was expecting that you would loop over m.getValue().length() so you can iterate values that are associated with m, not with chordSize which seems to be involved with this Map, though I’m not sure why.
Second, line with if (m.getValue().successor[i] < m.getKey()) { I think you’ll need to compare m.getValue().successor[i] with next element’s key, not current element’s key.
Having that in mind, you'll need to put all map items into another data structure that provides you with index based access (array seems OK, cause you already know the size of the map) to its components so you'll be able to get next map item while also working with current one. That will make comparing a lot more easy.
Hope that helps.
I fear your example is still a bit unclear. Is the following what you want to do:
3 keys [0,3,7]
Three sets of values: [1,2,4,8,16], [4,5,7,11,19], [8,9,11,15,23]
Step 1):
You take the 0 from the keys and compare it with the values:
0 > 1? No ... 0>16? No.
Step 2):
Compare the 3 from the keys with the values:
3>1? Yes -> Replace 1 with 3 ... 3>16? No ...
Step 3):
Compare the 7 from the keys with the values:
7>1 Yes -> Replace 1 with 7, 7>2? Yes -> Replace...
If yes, why bother to compare the smaller keys? The key 7 will "override" all changes that 0 and 3 have done before, so simply find the biggest key and compare it with the values.
int keyArray[] = new int[]{0,3,7};
int largest = keyArray[0];
for(int i=1; i< keyArray.length; i++)
{
if(keyArray[i] > largest)
largest = keyArray[i];
}
Then you could go over your valuesets and compare them with largest.equals() to the HashMap.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
public class Test {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Map<Integer, List<Integer>> hash = new HashMap<Integer,List<Integer>>();
List<Integer> list1 = new ArrayList<Integer>();
list1.add(1);list1.add(2);list1.add(4);list1.add(8);list1.add(16);
List<Integer> list2 = new ArrayList<Integer>();
list2.add(4);list2.add(5);list2.add(7);list2.add(11);list2.add(19);
List<Integer> list3 = new ArrayList<Integer>();
list3.add(8);list3.add(9);list3.add(11);list3.add(15);list3.add(23);
hash.put(0,list1);
hash.put(3,list2);
hash.put(7,list3);
System.out.println("Input:");
for (Map.Entry<Integer, List<Integer>> m1 : hash.entrySet()) {
System.out.println("Successor i: " + m1.getValue());
System.out.println ("Key: " + m1.getKey());
}
Map<Integer, List<Integer>> out = hash;
List<Integer> list = new ArrayList<Integer>();
int keyValue = 0;
int count=0;
for (Entry<Integer, List<Integer>> m : hash.entrySet()) {
if(count==0){
list = m.getValue();
keyValue = m.getKey();
count++;
continue;
}
else{
for(int i=0;i<list.size();i++){
if(m.getKey()>list.get(i)){
list.set(i, m.getKey());
}
}
out.put(keyValue,list);
list = m.getValue();
keyValue = m.getKey();
}
}
System.out.println("Output:----------------");
for (Map.Entry<Integer, List<Integer>> m1 : out.entrySet()) {
System.out.println("Successor i: " + m1.getValue());
System.out.println ("Key: " + m1.getKey());
}
}
}

Categories

Resources