Effective way. of comparing list elements in Java - java

Is there any **effective way **of comparing elements in Java and print out the position of the element which occurs once.
For example: if I have a list: ["Hi", "Hi", "No"], I want to print out 2 because "No" is in position 2. I have solved this using the following algorithm and it works, BUT the problem is that if I have a large list it takes too much time to compare the entire list to print out the first position of the unique word.
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i < strings.size(); i++) {
int oc = Collections.frequency(strings, strings.get(i));
if (oc == 1)
System.out.print(i);
break;
}

I can think of counting each element's occurrence no and filter out the first element though not sure how large your list is.
Using Stream:
List<String> list = Arrays.asList("Hi", "Hi", "No");
//iterating thorugh the list and storing each element and their no of occurance in Map
Map<String, Long> counts = list.stream().collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));
String value = counts.entrySet().stream()
.filter(e -> e.getValue() == 1) //filtering out all the elements which have more than 1 occurance
.map(Map.Entry::getKey) // creating a stream of element from map as all of these have only single occurance
.findFirst() //finding the first element from the element stream
.get();
System.out.println(list.indexOf(value));
EDIT:
A simplified version can be
Map<String, Long> counts2 = new LinkedHashMap<String, Long>();
for(String val : list){
long count = counts2.getOrDefault(val, 0L);
counts2.put(val, ++count);
}
for(String key: counts2.keySet()){
if(counts2.get(key)==1){
System.out.println(list.indexOf(key));
break;
}
}
The basic idea is to count each element's occurrence and store them in a Map.Once you have count of all elements occurrences. then you can simply check for the first element which one has 1 as count.

You can use HashMap.For example you can put word as key and index as value.Once you find the same word you can delete the key and last the map contain the result.

If there's only one word that's present only once, you can probably use a HashMap or HashSet + Deque (set for values, Deque for indices) to do this in linear time. A sort can give you the same in n log(n), so slower than linear but a lot faster than your solution. By sorting, it's easy to find in linear time (after the sort) which element is present only once because all duplicates will be next to each other in the array.
For example for a linear solution in pseudo-code (pseudo-Kotlin!):
counters = HashMap()
for (i, word in words.withIndex()) {
counters.merge(word, Counter(i, 1), (oldVal, newVal) -> Counter(oldVald.firstIndex, oldVald.count + newVal.count));
}
for (counter in counters.entrySet()) {
if (counter.count == 1) return counter.firstIndex;
}
class Counter(firstIndex, count)

Map<String,Boolean> + loops
Instead of using Map<String,Integer> as suggested in other answers.
You can maintain a HashMap (if you need to maintain the order, use LinkedHashMap instead) of type Map<String,Boolean> where a value would denote whether an element is unique or not.
The simplest way to generate the map is method put() in conjunction with containsKey() check.
But there are also more concise options like replace() + putIfAbsent(). putIfAbsent() would create a new entry only if key is not present in the map, therefore we can associate such string with a value of true (considered to be unique). On the other hand replace() would update only existing entry (otherwise map would not be effected), and if entry exist, the key is proved to be a duplicate, and it has to be associated with a value of false (non-unique).
And since Java 8 we also have method merge(), which expects tree arguments: a key, a value, and a function which is used when the given key already exists to resolve the old value and the new one.
The last step is to generate list of unique strings by iterating over the entry set of the newly created map. We need every key having a value of true (is unique) associated with it.
List<String> strings = // initializing the list
Map<String, Boolean> isUnique = new HashMap<>(); // or LinkedHashMap if you need preserve initial order of strings
for (String next: strings) {
isUnique.replace(next, false);
isUnique.putIfAbsent(next, true);
// isUnique.merge(next, true, (oldV, newV) -> false); // does the same as the commented out lines above
}
List<String> unique = new ArrayList<>();
for (Map.Entry<String, Boolean> entry: isUnique.entrySet()) {
if (entry.getValue()) unique.add(entry.getKey());
}
Stream-based solution
With streams, it can be done using collector toMap(). The overall logic remains the same.
List<String> unique = strings.stream()
.collect(Collectors.toMap( // creating intermediate map Map<String, Boolean>
Function.identity(), // key
key -> true, // value
(oldV, newV) -> false, // resolving duplicates
LinkedHashMap::new // Map implementation, if order is not important - discard this argument
))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.toList(); // for Java 16+ or collect(Collectors.toList()) for earlier versions

