Sort your list treemap by another list - java

Well, I saw other ways that involve list of arrays. Although nothing similar to this.
List<String> orderList = new ArrayList<>();
orderList.add("ARMOR");
orderList.add("ADIDAS");
orderList.add("NIKE");
I have my List<TreeMap<Brand,String>> brands returning this list.
[{NIKE=Shoes},{ADIDAS=Clothing},{ARMOR=Backpacks},{NIKE=Shorts}]
I want to sort this by the orderList provided to get the following:
[{ARMOR=Backpacks},{ADIDAS=Clothing},{NIKE=Shoes},{NIKE=Shorts}]
But I don't know how to.
Perhaps there are other ways to modify this as a list and reorder it, I am not sure.

You can convert the List<String> orderList to HashMap<String, Integer> indexMap:
["ARMOR", "ADIDAS", "NIKE"] -> {"ARMOR": 0, "ADIDAS": 1, "NIKE": 2}
Map<String, Integer> indexMap = IntStream.range(0, orderList.size()) // [0, 1, 2]
.boxed() // use int, not Integer
.collect(Collectors.toMap(i -> orderList.get(i), i -> i)); // add index to each element
Then, given TreeMap<String, String> brands you can do:
List<Map.Entry<String, String>> result = brands.entrySet()
.stream()
.filter(entry -> indexMap.contains(entry.getKey()) // Remove elements not in indexMap... and therefore also not in orderList
.sorted((a, b) -> Integer.compare(indexMap.get(a.getKey()), indexMap(b.getKey()))) // Sort all present brands to match the order from orderList
.collect(Collectors.toList());

You can use a customized Comparator directly with Collections.sort() method from the java.util.Collections API. Here is a copy-paste working example:
public class SortByList {
enum Brand {
ADIDAS, ARMOR, NIKE
}
static class BrandMap extends TreeMap<Brand, String> {
public BrandMap(Brand brand, String type) {
put(brand, type);
}
}
public static void main(String[] args) {
List<String> orderList = new ArrayList<>();
orderList.add("ARMOR");
orderList.add("ADIDAS");
orderList.add("NIKE");
List<TreeMap<Brand, String>> brands = new ArrayList<>();
brands.add(new BrandMap(Brand.NIKE, "Shoes"));
brands.add(new BrandMap(Brand.ADIDAS, "Clothing"));
brands.add(new BrandMap(Brand.ARMOR, "Backpacks"));
brands.add(new BrandMap(Brand.NIKE, "Shorts"));
brands.sort((o1, o2) -> {
Brand key1 = o1.keySet().iterator().next();
Brand key2 = o2.keySet().iterator().next();
int index1 = orderList.indexOf(key1.name());
int index2 = orderList.indexOf(key2.name());
return Integer.compare(index1, index2);
});
//[{ARMOR=Backpacks},{ADIDAS=Clothing},{NIKE=Shoes},{NIKE=Shorts}]
System.out.println(brands);
}
}
Just a note: I've created BrandMap just to easily generate the test data and populate them to the list. You can leave it and just take the Comparator implementation written in lambda expression. It should work.
Another update: If you care about performance and your orderList is likely to get bigger in time, using orderList.indexOf() inside the sort function won't do good. Because it makes a sequential search which costs O(N) for each sorting comparison:
brands.sort((o1, o2) -> {
Brand key1 = o1.keySet().iterator().next();
Brand key2 = o2.keySet().iterator().next();
// extra iteration costs extra O(N)
int index1 = orderList.indexOf(key1.name());
// extra iteration costs extra O(N)
int index2 = orderList.indexOf(key2.name());
return Integer.compare(index1, index2);
});
If this is the case, I suggest to store your orderList in a HashMap instead of a List or array if you have the chance. Alternatively, you can convert it in a one time operation and use your map before your sorting operation:
// this will cost O(N) only for once
Map<String, Integer> orderMap = new HashMap<>();
for (int i = 0; i < orderList.size(); i++) {
orderMap.put(orderList.get(i), i);
}
// then sort
brands.sort((o1, o2) -> {
Brand key1 = o1.keySet().iterator().next();
Brand key2 = o2.keySet().iterator().next();
// now use the map for getting indices in constant O(1) time
int index1 = orderMap.get(key1.name());
int index2 = orderMap.get(key2.name());
return Integer.compare(index1, index2);
});
A little extra: Surely, storing in a HashMap costs an extra O(N) space but I assume memory consumption is tolerable in your case.
You're welcome. Cheers!

Related

find the largest 3 shops using java stream

I have a list of shop objects that are grouped by the item they have.
class Shop{
String shopName;
String item;
int size;
...}
How can I get a list of the 3 biggest shops (or n biggest shops) for each item?
ie. suppose I have
Shop("Walmart", "Hammer", 100);
Shop("Target", "Scissor", 30);
Shop("Walgreens", "Hammer", 300);
Shop("Glens", "Hammer", 500);
Shop("Walmart", "Scissor", 75);
Shop("Toms", "Hammer", 150);
I want to return a list of the top 3 shops grouped by item.
I grouped the items but i am not sure how to iterate through the given Map or entryset...
public class Shop {
int size;
String item;
String name;
public Shop(int size, String item, String name){
this.size = size;
this.item = item;
this.name = name;
}
//Return a list of the top 3 largest shops by item
public static void main(){
List<Shop> shops = new LinkedList<Shop>();
Comparator<Shop> shopComparator = new Comparator<Shop>(){
#Override
public int compare(Shop f1, Shop f2) {
return f1.getSize() < f2.getSize() ? 1 : -1;
}
};
shops.stream().collect(groupingBy(Shop::getItem))
.entrySet()
.stream()
.filter(entry -> entry.getValue().stream().map )
.forEach(item -> item.getValue())//Stuck here
;
}
}
The most important thing that you can learn about streams is that they aren't inherently "better" than equivalent approaches by any measure. Sometimes, they make code more readable, other times, less so. Use them to clarify your code, and avoid them when they obfuscate it.
This is a case where your code will be far more readable by using a collector for this purpose. Coding your own is fairly easy, and if you really want to understand streams better, I recommend it as a simple learning exercise.
Here, I'm using MoreCollectors.greatest() from the StreamEx library:
Comparator<Shop> bySize = Comparator.comparingInt(Shop::getSize);
Map<String, List<Shop>> biggestByItem
= shops.stream().collect(groupingBy(Shop::getItem, greatest(bySize, 3)));
This isn't better because it's shorter, or because it is faster and uses constant memory; it's better because complexity is factored out of the code, and hidden behind meaningful names that explain the behavior. Instead of littering your application with complex pipelines that need to be read, tested, and maintained independently, you have written (or referenced) a reusable collector with a clear behavior.
As I mentioned, there is a bit of a learning curve in understanding how the pieces of a Collector work together, but it's worth studying. Here's a possible implementation for a similar collector:
public static <T> Collector<T, ?, List<T>> top(int limit, Comparator<? super T> order) {
if (limit < 1) throw new IndexOutOfBoundsException(limit);
Objects.requireNonNull(order);
Supplier<Queue<T>> supplier = () -> new PriorityQueue<>(order);
BiConsumer<Queue<T>, T> accumulator = (q, e) -> collect(order, limit, q, e);
BinaryOperator<Queue<T>> combiner = (q1, q2) -> {
q2.forEach(e -> collect(order, limit, q1, e));
return q1;
};
Function<Queue<T>, List<T>> finisher = q -> {
List<T> list = new ArrayList<>(q);
Collections.reverse(list);
return list;
};
return Collector.of(supplier, accumulator, combiner, finisher, Collector.Characteristics.UNORDERED);
}
private static <T> void collect(Comparator<? super T> order, int limit, Queue<T> q, T e) {
if (q.size() < limit) {
q.add(e);
} else if (order.compare(e, q.peek()) > 0) {
q.remove();
q.add(e);
}
}
Given this factory, it's trivial to create others that give you bottom(3, bySize), etc.
You may be interested in this related question and its answers.
Well, you could take the following steps:
With groupingBy(Shop::getItem), you could create a map which sorts by the item, so your result would be a Map<String, List<Shop>>, where the list contains all shops with that item.
Now we need to sort the List<Shop> in reversed order, so the top items of the list are the shops with the largest size. In order to do this, we could use collectingAndThen as downstream collector to groupingBy.
Collectors.collectingAndThen(Collectors.toList(), finisherFunction);
Our finisher function should sort the list:
list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list;
}
This would result in a Map<String, List<Shop>>, where the list is sorted, highest size first.
Now the only thing we need to do, is limiting the list size to 3. We could use subList. I think subList throws an exception if the list contains less than 3 items, so we need to use Math.min(3, list.size()) to take this into account.
list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list.subList(0, Math.min(3, list.size()));
}
The whole code then looks like this:
shops.stream()
.collect(groupingBy(Shop::item, Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list.subList(0, Math.min(3, list.size()));
})));
Online demo
Instead of 'manually' sorting the list and limiting it to 3, you could create a small class which automatically does this — both limit and sort the list upon adding elements.
Not as fancy as MC Emperor but it seems to work.
I started from the part you already did:
shops.stream().collect(Collectors.groupingBy(Shop::getItem))
.entrySet().stream().map(entry -> {
entry.setValue(entry.getValue().stream()
.sorted(Comparator.comparingInt(s->-s.size))
.limit(3) // only keep top 3
.collect(Collectors.toList()));
return entry;
}).forEach(item -> {
System.out.println(item.getKey()+":"+item.getValue());
});
You can use groupingBy along with limit to get desired result:
import static java.util.stream.Collectors.*;
// Define the sort logic. reversed() applies asc order (Default is desc)
Comparator<Shop> sortBySize = Comparator.comparingInt(Shop::getSize).reversed();
int limit = 3; // top n items
var itemToTopNShopsMap = list.stream().collect(
collectingAndThen(groupingBy(Shop::getItem),
itemToShopsMap -> getTopNShops(sortBySize, itemToShopsMap, limit)));
static Map<String, List<Shop>> getTopNShops(Comparator<Shop> sortBy, Map<String, List<Shop>> inMap, int limit) {
var returningMap = new HashMap<String, List<Shop>>();
for (var i : inMap.entrySet()) {
returningMap.put(i.getKey(), i.getValue().stream().sorted(sortBy).limit(Long.valueOf(limit)).collect(toList()));
}
return returningMap;
}
We took following steps:
Group the List by 'item'
For each grouping, i.e., item to list of shops entry, we sort the list of shops by predefined sort logic and collect (limit) the top n results.
Note:
In static method getTopNShops, mutation of source map is avoided. We could have written this method as a stream, but the stream version may have been less readable than the foreach loop.

