I am using a HashMap<String, Integer> to keep track of count of an occurrence of a specific string. I am performing this operation in a single-thread manner in the following way:
HashMap<String, Integer> count = new HashMap<>();
// List<String≥ words = ...;
for (String word : words) {
if (!count.containsKey(word)) {
count.put(word, 0);
}
count.put(word, count.get(word) + 1);
}
Is it possible, for the same word, the count increases by more than 1 because I am performing a put and get on the same key at the same time? i.e. Let's say the word = "hello". Initially, count.get(word) => 1. When I perform count.put(word, count.get(word) + 1), if I do count.get(word), instead of getting 2, I get 3.
To answer your questions directly: no it is not possible for the statement count.put(word, count.get(word) + 1) to increment the value by more than 1. Although the two method calls are in the same statement they are performed sequentially: the get is performed first to find the second argument to pass to the put.
You can combine your missing key test and initialisation into a single statement:
count.putIfAbsent(word, 0);
This conveniently returns the value afterwards, allowing:
count.put(word, 1 + count.putIfAbsent(word, 0));
However there is also a method that already combines those two operations:
count.merge(word, 1, Integer::sum);
Map has methods compute and merge that would allow to implement shorter updates of the values for the keys:
compute
for (String word : words) {
count.compute(word, (w, prev) -> prev == null ? 1 : prev + 1);
}
merge
for (String word : words) {
count.merge(word, 1, (prev, one) -> prev + one);
}
Lambda expression (prev, one) -> prev + one is actually a function of two int arguments returning their sum, therefore it may be replaced with a method reference Integer::sum:
for (String word : words) {
count.merge(word, 1, Integer::sum);
}
It absolutely safe to do it in a single thread.
No, it's not possible that "count increases by more than 1 because I am performing a put and get on the same key at the same time" because two operations never can happen at the same time with single-threaded execution.
Code count.put(word, count.get(word) + 1); will execute commands in following order:
Integer value1 = count.get(word);
int value2 = value1.intValue();
int value3 = value2 + 1;
Integer value4 = new Integer(value3);
count.put(word, value4);
By the way, your code will produce quite a lot of garbage and will be not very effective.
This way is more effective:
private static class CounterHolder{
int value;
}
Map<String, CounterHolder> count = new HashMap<>();
List<String> words = ...
for (String word : words) {
CounterHolder holder;
if (count.containsKey(word)) {
holder = new CounterHolder();
} else {
holder = new CounterHolder();
count.put(word, holder);
}
++holder.value;
}
Related
I m trying to understand reduction accumulator operation: In the below example
List<String> letters = Arrays.asList("a","bb","ccc");
String result123 = letters
.stream()
.reduce((partialString, element) ->
partialString.length() < element.length()
? partialString
: element
).get();
System.out.println(result123);
Is partialString initialized to empty string? Since its a fold operation, I assume that operation should result empty string but its printing "a". Can someone please explain how this accumulator works?
The for-loop corresponding code for the reduce operation is like
boolean seen = false;
String acc = null;
for (String letter : letters) {
if (!seen) {
seen = true;
acc = letter;
} else {
acc = acc.length() < letter.length() ? acc : letter;
}
}
The first pair of element to reduce are (firstElt, secondElt) , there is no empty initial element
If you print each step of the reduce
letters.stream()
.reduce((partialString, element) -> {
System.out.println(partialString + " " + element);
return partialString.length() < element.length() ? partialString : element;
}).get();
// output
a bb
a ccc
If you read the documentation, i.e. the javadoc of reduce(), you will learn that partialString is initialized to the first value, and reduce() is only called to combine values, aka to reduce them.
Is partialString initialized to empty string?
No. If you wanted that, you need to use the other reduce() method, so you wouldn't need to call get():
String result123 = letters
.stream()
.reduce("", (partialString, element) ->
partialString.length() < element.length()
? partialString
: element
);
Of course, that doesn't make any sense, because partialString is now always a string with length() = 0, so result of expression is always an empty string. You might as well just write String result123 = ""; and save all the CPU time.
I'm trying to get the value of when was the last time function was called with the same input. If it's the first time, return -1. For example:
System.out.println(newNumber(1)); // returns -1
System.out.println(newNumber(2)); // returns -1
System.out.println(newNumber(3)); // returns -1
System.out.println(newNumber(1)); // returns 2
System.out.println(newNumber(2)); // returns 2
System.out.println(newNumber(1)); // returns 1
System.out.println(newNumber(4)); // returns -1
System.out.println(newNumber(1)); // returns 1
Having really hard time finding the right way to start doing this. I'm pretty new to hash maps, and I think that's what you have to use?
You can store the number of times a number appears in a map from the number to the number of times it appears. Note that this map will have to be a data member to retain the values beyond a single function call.
Map<Integer, Integer> map = new HashMap<>();
public int newNumber(int i) {
Integer result = map.computeIfPresent(i, (k, v) -> v + 1);
if (result == null) {
map.put(i, 1);
return -1;
}
return result;
}
Assuming I've understood the question correctly as:
'Return the number of invocations of the method since the last time the current argument was given, or -1 if it has never been given before', then you could do this with any Collection which maintains insertion order.
For example:
private final List<Integer> invocations = new ArrayList<>();
public int newNumber(final int i) {
if (!this.invocations.contains(i)) {
// Not seen before, so add to the List, and return -1.
this.invocations.add(i);
return -1;
}
// size - 1 for 0 indexed list.
// - last index of it since that was the last time it was called.
final int lastInvocation
= this.invocations.size() - 1 - this.invocations.lastIndexOf(i);
// Remove all prior occurrences of it in the List.
// Not strictly necessary, but stops the List just growing all the time.
this.invocations.removeIf(value -> value.equals(i));
// Add the element as the latest invocation (head of the List)
this.invocations.add(i);
return lastInvocation;
}
Note, thread safety very much not considered here.
I think this works as required.
static Map<Integer, Integer> map = new HashMap<>();
public static int newNumber(int n) {
// store the call count at the null key
int calls = map.compute(null, (k,v)-> v == null ? 0 : v + 1);
// initialize the argument entry first time encountered
map.computeIfAbsent(n, v->calls);
int c = calls - map.get(n) - 1;
map.put(n, calls);
return c;
}
I am trying to find the integer that appears an odd numbers of time, but somehow the tests on qualified.io are not returning true. May be there is something wrong with my logic?
The problem is that in an array [5,1,1,5,2,2,5] the number 5 appears 3 times, therefore the answer is 5. The method signature wants me to use List<>. So my code is below.
public static List<Integer> findOdd( List<Integer> integers ) {
int temp = integers.size();
if (integers.size() % 2 == 0) {
//Do something here.
}
return integers;
}
}
I need to understand couple things. What is the best way to check all elements inside integers list, and iterate over to see if any similar element is present, if yes, return that element.
If you are allowed to use java 8, you can use streams and collectors for this:
Map<Integer, Long> collect = list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Given a list with integers, this code will generate a map, where the key is the actual number and value is number of repetitions.
You just have to iterate through map and find out what are you interested in.
You want to set up a data structure that will let you count every integer that appears in the list. Then iterate through your list and do the counting. When you're done, check your data structure for all integers that occur an odd number of times and add them to your list to return.
Something like:
public static List<Integer> findOdd(List<Integer> integers) {
Map<Integer, MutableInt> occurrences = new HashMap<>(); // Count occurrences of each integer
for (Integer i : integers) {
if (occurrences.containsKey(i)) {
occurrences.get(i).increment();
} else {
occurrences.put(i, new MutableInt(1));
}
}
List<Integer> answer = new ArrayList<>();
for (Integer i : occurrences.keySet()) {
if ((occurrences.get(i) % 2) == 1) { // It's odd
answer.add(i)
}
}
return answer;
}
MutableInt is an Apache Commons class. You can do it with plain Integers, but you have to replace the value each time.
If you've encountered streams before you can change the second half of the answer above (the odd number check) to something like:
return occurrences.entrySet().stream()
.filter(i -> i % 2 == 1)
.collect(Collectors.toList());
Note: I haven't compiled any of this myself so you may need to tweak it a bit.
int findOdd(int[] nums) {
Map<Integer, Boolean>evenNumbers = new HashMap<>();
nums.forEach(num -> {
Boolean status = evenNumbers.get(num);
if(status == null) {
evenNumbers.put(num, false);
}else{
evenNumbers.put(num, !status);
}
});
// Map holds true for all values with even occurrences
Iterator<Integer> it = evenNumbers.keySet().iterator();
while(it.hasNext()){
Integer key = it.next();
Boolean next = evenNumbers.get(key);
if(next == false){
return key;
}
}
}
You could use the reduce method from the IntStream package.
Example:
stream(ints).reduce(0, (x, y) -> x ^ y);
In my process to learn Java 8 forEach, I'm trying to change this code:
Integer counter;
for(String s : stringList) {
counter = myMap.get(s);
if(counter == null) {
myMap.put(s, 1);
}
else {
myMap.put(s, counter + 1);
}
}
As you can see, the idea is that if the String is absent, it's added as key and the value is set to 1.
When the key is present, counter is equal to the latest value and then the value is replace with counter + 1.
I tried this:
stringList.forEach(s-> myMap.putIfAbsent(s,1));
but that just covers half the job.
I know that methods like computeIfAbsent and computeIfPresent exist, but after the key, a function is required but I'm not sure what. Can't change the value to counter since it should be final. Seems like there isn't putIfPresent...
Is it possible to do this using forEach and a lambda expression or should I just keep it like that?
You can use the merge function on the map. It takes a key, a value, and a function that will be called if the key existed from before.
You can use it like this:
stringList.forEach(s -> myMap.merge(s, 1, (oldValue, newValue) -> oldValue + 1));
You can also return null from the lambda if you want to remove the key.
Full documentation here:
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#merge-K-V-java.util.function.BiFunction-
You can simply reuse your original code. Just move the declaration of the counter inside the lambda:
strings.forEach(s -> {
Integer counter = myMap.get(s);
if (counter == null) {
myMap.put(s, 1);
}
else {
myMap.put(s, counter + 1);
}
});
I have a map (instaWords) which is filled with thousands of words. I need to to loop over it N item at a time. Here is my code. In this code I need to read instaWords in the chunks of e.g 500 words and execute "updateInstaPhrases" with those words. Any help?
private static Map<InstaWord, List<Integer>> instaWords = new HashMap<InstaWord, List<Integer>>();
// here a couple of words are added to instaWords
updateInstaPhrases(instaWords);
private static void updateInstaPhrases(Map<InstaWord, List<Integer>> wordMap)
throws SQLException, UnsupportedEncodingException {
for (Map.Entry<InstaWord, List<Integer>> entry : wordMap.entrySet()) {
InstaWord instaWord = entry.getKey();
List<Integer> profiles = entry.getValue();
pst.setBytes(1, instaWord.word.getBytes("UTF-8"));
pst.setBytes(2, instaWord.word.getBytes("UTF-8"));
pst.setBytes(3, (instaWord.lang == null) ?·
"".getBytes("UTF-8") :·
instaWord.lang.getBytes("UTF-8"));
String profilesList = "";
boolean first = true;
for (Integer p : profiles) {
profilesList += (first ? "" : ", ") + p;
first = false;
}
pst.setString(4, profilesList);
pst.addBatch();
}
System.out.println("Words batch executed");
pst.executeBatch();
con.commit();
}
What I need is to iterate through a hashmap 'in chunks' (e.g. 500 item each time)
You may keep a counter, initialize to 0 and increment for each item, while collecting the items as you see fit (like, say, ArrayList<Map.Entry<InstaWord, List<Integer>>>). If counter (after increment) equals 500, process the whole batch, reset counter to 0 and clear the collection.
Another option is to have the counter control the loop and declare explicitly the iterator you draw the Map.Entrys from. In this way it’s probably a bit clearer what is going on.