Related

Generate a Map from a list using Streams with Java 8

I have a list of String.
I want to store each string as key and the string's length as value in a Map (say HashMap).
I'm not able to achieve it.
List<String> ls = Arrays.asList("James", "Sam", "Scot", "Elich");
Map<String,Integer> map = new HashMap<>();
Function<String, Map<String, Integer>> fs = new Function<>() {
#Override
public Map<String, Integer> apply(String s) {
map.put(s,s.length());
return map;
}
};
Map<String, Integer> nmap = ls
.stream()
.map(fs).
.collect(Collectors.toMap()); //Lost here
System.out.println(nmap);
All strings are unique.
There's no need to wrap each and every string with its own map, as the function you've created does.
Instead, you need to provide proper arguments while calling Collectors.toMap() :
keyMapper - a function responsible for extracting a key from the stream element.
valueMapper - a function that generates a value from the stream element.
Hence, you need the stream element itself to be a key we can use Function.identity(), which is more descriptive than lambda str -> str, but does precisely the same.
Map<String,Integer> lengthByStr = ls.stream()
.collect(Collectors.toMap(
Function.identity(), // extracting a key
String::length // extracting a value
));
In case when the source list might contain duplicates, you need to provide the third argument - mergeFunction that will be responsible for resolving duplicates.
Map<String,Integer> lengthByStr = ls.stream()
.collect(Collectors.toMap(
Function.identity(), // key
String::length, // value
(left, right) -> left // resolving duplicates
));
You said there would be no duplicate Strings. But if one gets by you can use distinct() (which internally uses set) to ensure it doesn't cause issues.
a-> a is a shorthand for using the stream value. Essentially a lambda that returns its argument.
distinct() removes any duplicate strings
Map<String, Integer> result = names.stream().distinct()
.collect(Collectors.toMap(a -> a, String::length));
If you want to get the length of a String, you can do it immediately as someString.length(). But suppose you want to get a map of all the Strings keyed by a particular length. You can do it using Collectors.groupingBy() which by default puts duplicates in a list. In this case, the duplicate would be the length of the String.
use the length of the string as a key.
the value will be a List<String> to hold all strings that match that length.
List<String> names = List.of("James", "Sam", "Scot",
"Elich", "lucy", "Jennifer","Bob", "Joe", "William");
Map<Integer, List<String>> lengthMap = names.stream()
.distinct()
.collect(Collectors.groupingBy(String::length));
lengthMap.entrySet().forEach(System.out::println);
prints
3=[Sam, Bob, Joe]
4=[Scot, lucy]
5=[James, Elich]
7=[William]
8=[Jennifer]

Check if list of integers contains two groups of different repeated numbers