Collect to map the order/position value of a sorted stream

I am sorting a populated set of MyObject (the object has a getName() getter) in a stream using a predefined myComparator.
Then once sorted, is there a way to collect into a map the name of the MyObject and the order/position of the object from the sort?
Here is what I think it should look like:
Set<MyObject> mySet; // Already populated mySet
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors.toMap(MyObject::getName, //HowToGetThePositionOfTheObjectInTheStream));
For example, if the set contain three objects (object1 with name name1, object2 with name name2, object3 with name name3) and during the stream they get sorted, how do I get a resulting map that looks like this:
name1, 1
name2, 2
name3, 3
Thanks.
A Java Stream doesn't expose any index or positioning of elements, so I know no way of replacing /*HowToGetThePositionOfTheObjectInTheStream*/ with streams magic to obtain the desired number.
Instead, one simple way is to collect to a List instead, which gives every element an index. It's zero-based, so when converting to a map, add 1.
List<String> inOrder = mySet.stream()
.sorted(myComparator)
.map(MyObject::getName)
.collect(Collectors.toList());
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < inOrder.size(); i++) {
nameMap.put(inOrder.get(i), i + 1);
}
Try this one. you could use AtomicInteger for value of each entry of map. and also to guarantee order of map use LinkedHashMap.
AtomicInteger index = new AtomicInteger(1);
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors
.toMap(MyObject::getName, value -> index.getAndIncrement(),
(e1, e2) -> e1, LinkedHashMap::new));
The simplest solution would be a loop, as a formally correct stream solution that would also work in parallel requires a nontrivial (compared to the rest) merge functions:
Map<String,Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(HashMap::new, (m, s) -> m.put(s.getName(), m.size()),
(m1, m2) -> {
int offset = m1.size();
m2.forEach((k, v) -> m1.put(k, v + offset));
});
Compare with a loop/collection operations:
List<MyObject> ordered = new ArrayList<>(mySet);
ordered.sort(myComparator);
Map<String, Integer> result = new HashMap<>();
for(MyObject o: ordered) result.put(o.getName(), result.size());
Both solutions assume unique elements (as there can be only one position). It’s easy to change the loop to detect violations:
for(MyObject o: ordered)
if(result.putIfAbsent(o.getName(), result.size()) != null)
throw new IllegalStateException("duplicate " + o.getName());
Dont use a stream:
List<MyObject> list = new ArrayList<>(mySet);
list.sort(myComparator);
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
nameMap.put(list.get(i).getName(), i);
}
Not only will this execute faster than a stream based approach, everyone knows what's going on.
Streams have their place, but pre-Java 8 code does too.

