Use plus operator of integer as BiFunction - java

I have a map in which I want to count things. Previous to java 8, I would have had to put a zero in the map for every key, before I could so something like map.put(key, map.get(key)+1).
Since Java 8, I can now use Map's merge method like in the following example:
public class CountingMap {
private static final Map<Integer, Integer> map = new HashMap<> ();
public static Integer add (final Integer i1,
final Integer i2) {
return i1 + i2;
}
public static void main (final String[] args) {
map.merge (0, 1, CountingMap::add);
System.out.println (map); //prints {0=1}
map.merge (0, 1, CountingMap::add);
System.out.println (map); //prints {0=2}
}
}
My question is, can I pass a reference to the + operator of Integer as BiFunction instead of having to declare my own add function?
I already tried things like Integer::+, Integer::operator+, but none of those works.
EDIT: As Tunaki pointed out, I could've used Integer::sum instead. Still, I'm wondering whether there is a possibility to pass an operator directly as reference.

There is no way to pass + operator in Java.
You can instantiate add directly in method call.
map.merge (0, 1, (i, j) -> i + j);
Or assign variable:
BiFunction<Integer, Integer, Integer> add = (i, j) -> i + j;
Or the same things with Integer::sum

Related

How to sort an array of strings in a customized order?

I have an array of strings in Java which I read from an excel file where the orders will vary every time:
//This is an example of users list
String[] usersList = ["Printers", "Configuration","Admin", "Service Desk", "Event Manager"]
I want to sort the array of strings in a customized order as:
private final String[] userSortOrder = ["Admin","Printers","Configurations","Event Manager","Service Desk"]
How can I apply the sorted order into my userList array of strings?
You just need to use sort method from java utils Array, giving a comparator object, with a method compare that should return:
- a negative number if first object is under second one
- a positive number if first object is above second one
- 0 if both objects are equal
, as:
Arrays.sort(sorted, new Comparator<String>() {
public int compare(String o1, String o2) {
int result = 0;
// Ordering algorithm here
return result;
}});
I recommend using a Map to store the order of those Strings, then sort using a custom Comparator.
HashMap<String, Integer> orderMap = new HashMap<>();
for (int i = 0; i < userSortOrder.length; i++)
orderMap.put(userSortOrder[i], i);
Arrays.sort(usersList, (a, b) -> orderMap.get(a) - orderMap.get(b));
Note that you must make sure every String in userList has its own order in orderMap.
Use the overload of Arrays.sort that takes an implementation of Comparator<T> as the second parameter. Implement the Comparator<String> interface to implement custom comparison.
I have used a HashMap to assign a weight/priority to each entry and then used this priority to compare. An object of higher priority will be considered greater. You can customize the priority by changing the second argument of map.put(). The way the compare function of the Comparator<T> interface works is, for every two objects, if:
if the int returned is negative, second element is greater
if the int returned is 0, both elements are equal
if the int returned is positive, first element is greater
class CustomComparator implements Comparator<String>
{
HashMap<String, Integer> map;
public CustomComparator()
{
map = new HashMap<>();
map.put("Admin", 1);
map.put("Printers", 2);
map.put("Configuration", 3);
map.put("Event Manager", 4);
map.put("Service Desk", 5);
}
public int compare(String s1, String s2) {
return map.get(s1) - map.get(s2);
}
}
class Main {
public static void main(String []args){
System.out.println("Hello World");
String[] usersList = {"Printers", "Configuration","Admin", "Service Desk", "Event Manager"};
System.out.println("Before sorting: ");
Arrays.toString(usersList);
Arrays.sort(usersList, new CustomComparator());
System.out.println("After sorting: ");
Arrays.toString(usersList);
}
}
Since Comparator<T> is a functional interface, you can also use a lambda expression and define the map in the same function:
HashMap<String, Integer> map = new HashMap<>();
map.put("Admin", 1);
map.put("Printers", 2);
map.put("Configuration", 3);
map.put("Event Manager", 4);
map.put("Service Desk", 5);
Arrays.sort(usersList, (s1, s2) -> map.get(s1) - map.get(s2));
System.out.println("After sorting:");
Arrays.toString(usersList);

Lambda expressions Map.computeIfPresent() and Map.merge() [duplicate]

