Loop over 3 lists that is inside a map - java

I have a map which consists of 3 lists as follows.
User user = new User();
user.setId(1);
// more user creation
List<User> usersOne = Arrays.asList(user1, user2, user3);
// more lists created
// This is the map with 3 lists. Adding data to map
Map<String, List<User>> map = new HashMap<>();
map.put("key1", usersOne);
map.put("key2", usersTwo);
map.put("key3", usersThree);
Above is just to show example. This map is constructed this way and coming from a rest call.
Is there a way I could loop over those lists for all 3 keys and add per item to a new list?
Meaning like this.
List<User> data = new ArrayList<>();
List<User> list1 = map.get("key1");
List<User> list2 = map.get("key2");
List<User> list3 = map.get("key3");
data.add(list1.get(0));
data.add(list2.get(0));
data.add(list3.get(0));
// and then
data.add(list1.get(1));
data.add(list2.get(1));
data.add(list3.get(1));
and so on. Or a better way to do it. Ultimate looking to get a new list of the users by getting them in this manner, 1 from each list and then move on to next index.
Note that the 3 lists are not of same length.
Was looking to see if I could achieve it via something like the following.
But looks expensive. Could I get some advice on how I could achieve this?
for (User list1User : list1) {
for (User list2User : list2) {
for (User list3User: list3) {
// write logic in here since I now do have access to all 3 lists.
// but is expensive plus also going to run into issues since the length is not the same for all 3 lists.
}
}
}