Overwriting values in a HashMap that are in an ArrayList<String>

Let's say I have a HashMap with String keys and Integer values:
map = {cat=1, kid=3, girl=3, adult=2, human=5, dog=2, boy=2}
I want to switch the keys and values by putting this information into another HashMap. I know that a HashMap cannot have duplicate keys, so I tried to put the information into a HashMap with the Integer for the keys that would map to a String ArrayList so that I could potentially have one Integer mapping to multiple Strings:
swap = {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
I tried the following code:
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (String x : map.keySet()) {
for (int i = 0; i <= 5; i++) {
ArrayList<String> list = new ArrayList<String>();
if (i == map.get(x)) {
list.add(x);
swap.put(i, list);
}
}
}
The only difference in my code is that I didn't hard code the number 5 into my index; I have a method that finds the highest integer value in the original HashMap and used that. I know it works correctly because I get the same output even if I hard code the 5 in there, I just didn't include it to save space.
My goal here is to be able to do this 'reversal' with any set of data, otherwise I could just hard code the value. The output I get from the above code is this:
swap = {1=[cat], 2=[boy], 3=[girl], 5=[human]}
As you can see, my problem is that the value ArrayList is only keeping the last String that was put into it, instead of collecting all of them. How can I make the ArrayList store each String, rather than just the last String?
With Java 8, you can do the following:
Map<String, Integer> map = new HashMap<>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
Map<Integer, List<String>> newMap = map.keySet()
.stream()
.collect(Collectors.groupingBy(map::get));
System.out.println(newMap);
The output will be:
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
you are recreating the arrayList for every iteration and i can't figure out a way to do it with that logic, here is a good way though and without the need to check for the max integer:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
List<String> get = swap.get(value);
if (get == null) {
get = new ArrayList<>();
swap.put(value, get);
}
get.add(key);
}
Best way is to iterate over the key set of the original map.
Also you have to asure that the List is present for any key in the target map:
for (Map.Entry<String,Integer> inputEntry : map.entrySet())
swap.computeIfAbsent(inputEntry.getValue(),()->new ArrayList<>()).add(inputEntry.getKey());
This is obviously not the best solution, but approaches the problem the same way you did by interchanging inner and outer loops as shown below.
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (Integer value = 0; value <= 5; value++) {
ArrayList<String> list = new ArrayList<String>();
for (String key : map.keySet()) {
if (map.get(key) == value) {
list.add(key);
}
}
if (map.containsValue(value)) {
swap.put(value, list);
}
}
Output
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
Best way I can think of is using Map.forEach method on existing map and Map.computeIfAbsent method on new map:
Map<Integer, List<String>> swap = new HashMap<>();
map.forEach((k, v) -> swap.computeIfAbsent(v, k -> new ArrayList<>()).add(k));
As a side note, you can use the diamond operator <> to create your new map (there's no need to repeat the type of the key and value when invoking the map's constructor, as the compiler will infer them).
As a second side note, it's good practice to use interface types instead of concrete types, both for generic parameter types and for actual types. This is why I've used List and Map instead of ArrayList and HashMap, respectively.
Using groupingBy like in Jacob's answer but with Map.entrySet for better performance, as suggested by Boris:
// import static java.util.stream.Collectors.*
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(groupingBy(Entry::getValue, mapping(Entry::getKey, toList())));
This uses two more methods of Collectors: mapping and toList.
If it wasn't for these two helper functions, the solution could look like this:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
groupingBy(
Entry::getValue,
Collector.of(
ArrayList::new,
(list, e) -> {
list.add(e.getKey());
},
(left, right) -> { // only needed for parallel streams
left.addAll(right);
return left;
}
)
)
);
Or, using toMap instead of groupingBy:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
toMap(
Entry::getValue,
(e) -> new ArrayList<>(Arrays.asList(e.getKey())),
(left, right) -> {
left.addAll(right);
return left;
}
)
);
It seams you override the values instrad of adding them to the already creared arraylist. Try this:
HashMap<Integer, ArrayList<String>> swapedMap = new HashMap<Integer, ArrayList<String>>();
for (String key : map.keySet()) {
Integer swappedKey = map.get(key);
ArrayList<String> a = swapedMap.get(swappedKey);
if (a == null) {
a = new ArrayList<String>();
swapedMap.put(swappedKey, a)
}
a.add(key);
}
I didn't have time to run it (sorry, don't have Java compiler now), but should be almost ok :)
You could use the new merge method in java-8 from Map:
Map<Integer, List<String>> newMap = new HashMap<>();
map.forEach((key, value) -> {
List<String> values = new ArrayList<>();
values.add(key);
newMap.merge(value, values, (left, right) -> {
left.addAll(right);
return left;
});
});