A very common operation on maps of collections is to create a new collection with an initial value when the key is not present, or if the key is present, do some function on the existing collection. Take for example a Map<String, Set<Integer>>, if the key is not there, create a Set with an initial value of 1. If the key is there, add the value map.size()+1 to the set (or replace this function with some other simple one-liner operation). In Java 7, it's straightforward with if/else, but pretty verbose. I can only come up with the below code for Java 8, which isn't much better (actually worse due to more lines of code). Is there a way to make this more concise?
public void process(Map<String, Set<Integer>> m, String key) {
m.compute(key, (k, v) -> {
if (v == null) {
v = new HashSet<>();
v.add(1);
return v;
} else {
v.add(v.size() + 1);
return v;
}
});
}
Here's another alternative:
Set<Integer> set = m.computeIfAbsent (key , k -> new HashSet<> ());
set.add(set.size() + 1);
The only reason this is a two liner (instead of one) is the need to obtain the current size of the Set in order to decide which value to add to it.
Not a one-liner unfortunately but it does its magic and is also directly more readable (downside : it creates a new HashSet<>() everytime)
m.putIfAbsent(key, new HashSet<>());
// Solution 1 :
m.compute(key, (k, v) -> {v.add(v.size() + 1); return v;});
// Solution 2 :
Set<Integer> s = m.get(key);
s.add(s.size() + 1);
Or as proposed by #Thilo and inspired by #Eran
m.computeIfAbsent(key, k -> new HashSet<>()).add(m.get(key).size() + 1);
The one liner is possible because it returns the value it computed as mentioned in the javadoc
If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.
There is even a similar example in the javadoc
map.computeIfAbsent(key, k -> new HashSet()).add(v);
The little trade-off of the liner is the extra call to m.get(key) which is not happening with the solution of #Eran
Set<Integer> v = m.getOrDefault(key, new HashSet<>());
v.add(v.size() + 1);
m.put(key, v);

Counting each distinct array occurrence in a list of arrays with duplicates

PROBLEM
I have a list of arrays and I want to count the occurrences of duplicates.
For example, if I have this :
{{1,2,3},
{1,0,3},
{1,2,3},
{5,2,6},
{5,2,6},
{5,2,6}}
I want a map (or any relevant collection) like this :
{ {1,2,3} -> 2,
{1,0,3} -> 1,
{5,2,6} -> 3 }
I can even lose the arrays values, I'm only interested in cardinals (e.g. 2, 1 and 3 here).
MY SOLUTION
I use the following algorithm :
First hash the arrays, and check if each hash is in an HashMap<Integer, ArrayList<int[]>>, let's name it distinctHash, where the key is the hash and the value is an ArrayList, let's name it rowList, containing the different arrays for this hash (to avoid collisions).
If the hash is not in distinctHash, put it with the value 1 in another HashMap<int[], Long> that counts each occurrence, let's call it distinctElements.
Then if the hash is in distinctHash, check if the corresponding array is contained in rowList. If it is, increment the value in distinctElements associated to the identical array found in rowList. (If you use the new array as a key you will create another key since their reference are different).
Here is the code, the boolean returned tells if a new distinct array was found, I apply this function sequentially on all of my arrays :
HashMap<int[], Long> distinctElements;
HashMap<Integer, ArrayList<int[]>> distinctHash;
private boolean addRow(int[] row) {
if (distinctHash.containsKey(hash)) {
int[] indexRow = distinctHash.get(hash).get(0);
for (int[] previousRow: distinctHash.get(hash)) {
if (Arrays.equals(previousRow, row)) {
distinctElements.put(
indexRow,
distinctElements.get(indexRow) + 1
);
return false;
}
}
distinctElements.put(row, 1L);
ArrayList<int[]> rowList = distinctHash.get(hash);
rowList.add(row);
distinctHash.put(hash, rowList);
return true;
} else {
distinctElements.put(row, 1L);
ArrayList<int[]> newValue = new ArrayList<>();
newValue.add(row);
distinctHash.put(hash, newValue);
return true;
}
}
QUESTION
The problem is that my algorithm is too slow for my needs (40s for 5,000,000 arrays, and 2h-3h for 20,000,000 arrays). Profiling with NetBeans told me that the hashing takes 70% of runtime (using Google Guava murmur3_128 hash function).
Is there another algorithm that could be faster? As I said I'm not interested in arrays values, only in the number of their occurrences. I am ready to sacrifice precision for speed so a probabilistic algorithm is fine.
Wrap the int[] in a class that implements equals and hashCode, then build Map of the wrapper class to instance count.
class IntArray {
private int[] array;
public IntArray(int[] array) {
this.array = array;
}
#Override
public int hashCode() {
return Arrays.hashCode(this.array);
}
#Override
public boolean equals(Object obj) {
return (obj instanceof IntArray && Arrays.equals(this.array, ((IntArray) obj).array));
}
#Override
public String toString() {
return Arrays.toString(this.array);
}
}
Test
int[][] input = {{1,2,3},
{1,0,3},
{1,2,3},
{5,2,6},
{5,2,6},
{5,2,6}};
Map<IntArray, Long> map = Arrays.stream(input).map(IntArray::new)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
map.entrySet().forEach(System.out::println);
Output
[1, 2, 3]=2
[1, 0, 3]=1
[5, 2, 6]=3
Note: The above solution is faster and uses less memory than solution by Ravindra Ranwala, but it does require the creation of an extra class, so it is debatable which is better.
For smaller arrays, use the simpler solution below by Ravindra Ranwala.
For larger arrays, the above solution is likely better.
Map<List<Integer>, Long> map = Stream.of(input)
.map(a -> Arrays.stream(a).boxed().collect(Collectors.toList()))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
You may do it like so,
Map<List<Integer>, Long> result = Stream.of(source)
.map(a -> Arrays.stream(a).boxed().collect(Collectors.toList()))
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
And here's the output,
{[1, 2, 3]=2, [1, 0, 3]=1, [5, 2, 6]=3}
If the sequence of elements for all duplication of that array is like each other and the length of each array is not much, you can map each array to an int number and using from last part of your method. Although this method decrease the time of hashing, there are some assumptions here which might not be true for your case.