How to using java stream, check if list of integers contains two groups of different repeated numbers. Number must be repeated not more then two time.
Example: list of 23243.
Answer: true, because 2233
Example 2: list of 23245.
Answer: none
Example 3: list of 23232.
Answer: none, because 222 repeated three times
One more question, how can i return not anyMatch, but the biggest of repeated number?
listOfNumbers.stream().anyMatch(e -> Collections.frequency(listOfNumbers, e) == 2)
This will tell you if the list meets your requirements.
stream the list of digits.
do a frequency count.
stream the resultant counts
filter out those not equal to a count of 2.
and count how many of those there are.
Returns true if final count == 2, false otherwise.
List<Integer> list = List.of(2,2,3,3,3,4,4);
boolean result = list.stream()
.collect(Collectors.groupingBy(a -> a,
Collectors.counting()))
.values().stream().filter(count -> count == 2).limit(2)
.count() >= 2; // fixed per OP's comment
The above prints true since there are two groups of just two digits, namely 2's and 4's
EDIT
First, I made Holger's suggestion to short circuit the count check.
To address your question about returning multiple values, I broke up the process into parts. The first is the normal frequency count that I did before. The next is gathering the information requested. I used a record to return the information. A class would also work. The max count for some particular number is housed in an AbstractMap.SimpleEntry
List<Integer> list = List.of(2, 3, 3, 3, 4, 4, 3, 2, 3);
Results results = groupCheck(list);
System.out.println(results.check);
System.out.println(results.maxEntry);
Prints (getKey() and getValue() may be used to get the individual values. First is the number, second is the occurrences of that number.)
true
3=5
The method and record declaration
record Results(boolean check,
AbstractMap.SimpleEntry<Integer, Long> maxEntry) {
}
Once the frequency count is computed, simply iterate over the entries and
count the pairs and compute the maxEntry by comparing the existing maximum count to the iterated one and update as required.
public static Results groupCheck(List<Integer> list) {
Map<Integer, Long> map = list.stream().collect(
Collectors.groupingBy(a -> a, Collectors.counting()));
AbstractMap.SimpleEntry<Integer, Long> maxEntry =
new AbstractMap.SimpleEntry<>(0, 0L);
int count = 0;
for (Entry<Integer, Long> e : map.entrySet()) {
if (e.getValue() == 2) {
count++;
}
maxEntry = e.getValue() > maxEntry.getValue() ?
new AbstractMap.SimpleEntry<>(e) : maxEntry;
}
return new Results(count >= 2, maxEntry);
}
One could write a method which builds a TreeMap of the frequencies.
What happens here, is that a frequency map is built first (by groupingBy(Function.identity(), Collectors.counting()))), and then we must 'swap' the keys and values, because we want to use the frequencies as keys.
public static TreeMap<Long, List<Integer>> frequencies(List<Integer> list) {
return list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.collect(Collectors.toMap(e -> e.getValue(), e -> List.of(e.getKey()), (a, b) -> someMergeListsFunction(a, b), TreeMap::new));
}
And then we can just use our method like this:
// We assume the input list is not empty
TreeMap<Long, List<Integer>> frequencies = frequencies(list);
var higher = frequencies.higherEntry(2L);
if (higher != null) {
System.out.printf("There is a number which occurs more than twice: %s (occurs %s times)\n", higher.getValue().get(0), higher.getKey());
}
else {
List<Integer> occurTwice = frequencies.lastEntry().getValue();
if (occurTwice.size() < 2) {
System.out.println("Only " + occurTwice.get(0) " occurs twice...");
}
else {
System.out.println(occurTwice);
}
}
A TreeMap is a Map with keys sorted by some comparator, or the natural order if none is given. The TreeMap class contains methods to search for certain keys. For example, the higherEntry method returns the first entry which is higher than the given key. With this method, you can easily check if a key higher than 2 exists, for one of the requirements is that none of the numbers may occur more than twice.
The above code checks whether there is a number occurring more than twice, that is when higherEntry(2L) returns a nonnull value. Otherwise, lastEntry() is the highest number occurring. With getValue(), you can retrieve the list of these numbers.

how to reduce a map to get a pair or entry

I have a map ,the Integer stands for the frequency and the Set stands for a Set of words ,Now I want to reduce this map to get the frequency of the set with the most words ,and the return the frequency and the number of words in that Set with a pair
public static Map<Integer, Set<String>> getCounts(In in)
My instinct tells me this is ok, this is what I think :
initial
Compare each word set separately,once the sum of words in a Set greater than the previous recorded, Update the pair
get the pair
but I get stuck as soon as I start ....
var temp = new Pair<Integer,Integer>(0, 0);
Stream.of(wordCounts)
.reduce()
Note that you might want to think about what you want to output when the map is empty.
You can use the convenient max method on streams
var pair = wordCounts.entrySet().stream()
.map(x -> new Pair<>(x.getKey(), x.getValue().size()))
.max(Comparator.comparing(Pair::getSecond));
This gives you an optional pair. The optional will be empty when the map is empty.
If you really want to use reduce, you can. Just change the max call to:
.reduce((a, b) -> a.getSecond() >= b.getSecond() ? a : b);
I hope I understood this correctly:
map.entrySet().stream().max(Comparator.comparing(x -> x.getValue().size()))
.map(x -> new Map.entry(x.getKey(), x.getValue().size()))
.orElse(new Map.entry(0, 0));