How to GROUP BY same strings in Java

I have got an Arraylist of strings and I need to return the Arraylist indexes of strings that are the same.
For example
Arraylist[0]: IPAddress
Arraylist[1]: DomainName
Arraylist[2]: IPAddress
Arraylist[3]: Filesize
The output should be:
Arraylist[0]
IPAddress|0,2 //0,2 denotes the arraylist index that is of the same
Arraylist[1]
DomainName|1
Arraylist[2]
Filesize|3
Any idea how can this be achieved?
What I have done is:
for(int i=0; i<arr.size(); i++){
if(arr.get(i).equals(arr.size()-1)){
//print index
}
}
With Java8 streams
List<String> strings = Arrays.asList("IPAddress", "DomainName", "IPAddress", "Filesize");
Map<String, List<Integer>> map = IntStream.range(0, strings.size()).boxed().collect(Collectors.groupingBy(strings::get));
System.out.println(map);
output
{DomainName=[1], Filesize=[3], IPAddress=[0, 2]}
To get the results in ordered
Map<String, List<Integer>> map = IntStream.range(0, strings.size())
.boxed()
.collect(Collectors.groupingBy(strings::get, LinkedHashMap::new, Collectors.toList()));
The mechanical steps are fairly straightforward:
Get a collection which can support a key (which is the string in your list) and a list of values representing the indexes in which they occur (which would be another ArrayList).
If the element exists in the collection, simply add the index to its value.
Otherwise, create a new list, add the index to that, then add that to the collection.
Here is some sample code below.
final List<String> list = new ArrayList<String>() {{
add("IPAddress");
add("DomainName");
add("IPAddress");
add("Filesize");
}};
final Map<String, List<Integer>> correlations = new LinkedHashMap<>();
for (int i = 0; i < list.size(); i++) {
final String key = list.get(i);
if (correlations.containsKey(key)) {
correlations.get(key).add(i);
} else {
final List<Integer> indexList = new ArrayList<>();
indexList.add(i);
correlations.put(key, indexList);
}
}
Any optimizations to the above are left as an exercise for the reader.