Simple solution for your problem is "use index".
int size1 = list1.size();
int size2 = list2.size();
int size3 = list3.size();
//Choose maxSize of (size1,size2,size3)
int maxSize = Math.max(size1,size2);
maxSize = Math.max(maxSize,size3);
then use single loop, only add with condition i < listSize:
for(int i=0;i<maxSize;i++){
if(i < size1) data.add(list1.get(i);
if(i < size2) data.add(list2.get(i);
if(i < size3) data.add(list3.get(i);
}
In case you have more than 3 list in map:
Collection<List<Object>> allValues = map.values();
int maxSize = allValues.stream().map(list-> list.size()).max(Integer::compare).get();
List<Object> data = new ArrayList<>();
for(int i=0;i<maxSize;i++) {
for(List<Object> list: allValues) {
if(i < list.size()) data.add(list.get(i));
}
}
Ps: you might get warning because i am using notepad++ not editor tool.

Not very simple but very concise and pragmatic would be to extract the lists from the map, flatten them and then collect them. Since Java 8 we can utilize streams for that.
List<User> data = map.values().stream()
.flatMap(users -> users.stream())
.collect(Collectors.toList());
With .values() you get a Set<List<User>> of your UserLists, i assume, that you don't need the keys.
With .flatMap() we gather all users from each list. And we use flatMap() instead of map() because we want to retrieve the users from each list.
The last method-call .collect(Collectors.toList() collects all elements from this stream and puts them into a read-only-list.
In this case, you would preserve multiple occurrences of the same user, which you wouldn't if you collect them as a set collect(Collectos.asSet()), given they are really different objects.

Related

Best way to get unique list without changing the Order

I have a list of string or list of integers of 20,000 items
Now it contains duplicates...However i don't want to disturb the order of the item.
We can easily convert a list to Set for unique Set unique = new HashSet(list);
However the above breaks the sequential order of the items.
What would be the best approach for this?
Thanks.
You should use java.util.LinkedHashSet to get unique elements without changing the order:
Set<String> uniqueSet = new LinkedHashSet<>(list);
One other way is to use distinct():
list.stream().distinct().collect(Collectors.toList())
But distinct() uses LinkedHashSet internally. There is no need for unnecessary procedure.
So best way is using the LinkedHashSet constructor:
LinkedHashSet(Collection c) Constructs a new linked hash
set with the same elements as the specified collection.
You can try stream distinct
yourList.stream().distinct().collect(Collectors.toList());
Update1:
As I know, this is the best solution.
list.contains(element) will do 2 loop processes. One for iterate the element and add it to new list, one for check element is contained -> 0(n*n)
new LinkedHashSet() will created a new LinkedHashSet, and a new Arraylist output -> issue about memory. And the performance, i think it is equals with stream distinct
Update2: we must ensure that the output is a List, not a Set
As I know, stream distinct use HashSet internally. It is an more efficient memory implementation than LinkedHashSet (which is hash table and linked list implementation of the set interface) in our case.
Detail here
If you apply LinkedHashSet, the source code will something like below, so we have 1 ArrayList and 1 LinkedHashSet.
output = new ArrayList(new LinkedHashSet(yourList));
I did a small benchmark with 1k for-loop.
int size = 1000000;
Random rand = new Random((int) (System.currentTimeMillis() / 1000));
List<Integer> yourList = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
yourList.add(rand.nextInt(10000));
}
// test1: LinkedHashSet --> 35ms
new ArrayList<Integer>(new LinkedHashSet<Integer>(yourList));
// test2: Stream distinct --> 30ms
yourList.stream().distinct().collect(Collectors.toList());
If you don't want to break the order, then Iterate the list and make a new list as below.
ArrayList<Integer> newList = new ArrayList<Integer>();
for (Integer element : list) {
if (!newList.contains(element)) {
newList.add(element);
}
}
Try the bellow code
public static void main(String[] args) {
String list[] = {"9","1","1","9","2","7","2"};
List<String> unique = new ArrayList<>();
for(int i=0; i<list.length; i++) {
int count = unique.size();
if(count==0) {
unique.add(list[i]);
}else {
boolean available = false;
for(int j=0; j<count; j++) {
if(unique.get(j).equals(list[i])) {
available = true;
break;
}
}
if(!available) {
unique.add(list[i]);
}
}
}
//checking latest 'unique' value
for(int i=0; i<unique.size(); i++) {
System.out.println(unique.get(i));
}
}
It will return 9 1 2 7, but I haven't tried up to 20,000 collection lists, hopefully there are no performance issues
If you are trying to eliminate duplicates, you can use LinkedHashSet it will maintain the order.
if String
Set<String> dedupSet = new LinkedHashSet<>();
if Integer
Set<Integer> dedupSet = new LinkedHashSet<>();

How can I order a Map properly?

So I was trying to order a Map, and I learned that there is a thing in java called Comparator that helps me do it, but for some reason it appears to have no error but the program doesn't work... It's appears to have an infinite cicle but I don't know where.
public long[] top_most_active (TCD_community com, int N){
int i=0;
long[] array= new long[N];
Arrays.fill (array,0);
List<User> l = new ArrayList<>();
l = topX (N, com);
for (User u : l) {
array[i] = u.getid();
System.out.println (u.getid());
i++;
}
return array;
}
I want to order everything and pick up to my list the top N users with most posts!
public List<User> topX(int x , TCD_community com) {
List<User> l = new ArrayList<>();
Map<Integer, User> m = com.getUsers();
for (Map.Entry<Integer, User> entrada : m.entrySet()) {
User u = entrada.getValue();
l.add(u);
l.sort(new Comparator<User>() {
public int compare(User u1, User u2) {
if(u1.getpost_count() == u2.getpost_count()){
return 0;
}
else if (u1.getpost_count() > u2.getpost_count())
return -1;
else return 1;
}
});
}
return l.subList(0,x);
}
You're overcomplicated things. Consider the following:
The top_most_active method becomes:
public long[] top_most_active (TCD_community com, int N){
long[] result = topX(N, com).stream()
.mapToLong(User::getId)
.peek(id -> System.out.println(id))
.toArray();
}
then the topX method becomes:
public List<User> topX(int x , TCD_community com) {
return com.getUsers()
.stream()
.map(e -> e.getValue())
.sorted(Comparator.comparingLong(User::getpost_count).reversed())
.limit(x)
.collect(Collectors.toCollection(ArrayList::new));
}
This fixes the issue of you trying to sort the accumulating list in each iteration of the loop as well as removing most of the redundancy and boilerplate code you currently have.
You are trying to sort your list after adding each entry to the list.
You should simply collect all the users into a new list, and afterwards sort the list.
List<User> l = new ArrayList<>(com.getUsers().values());
l.sort( ... your comparator ...);
return l.subList(0, x);
Okay, so it seems you don't want to order a Map, you want to order a list.
What I think your code does now is a number of things: (1) It deals with a Map for some reason. I guess it's like Python enumerate(your_list) giving your indices, but that's unnecessary. (2) It also seemingly re-sorts the list at every single add. You don't need to do that.
Simply get all your elements and sort them later in one go. Getting things is a relatively simple task. Comparing and ordering them is relatively expensive, so you want to minimise the times you have to do that.
However it is, to order your list, just implement Comparator and invoke a sorting method with it. Doing this is easy now, since we have both natural order in int (and Integer, long, Long, etc.) and easy comparator creation via Comparator#comparing.
Get your list of users. Then,
Map<Integer, User> map = magicSupplier();
List<User> users = new ArrayList<>(map.values());
Comparator<User> comparator = Comparator.comparing(User::postCount)
Collections.sort(users, comparator.reversed()); // for most-to-least
With the assumption that your User object has a method postCount returning a naturally ordered integer or long that counts the number of posts. I think you call it something different.
Then do your sublisting and return.

Generation of child lists from a mother list

I have a list of N objects in input and I would like to group in daughter lists the objects of the same family. Objects in the same family share the attribute "File_identifier". Finally I would like to access these daughter lists via a key being an attribute of one of the objects of the daughter list.
SentinelReportModels is the parent list
HashMap<String, List<SentinelReportModel>> hashmap = new HashMap<String, List<SentinelReportModel>>();
for (int i = 0; i < sentinelReportModels.size(); i++) {
for (int k = 0; k < sentinelReportModels.size(); k++) {
if (sentinelReportModels.get(i).getIdentifiantfichier()
.equals(sentinelReportModels.get(k).getIdentifiantfichier()) ) {
ArrayList<SentinelReportModel> listeTemp = new ArrayList<>();
listeTemp.add(sentinelReportModels.get(i));
listeTemp.add(sentinelReportModels.get(k));
hashmap.put(sentinelReportModels.get(i).getTypeflux(),listeTemp);
}
}
}
However it does not work, I get X lists with duplicates.
I believe the below code should do the trick. It is basically, putting all the setinels with same Flux in a list. If flux id is different a new list is added to the map.
HashMap<String, List<SentinelReportModel>> hashmap = new HashMap<String, List<SentinelReportModel>>();
hashmap.put(,listeTemp);
for (int i = 0; i < sentinelReportModels.size(); i++) {
if (hashMap.get(sentinelReportModels.get(i).getTypeflux())==null) {
ArrayList<SentinelReportModel> list = new ArrayList<SentinelReportModel>();
list.add(sentinelReportModels.get(i));
hashMap.put(sentinelReportModels.get(i).getTypeflux(),list);
}else { hashMap.get(sentinelReportModels.get(i).getTypeflux()).add(sentinelReportModels.get(i));
}
}
This is easy to achieve with standard streams:
The group of child lists can be obtained using a group by:
Map<String, List<SentinelReportModel>> map =
sentinelReportModels.stream().collect(
Collectors.groupingBy(model -> model.getIdentifiantfichier()));
This will give you a map of <identifiantfichier, List<SentinelReportModel>>.
And this can be process normally as a string/list map.
Your inner loop has a flaw in the sense that it doesn't take into account that previous iterations may have already created a list for the current element's identifiantfichier and overwrites it anyway.

How can I check for duplicate values of an object in an array of objects, merge the duplicates' values, and then remove the duplicate?

Right now I have an array of "Dragon"s. Each item has two values. An ID and a Count. So my array would look something like this:
Dragon[] dragons = { new Dragon(2, 4),
new Dragon(83, 199),
new Dragon(492, 239),
new Dragon(2, 93),
new Dragon(24, 5)
};
As you can see, I have two Dragons with the ID of 2 in the array. What I would like to accomplish is, when a duplicate is found, just add the count of the duplicate to the count of the first one, and then remove the duplicate Dragon.
I've done this sort of successfully, but I would end up with a null in the middle of the array, and I don't know how to remove the null and then shuffle them.
This is what I have so far but it really doesn't work properly:
public static void dupeCheck(Dragon[] dragons) {
int end = dragons.length;
for (int i = 0; i < end; i++) {
for (int j = i + 1; j < end; j++) {
if (dragons[i] != null && dragons[j] != null) {
if (dragons[i].getId() == dragons[j].getId()) {
dragons[i] = new Item(dragons[i].getId(), dragons[i].getCount() + dragons[j].getCount());
dragons[j] = null;
end--;
j--;
}
}
}
}
}
You should most probably not maintain the dragon count for each dragon in the dragon class itself.
That aside, even if you are forced to use an array, you should create an intermeditate map to store your dragons.
Map<Integer, Dragon> idToDragon = new HashMap<>();
for (Dragon d : yourArray) {
// fetch existing dragon with that id or create one if none present
Dragon t = idToDragon.computeIfAbsent(d.getId(), i -> new Dragon(i, 0));
// add counts
t.setCount(t.getCount() + d.getCount());
// store in map
idToDragon.put(d.getId(), t);
}
Now the map contains a mapping between the dragons' ids and the dragons, with the correct counts.
To create an array out of this map, you can just
Dragon[] newArray = idToDragon.values().toArray(new Dragon[idToDragon.size()]);
You may be force to store the result in an array but that doesn't mean that you're force to always use an array
One solution could be using the Stream API, group the items adding the count and save the result into an array again. You can get an example of how to use the Stream API to sum values here. Converting a List<T> into a T[] is quite straightforward but anyways, you have an example here
The size of an array cannot be changed after it's created.
So you need to return either a new array or list containing the merged dragons.
public static Dragon[] merge(Dragon[] dragonArr) {
return Arrays.stream(dragonArr)
// 1. obtain a map of dragon IDs and their combined counts
.collect(groupingBy(Dragon::getId, summingInt(Dragon::getCount)))
// 2. transform the map entries to dragons
.entrySet().stream().map(entry -> new Dragon(entry.getKey(), entry.getValue()))
// 3. collect the result as an array
.toArray(Dragon[]::new);
}

How to Sort a list of strings and find the 1000 most common values in java

In java (either using external libraries or not) I need to take a list of approximately 500,000 values and find the most frequently occurring (mode) 1000. Doing my best to keep the complexity to a minimum.
What I've tried so far, make a hash, but I can't because it would have to be backwards key=count value =string, otherwise when getting the top 1000, my complexity will be garbage. and the backwards way doesn't really work great because I would be having a terrible complexity for insertion as I search for where my string is to be able to remove it and insert it one higher...
I've tried using a binary search tree, but that had the same issue of what the data would be for sorting, either on the count or the string. If it's on the string then getting the count for the top 1000 is bad, and vice versa insertion is bad.
I could sort the list first (by string) and then iterate over the list and keep a count until it changes strings. but what data structure should I use to keep track of the top 1000?
Thanks
I would first create a Map<String, Long> to store the frequency of each word. Then, I'd sort this map by value in descending order and finally I'd keep the first 1000 entries.
In code:
List<String> top1000Words = listOfWords.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.sorted(Map.Entry.comparingByValue().reversed())
.limit(1000)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
You might find it cleaner to separate the above into 2 steps: first collecting to the map of frequencies and then sorting its entries by value and keeping the first 1000 entries.
I'd separate this into three phases:
Count word occurrences (e.g. by using a HashMap<String, Integer>)
Sort the results (e.g. by converting the map into a list of entries and ordering by value descending)
Output the top 1000 entries of the sorted results
The sorting will be slow if the counts are small (e.g. if you've actually got 500,000 separate words) but if you're expecting lots of duplicate words, it should be fine.
I have had this question open for a few days now and have decided to rebel against Federico's elegant Java 8 answer and submit the least Java 8 answer possible.
The following code makes use of a helper class that associates a tally with a string.
public class TopOccurringValues {
static HashMap<String, StringCount> stringCounts = new HashMap<>();
// set low for demo. Change to 1000 (or whatever)
static final int TOP_NUMBER_TO_COLLECT = 10;
public static void main(String[] args) {
// load your strings in here
List<String> strings = loadStrings();
// tally up string occurrences
for (String string: strings) {
StringCount stringCount = stringCounts.get(string);
if (stringCount == null) {
stringCount = new StringCount(string);
}
stringCount.increment();
stringCounts.put(string, stringCount);
}
// sort which have most
ArrayList<StringCount> sortedCounts = new ArrayList<>(stringCounts.values());
Collections.sort(sortedCounts);
// collect the top occurring strings
ArrayList<String> topCollection = new ArrayList<>();
int upperBound = Math.min(TOP_NUMBER_TO_COLLECT, sortedCounts.size());
System.out.println("string\tcount");
for (int i = 0; i < upperBound; i++) {
StringCount stringCount = sortedCounts.get(i);
topCollection.add(stringCount.string);
System.out.println(stringCount.string + "\t" + stringCount.count);
}
}
// in this demo, strings are randomly generated numbers.
private static List<String> loadStrings() {
Random random = new Random(1);
ArrayList<String> randomStrings = new ArrayList<>();
for (int i = 0; i < 5000000; i++) {
randomStrings.add(String.valueOf(Math.round(random.nextGaussian() * 1000)));
}
return randomStrings;
}
static class StringCount implements Comparable<StringCount> {
int count = 0;
String string;
StringCount(String string) {this.string = string;}
void increment() {count++;}
#Override
public int compareTo(StringCount o) {return o.count - count;}
}
}
55 lines of code! It's like reverse code golf. The String generator creates 5 million strings instead of 500,000 because: why not?
string count
-89 2108
70 2107
77 2085
-4 2077
36 2077
65 2072
-154 2067
-172 2064
194 2063
-143 2062
The randomly generated strings can have values between -999 and 999 but because we are getting gaussian values, we will see numbers with higher scores that are closer to 0.
The Solution I chose to use was to first make a hash map with key value pairs as . I got the count by iterating over a linked list, and inserting the key value pair, Before insertion I would check for existence and if so increase the count. That part was quite straight forward.
The next part where I needed to sort it according to it's value, I used a library called guava published by google and it was able to make it very easy to sort by value instead of key using what they called a multimap. where they in a sense reverse the hash, and allow multiple values to be mapped to one key, so that I can have all my top 1000, opposed to some solutions mentioned above which didn't allow that, and would cause me to just get one value per key.
The last step was to iterate over the multimap (backwards) to get the 1000 most frequent occurrences.
Have a look at the code of the function if you're interested
private static void FindNMostFrequentOccurences(ArrayList profileName,int n) {
HashMap<String, Integer> hmap = new HashMap<String, Integer>();
//iterate through our data
for(int i = 0; i< profileName.size(); i++){
String current_id = profileName.get(i).toString();
if(hmap.get(current_id) == null){
hmap.put(current_id, 1);
} else {
int current_count = hmap.get(current_id);
current_count += 1;
hmap.put(current_id, current_count);
}
}
ListMultimap<Integer, String> multimap = ArrayListMultimap.create();
hmap.entrySet().forEach(entry -> {
multimap.put(entry.getValue(), entry.getKey());
});
for (int i = 0; i < n; i++){
if (!multimap.isEmpty()){
int lastKey = Iterables.getLast(multimap.keys());
String lastValue = Iterables.getLast(multimap.values());
multimap.remove(lastKey, lastValue);
System.out.println(i+1+": "+lastValue+", Occurences: "+lastKey);
}
}
}
You can do that with the java stream API :
List<String> input = Arrays.asList(new String[]{"aa", "bb", "cc", "bb", "bb", "aa"});
// First we compute a map of word -> occurrences
final Map<String, Long> collect = input.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// Here we sort the map and collect the first 1000 entries
final List<Map.Entry<String, Long>> entries = new ArrayList<>(collect.entrySet());
final List<Map.Entry<String, Long>> result = entries.stream()
.sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
.limit(1000)
.collect(Collectors.toList());
result.forEach(System.out::println);

Categories