How to search efficiently return all the values for keys which are in a arraylist

I am having an arraylist which contains a list of numbers. I want to get all the values from the HashMap which has the keys which are in the array list.
For example say the array list contains 1,2,3,4,5,6,7,8,9 list
I want to get all the values for the keys 1,2,3,4,5,6,7,8,9 map
So currently I am implementing
for (i=0;i<list.size;i++){
map_new.put(list.get(),map.get(list.get()))
}
Is there any efficient way to do this?
Your code basically assumes that map.get(list.get()) always returns a value, you can try the following code which first filters the not null values from the list object and then adds to the new Map:
Map<String, Integer> newMap = list.stream().
filter(key -> (map.get(key) != null)).//filter values not present in Map
collect(Collectors.toMap(t -> t, t -> map.get(t)));//now collect to a new Map
In case, if map.get(list.get()) returns null, your code creates a Map with null values in it for which you might end up doing null checks, which is not good, rather you can ensure that your newly created Map always contains a value for each key.
Assuming the signature of list and the map are as following
List<Integer> list;
Map<Integer, Integer> map;
You can use following
for(int a : list){
Integer b = map.get(a);
if(b != null)
// b is your desired value you can store in another collection
}
Which is similar to the procedure you have already used.
As you can access the map in O(1) so the complexity of this code will be O(listsize)
There is not much you can do for efficiency. Still couple of small things you can do considering code example you have given above:
1) Change your for loop to
for(Long num : list)
instead of iterating using index, this will reduce you get calls over list.
2) You can update the existing map , so that you even do not need to iterate.
map.keySet().retainAll(list);
for(Long key: map.keySet()) {
System.out.println(map.get(key));
}
With this existing map will contain only those data whose keys are present in list, but you should use it carefully depending upon rest of the code logic.
You can capitalize on the fact that the keyset of a map is backed by the map itself and modifications to the keyset will reflect back to the map itself. This way, you can use the retainAll() method of the Set interface to reduce the map with a single line of code. Here is an example:
final Map<Integer, String> m = new HashMap<Integer, String>();
m.put(1, "A");
m.put(2, "B");
m.put(3, "C");
m.put(4, "D");
m.put(5, "E");
final List<Integer> al = Arrays.asList(new Integer[] { 2, 4, 5 });
System.out.println(m);
m.keySet().retainAll(al);
System.out.println(m);
This will output:
{1=A, 2=B, 3=C, 4=D, 5=E}
{2=B, 4=D, 5=E}

Java How to return top 10 items based on value in a HashMap