How is this piece of Recursive lambda call in Java working

I recently came across this piece of code in Java. It involves Function and printing fibonacci numbers and it works.
public class AppLambdaSubstitution {
public static Function<Integer, Integer> Y(Function<Function<Integer, Integer>, Function<Integer, Integer>> f) {
return x -> f.apply(Y(f)).apply(x);
}
public static void main(String[] args) {
Function<Integer, Integer> fib = Y(
func -> x -> {
if (x < 2)
return x;
else
return func.apply(x - 1) + func.apply(x - 2);
});
IntStream.range(1,11).
mapToObj(Integer::valueOf).
map(fib).forEach(System.out::println);
}
}
The part that has me confused is return x -> f.apply(Y(f)).apply(x);. Isn't Y(f) a recursive call to the method Y? We keep calling it with the Function f as a parameter. To me, there's no base case for this recursive call to return from. Why is there no overflow resulting from an endless recursive call?
Fundamentally you are missing the point that x -> f.apply(Y(f)).apply(x); will not call apply, it will return a Function.
That's just a very complicated (and non-intuitive?) way of showing currying and recursive function IMO. Things would be much simpler if you would replace a couple of things and make it a bit more readable.
This construction:
Function<Function<Integer, Integer>, Function<Integer, Integer>>
is not needed at all, since the left parameter is not used at all. It's simply needed to get a hold of the right one. As such the left parameter could be anything at all (I will later replace it with Supplier - that is not needed either, but just to prove a point).
Actually all you care about here is this Function that does the actual computation for each element of the Stream:
public static Function<Integer, Integer> right() {
return new Function<Integer, Integer>() {
#Override
public Integer apply(Integer x) {
if (x < 2) {
return x;
} else {
return apply(x - 1) + apply(x - 2);
}
}
};
}
Now you could write that entire construct with:
Supplier<Function<Integer, Integer>> toUse = () -> right();
Function<Integer, Integer> fib = curry(toUse);
IntStream.range(1, 11)
.mapToObj(Integer::valueOf)
.map(fib)
.forEach(System.out::println);
This Supplier<Function<Integer, Integer>> toUse = () -> right(); should make you understand why in the previous example (Function<Function, Function>) the left part was needed - just to get a hold of the right one.
If you look even closer, you might notice that the Supplier is entirely not needed, thus you could even further simplify it with:
IntStream.range(1, 11)
.mapToObj(Integer::valueOf)
.map(right())
.forEach(System.out::println);

Increment an Integer within a HashMap