Sort list based on priority from a map

I have a simple map and need to create a list that is sorted based on the number in ascending order from a given list:
Map auto = new HashMap();
auto.put("Merc", 3);
auto.put("Citroen", 5);
auto.put("Opel", 10);
auto.put("BMW", 20);
List<String> given = new ArrayList<>();
given.add("Opel");
given.add("BMW");
given.add("Citroen");
So the given list needs to be sorted so that it will be in this order: Citroen, Opel, BMW. Was thinking of:
create another map then iterate through list
get number from first map
put the number as the key and the name as value in the new map
sort the map by key
iterate threw new map then add values to the list
This seems terrible :/, any suggestions and perhaps better data structures to use?
Using Java 8 you can do.
Map<String, Integer> auto = new HashMap<>();
auto.put("Merc", 3);
auto.put("Citroen", 5);
auto.put("Opel", 10);
auto.put("BMW", 20);
List<String> given = new ArrayList<>();
given.add("Opel");
given.add("BMW");
given.add("Citroen");
// to sort the selected elements.
given.sort(Comparator.comparing(auto::get));
// to sort all elements.
List<String> names = auto.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry::getValue))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Breaking this down
List<String> names =
// give the set of entries as a Stream.
auto.entrySet().stream()
// sort these entries, using the field returned by getValue()
.sorted(Comparator.comparing(Map.Entry::getValue))
// now sorted, turn each Entry into just the getKey()
.map(Map.Entry::getKey)
// now we have a stream of keys, turn this into a List<String>
.collect(Collectors.toList());
Collections#sort
Collections.sort(given, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return auto.get(o1).compareTo(auto.get(o2));
}
});
Or with lambda:
Collections.sort(given, (o1, o2) -> auto.get(o1).compareTo(auto.get(o2)));
Java 8 null-safe solution inspired from multiple answers
given.sort(Comparator.comparing((s) -> auto.getOrDefault(s, Integer.MAX_VALUE)));
With Java 8, you could just do
given.sort(Comparator.comparing(auto::get));
...and it's just that one-liner. Or with the Guava library you could do
Collections.sort(given, Ordering.natural().onResultOf(Functions.forMap(auto)));
Create a Car class that implements Comparable and includes the name and priority.
Then you can sort lists with Collections.sort() directly.
Map<String,Integer> auto = new HashMap<String,Integer>();
auto.put("Merc", 3);
auto.put("Citroen", 5);
auto.put("Opel", 10);
auto.put("BMW", 20);
Set<Map.Entry<String,Integer>> set = auto.entrySet();
List<Map.Entry<String,Integer>> list = new ArrayList<Map.Entry<String,Integer>>(set);
Collections.sort(list,new Comparator<Map.Entry<String,Integer>>(){
#Override
public int compare(Entry<String, Integer> o1,
Entry<String, Integer> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
Once you have List Objects of Map.Entry,you can extract key using Entry.getKey()

Categories