So I am very new to Java and as such I'm fighting my way through an exercise, converting one of my Python programs to Java.
I have run into an issue where I am trying to replicate the behavior, from python the following will return only the keys sorted (by values), not the values:
popular_numbers = sorted(number_dict, key = number_dict.get, reverse = True)
In Java, I have done a bit of research and have not yet found an easy enough sample for a n00b such as myself or a comparable method. I have found examples using Guava for sorting, but the sort appears to return a HashMap sorted by key.
In addition to the above, one of the other nice things about Python, that I have not found in Java is the ability to, easily, return a subset of the sorted values. In Python I can simply do the following:
print "Top 10 Numbers: %s" % popular_numbers[:10]
In this example, number_dict is a dictionary of key,value pairs where key represents numbers 1..100 and the value is the number of times the number (key) occurs:
for n in numbers:
if not n == '':
number_dict[n] += 1
The end result would be something like:
Top 10 Numbers: ['27', '11', '5', '8', '16', '25', '1', '24', '32',
'20']
To clarify, in Java I have successfully created a HashMap, I have successfully examined numbers and increased the values of the key,value pair. I am now stuck at the sort and return the top 10 numbers (keys) based on value.
Put the map's entrySet() into a List.
Sort this list using Collections.sort and a Comparator which sorts Entrys based on their values.
Use the subList(int, int) method of List to retrieve a new list containing the top 10 elements.
Yes, it will be much more verbose than Python :)
With Java 8+, to get the first 10 elements of a list of intergers:
list.stream().sorted().limit(10).collect(Collectors.toList());
To get the first 10 elements of a map's keys, that are integers:
map.keySet().stream().sorted().limit(10).collect(Collectors.toMap(Function.identity(), map::get));
HashMaps aren't ordered in Java, and so there isn't really a good way to order them short of a brute-force search through all the keys. Try using TreeMap: http://docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html
Assuming your map is defined something like this and that you want to sort based on values:
HashMap<Integer, Integer> map= new HashMap<Integer, Integer>();
//add values
Collection<Integer> values= map.values();
ArrayList<Integer> list= new ArrayList<Integer>(values);
Collections.sort(list);
Now, print the first top 10 elements of the list.
for (int i=0; i<10; i++) {
System.out.println(list.get(i));
}
The values in the map are not actually sorted, because the HashMap is not sorted at all (it stores the values in the buckets based on the hashCode of the key). This code is just displaying 10 smallest elements in the map.
EDIT sort without loosing the key-value pairs:
//sorted tree map
TreeMap<Integer, Integer> tree= new TreeMap<>();
//iterate over a map
Iteartor<Integer> it= map.keySet().iterator();
while (it.hasNext()) {
Integer key= it.next();
tree.put(map.get(key), key);
}
Now you have the TreeMap tree that is sorted and has reversed key-value pairs from the original map, so you don't lose the information.
Try the next:
public static void main(String[] args) {
// Map for store the numbers
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
// Populate the map ...
// Sort by the more popular number
Set<Entry<Integer, Integer>> set = map.entrySet();
List<Entry<Integer, Integer>> list = new ArrayList<>(set);
Collections.sort(list, new Comparator<Entry<Integer, Integer>>() {
#Override
public int compare(Entry<Integer, Integer> a,
Entry<Integer, Integer> b) {
return b.getValue() - a.getValue();
}
});
// Output the top 10 numbers
for (int i = 0; i < 10 && i < list.size(); i++) {
System.out.println(list.get(i));
}
}
Guava Multiset is a great fit for your use case, and would nicely replace your HashMap. It is a collection which counts the number of occurences of each element.
Multisets has a method copyHighestCountFirst, which returns an immutable Multiset ordered by count.
Now some code:
Multiset<Integer> counter = HashMultiset.create();
//add Integers
ImmutableMultiset<Integer> sortedCount = Multisets.copyHighestCountFirst(counter);
//iterate through sortedCount as needed
Use a SortedMap, call values(). The docs indicate the following:
The collection's iterator returns the values in ascending order of the corresponding keys
So as long as your comparator is written correctly you can just iterate over the first n keys
Build a list from the keyset.
Sort the HashMap by values using the keys to access the value in the Collection.sort() method.
Return a sub list of the sorted key set.
if you care about the values, you can use the keys in step 3 and build value set.
HashMap<String, Integer> hashMap = new HashMap<String, Integer>();
List list = new ArrayList(hashMap.keySet());
Collections.sort(list, (w1, w2) -> hashMap.get(w2) - hashMap.get(w1)); //sorted descending order by value;
return list.subList(0, 10);
To preserve the ranking order and efficiently return top count, much smaller than the size of the map size:
map.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(count)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
(e1, e2) -> e1,
LinkedHashMap::new))

Categories