Do I have to return the object and then put a new one in ? Or can I just directly increment ?
Integer temp = myMap.get(key);
temp++;
myMap.put(key, temp);
there is no way to just do this (this doesn't work) :
myMap.get(key)++;
This is the shortest code that does this job.
myMap.put(key, myMap.get(key) + 1)
I think it is not too long.
In Java 8 there are new methods on Map which you can use with lambdas to solve this. First alternative, compute:
a.compute(key, (k, v) -> v+1);
Note that this only works if the hash is initialized for all possible keys.
If this is not guaranteed you can either change the above code to:
a.compute(key, (k, v) -> v == null ? 1 : v + 1);
Or use the merge method (which I would prefer):
a.merge(key, 1, (a, b) -> a + b);
Maybe there are more lambda based methods I am not aware of.
You can use a mutable integer such as AtomicInteger.
Map<Key, AtomicInteger> myMap = new HashMap<Key, AtomicInteger>();
myMap.get(key).incrementAndGet();
Or you can use Trove4j which supports primitives in collections.
TObjectIntHashMap<Key> myMap;
myMap.increment(key);
Do I have to return the object and then put a new one in ?
As long as you use the Integer wrapper class yes, because it's immutable. You could use a mutable wrapper class instead, even one that has an increment() method. However, you then lose the ability to use autoboxing and autounboxing on the values.
You can't directly increment it, because it is immutable. You have to increment it and put the new object back.
Auto boxing is also interfering here. In fact what's happening is something similar to:
Integer i1 = getFromMap();
i1 = Integer.valueOf(++ i1.intValue());
So here your reference points to a new object. You have to put that object back in the map, under the same key.
As Integer are immutable, yes, you have to do it that way.
If you really want to increment it directly, you'll have to write your own mutable class.
If you have to do this more than twice you'd prefer to create a tiny class like:
public class MappedCounter {
private Map<String, Integer> map = new HashMap<String, Integer>();
public void addInt(String k, int v) {
if (!map.containsKey(k)) map.put(k, v);
else map.put(k, map.get(k) + v);
}
public int getInt(String k) {
return map.containsKey(k) ? map.get(k) : 0;
}
public Set<String> getKeys() {
return map.keySet();
}
}
Here are solutions using a Map (Java 8+), and a primitive Map and Bag using Eclipse Collections (EC).
JDK Map
Map<String, Integer> map = new HashMap<>();
map.merge("item", 1, Integer::sum);
Integer count = map.getOrDefault("item", 0);
EC Primitive Map
MutableObjectIntMap<String> map = ObjectIntMaps.mutable.empty();
map.addToValue("item", 1);
int count = map.getIfAbsent("item", 0);
EC Bag
MutableBag<String> bag = Bags.mutable.empty();
bag.add("item");
int count = bag.occurrencesOf("item");
The benefit of the primitive Map or Bag (which wraps a primitive Map) is that there is no boxing of the count values, and adding is explicit in both method names (addToValue / add). A Bag is a better data structure IMO if you want to simply count things.
Note: I am a committer for Eclipse Collections.
First of all: be aware of unboxing: the temp is from type Integer. But the operation ++ is implemented for int. So temp is unboxed to type int. This means if temp is null you run in a NullPointerException.
And you have to do it like you discripted in your first code block.
I use the below code and it works but at the beginning you need to define a BiFunction describing that the operation is incrementing by 1.
public static Map<String, Integer> strInt = new HashMap<String, Integer>();
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> bi = (x,y) -> {
if(x == null)
return y;
return x+y;
};
strInt.put("abc", 0);
strInt.merge("abc", 1, bi);
strInt.merge("abc", 1, bi);
strInt.merge("abc", 1, bi);
strInt.merge("abcd", 1, bi);
System.out.println(strInt.get("abc"));
System.out.println(strInt.get("abcd"));
}
output is
3
1
Just for completeness in Java 8 there is a longAdder which brings some benefits in comparison to AtomicInteger (http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/LongAdder.html)
final Map<WhatEver, LongAdder> result = new HashMap<>();
result.get(WhatEver).increment();
This should work
// If the key you want to add does not exist then add it as a new key
// And make the value 1
if (map.get(key) == null) {
map.put(key, 1);
} else {
// If the key does exist then replace the key's value with it's
// Original value plus one
map.put(key, map.get(key) + 1);
}
Found this to be the best way, avoiding NPE.
Map<Integer, Integer> map = new HashMap<>();
map.put(5, map.getOrDefault(5, 0) + 1);
System.out.println(map.get(5));
Output:
1

Categories