Count the occurences of a particular variable inside an ArrayList - java

In my class Feeds I have along with other members a member variable called "Date" which is of String type. I have an ArrayList of Feeds objects. I want to find the occurrences of objects which have the same date String. The occurrences can then be put in a HashMap that contains the String Date as key and # of occurrences as value.
Something along these lines:
List<Feeds> m_feeds = new ArrayList<Feeds>();
//add all feeds objects
m_feeds.add(...);
int occurrences = 0;
HashMap<String, Integer> repeatedDatabase = new HashMap<String, Integer>();
for (Feeds f : m_feeds){
occurrences = Collections.frequency(m_feeds, f.date);
// i know this method compares objects but i want to know how
// only a single variable can be done
repeatedDatabase.put(f.date, occurrences);
}

Other than giving you a simple solution, I took the liberty of fixing some things in your code please take a look:
List<Feeds> mFeeds = new ArrayList<>(); //If you are using Java 7+ you do not need to declare explicitly the Type in Diamonds. If you aren't, ignore this. Also fixed name to adapt to Java standards.
//add all feeds objects
m_feeds.add(...);
HashMap<String, Integer> repeatedDatabase = new HashMap<>(); //See Above.
for (Feeds f : m_feeds){
String s = f.date; //Suggestion: use a getter method, do not make public variables accessible outside class
Integer i = repeatedDatabase.get(s);
if (i == null){
repeatedDatabase.put(s, 1);
} else {
repeatedDatabase.put(s, i+1);
}
}

Your code will work if you properly overrode equals and in the Feeds class to return true for two Feeds instances having the same date (since if you try to put the same key in the Map twice, the new value will override the old value, and since in your case the values would also be the same, it would make no difference). However, each call to Collections.frequency would iterate over the entire List, which would give you an O(n^2) time complexity.
One way to make it more efficient :
for (Feeds f : m_feeds){
if (!repeatedDatabase.containsKey(f.date)) {
occurrences = Collections.frequency(m_feeds, f.date);
repeatedDatabase.put(f.date, occurrences);
}
}
This would still do more iterations than necessary. It would call Collections.frequency once for each unique date, which means you would iterate the List as many times as there are unique dates.
A more efficient implementation will not use Collection.frequency at all. Instead, you'll iterate just one time over the list and count the number of occurrences of each date yourself. This would give you an O(n) time complexity.
for (Feeds f : m_feeds){
if (!repeatedDatabase.containsKey(f.date)) {
repeatedDatabase.put(f.date, 1);
} else {
repeatedDatabase.put(f.date, repeatedDatabase.get(f.date)+1);
}
}

Why don't use directly the hashMap?
you can do something like
HashMap<String,Iteger> map = new HashMap<>();
for (Feeds f : m_feeds){
if (map.contains(f.getDate()) { // use the method to get the date
map.put(f.getDate(),map.get(f)+1);
else
map.put(f.getDate(),1);
}
I didn't test the code but it should work.

A small update to Angelo's answer..pushing it a bit further.. you can also use a map of string,int[] like this
Map<String,int[]> map = new HashMap<>();
int[] countArray = map.get(key);
if(countArray == null)
map.put(key, new int[]{0});
else
countArray[0]++;
Using the beauty of references :)

Related

Iterate over key-range of HashMap

Is it possible to iterate over a certain range of keys from a HashMap?
My HashMap contains key-value pairs where the key denotes a certainr row-column in Excel (e.g. "BM" or "AT") and the value is the value in this cell.
For example, my table import is:
startH = {
BQ=2019-11-04,
BU=2019-12-02,
BZ=2020-01-06,
CD=2020-02-03,
CH=2020-03-02,
CM=2020-04-06
}
endH = {
BT=2019-11-25,
BY=2019-12-30,
CC=2020-01-27,
CG=2020-02-24,
CL=2020-03-30,
CP=2020-04-27
}
I need to iterate over those two hashmap using a key-range in order to extract the data in the correct order. For example from "BQ" to "BT".
Explanation
Is it possible to iterate over hashmap but using its index?
No.
A HashMap has no indices. Depending on the underlying implementation it would also be impossible. Java HashMaps are not necessarily represented by a hashing-table. It can switch over to a red-black tree and they do not provide direct access at all. So no, not possible.
There is another fundamental flaw in this approach. HashMap does not maintain any order. Iterating it yields random orders that can change each time you start the program. But for this approach you would need insertion order. Fortunately LinkedHashMap does this. It still does not provide index-based access though.
Solutions
Generation
But, you actually do not even want index based access. You want to retrieve a certain key-range, for example from "BA" to "BM". A good approach that works with HashMap would be to generate your key-range and simply using Map#get to retrieve the data:
char row = 'B';
char columnStart = 'A';
char columnEnd = 'M';
for (char column = columnStart; columnStart <= columnEnd; column++) {
String key = Chararcter.toString(row) + column;
String data = map.get(key);
...
}
You might need to fine-tune it a bit if you need proper edge case handling, like wrapping around the alphabet (use 'A' + (column % alphabetSize)) and maybe it needs some char to int casting and vice versa for the additions, did not test it.
NavigableMap
There is actually a variant of map that offers pretty much what you want out of the box. But at higher cost of performance, compared to a simple HashMap. The interface is called NavigableMap. The class TreeMap is a good implementation. The problem is that it requires an explicit order. The good thing though is that you actually want Strings natural order, which is lexicographical.
So you can simply use it with your existing data and then use the method NavigableMap#subMap:
NavigableMap<String, String> map = new TreeMap<>(...);
String startKey = "BA";
String endKey = "BM";
Map<String, String> subMap = map.subMap(startKey, endKey);
for (Entry<String, String> entry : subMap.entrySet()) {
...
}
If you have to do those kind of requests more than once, this will definitely pay off and it is the perfect data-structure for this use-case.
Linked iteration
As explained before, it is also possible (although not as efficient) to instead have a LinkedHashMap (to maintain insertion order) and then simply iterate over the key range. This has some major drawbacks though, for example it first needs to locate the start of the range by fully iterating to there. And it relies on the fact that you inserted them correctly.
LinkedHashMap<String, String> map = ...
String startKey = "BA";
String endKey = "BM";
boolean isInRange = false;
for (Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
if (!isInRange) {
if (key.equals(startKey)) {
isInRange = true;
} else {
continue;
}
}
...
if (key.equals(endKey)) {
break;
}
}
// rangeLower and rangeUpper can be arguments
int i = 0;
for (Object mapKey : map.keySet()) {
if (i < rangeLower || i > rangeUpper) {
i++;
continue;
}
// Do something with mapKey
}
The above code iterates by getting keyset and explicitly maintaining index and incrementing it in each loop. Another option is to use LinkedHashMap, which maintains a doubly linked list for maintaining insertion order.
I don't believe you can. The algorithm you propose assumes that the keys of a HashMap are ordered and they are not. Order of keys is not guaranteed, only the associations themselves are guaranteed.
You might be able to change the structure of your data to something like this:
ranges = {
BQ=BT,
BU=BY,
....
}
Then the iteration over the HashMap keys (start cells) would easily find the matching end cells.

Read a file and spit out the count of the words using HashMap and HashSet

This is my first experience in writing code using HashMap and HashSet and I am a little confused where to start from. I want to read a file and count the number of strings used but I have to do this using HashMap and HashSet.
Any ideas on where to start from?
So I will read the file and put the strings in an array and then read it from the array and putting them into a HashSet? Is this an idiotic idea?
The constraint is that The only O(n) operation in the program should be iterating through the text file.
Thank you for the contribution in increasing my knowledge ;)
first you read entire data from file and store in string object
now use java.util.StringTokenizer class .it will split up all words in token
now read all token one by one and check it like following
use word as Key and its frequency as value in HashMap
HashMap map=new HashMap();
HashSet set=new HashSet();
StringTokenizer st = new StringTokenizer(strObj);
String s;
while (st.hasMoreTokens()) {
s=st.nextToken();
if(map.containsKey(s))
{
count=(Integer)map.get(s);
count++;
map.put(s,count);
set.add(s);
}
else
{
map.put(s,count);
set.add(s);
}
}
You're close, but can miss out the middle man (that array).
You can use a HashMap<String, Integer> to store a map of string to count of string.
What you need your program to do is:
Read the next string from the file.
Check if that string exists in you HashMap:
If it does exist, just grab the Integer that the String maps onto from the map, increment it, and put it back into the map.
If it does not exist, put the String in the map with the Integer 1.
Repeat from step 1 until the file has been read.
Grab the value collection from the HashMap and store it using Collection<Integer> counts = map.values();
Sum the collection using streams int sum = counts.stream().mapToInt(i -> i).sum();
Output the value of sum.
I'm sure you can figure out to convert that to code yourself! :)
You can find more info on HashMap here (check out the values() method), more info on Stream here, and more info on that funky bit of code from step 5 here.
In addition to Sharad's answer: Reading from file...
// would loved to use Integer, but it is immutable...
class Wrapper
{
public Wrapper(int value)
{
this.value = value;
}
public int value;
}
HashMap<String, Wrapper> counts = new HashMap<String, Wrapper>();
Scanner scanner = new Scanner(new File(fileName));
while(scanner.hasNext())
{
String token = scanner.next();
Wrapper count = counts.get(token);
if(count == null)
{
counts.put(token, new Wrapper(1));
}
else
{
++count.value;
}
}
scanner.close();
I varied sharad's algorithm a little, not having to calculate the hash value twice if the value is already in the map, and using generics saves you from having to cast.
If you need only the strings in the file as set, you get it via counts.keySet();.

Looking for a workaround for dynamic variable declaration in a for loop

I have a number of repetitions of a task I would like to put in a for loop. I have to store a time series object as an IExchangeItem, a special class in openDA (a data assimilation software).
This is one of the tasks (that works):
HashMap<String, TimeSeries> items = new LinkedHashMap<String, TimeSeries>();
...
TimeSeries tsc1Q = new TimeSeries(time,value);
id = "Q1";
tsc1Q.setId(id);
this.items.put(id,tsc1Q);
IExchangeItem c1Q = new TimeSeries(tsc1Q);
What changes across the tasks is the id of the time series object and the name of IExchangeItem. I have to create a new IExchangeItem object for each time series.
This is what I tried in the for loop:
HashMap<String, TimeSeries> items = new LinkedHashMap<String, TimeSeries>();
...
TimeSeries temp;
for (int i = 0; i<readDataDim[0]; i++) {
value[0] = values[i];
id = exchangeItemIDs[i];
temp = new TimeSeries(time,value);
temp.setId(id);
this.items.put(id,temp);
IExchangeItem <??> = new TimeSeries(temp); //* How can I handle this line?
}
I know I cannot use dynamic variable names in java and that arrays, lists, or maps are commonly used to work around this issue (this is why I used <??> in the code snippet above. However, I'm a relative beginner with java and I have no clue how I can work around this specific problem since I have to have a new invocation of IExchangeItem for each time series.
From here I take it that my IExchangeItem created in the for loop will not be accessible outside the for loop so how can I initialise n replicates of IExchangeItem outside the for loop?
Edit:
Does a HashMap create n instances of IExchangeItem if I try something like this?
HashMap<String,IExchangeItem> list = new LinkedHashMap<String,IExchangeItem>();
Just one suggestion, try to write a separate method when you can pass the size of the array or a fixed number (based on array), then you created a hashMap and add that many number of instances with its keys, and values, cannot post this as a comment and hence posting it as an answer.
Try to create a new method using the value of readDataDim[0] value,
public Map<String, IExchangeItem> createAndInitialzeMap(int maxValue) {
Map<String, IExchangeItem> map = new HashMap<>();
String temp = "tempName";
for(int i =0; i < maxValue ; i ++ ) {
map.put(temp+i, new IExchangeItem());
}
return map;
}
return this way you can initialize your map along with its variable name and you can use it in your app anywhere. However I would consider refactoring if such code exists and time permits.
One more thing you should read about hashMap. :) :)

ArrayList in java, only doing an action once

For some reason, I'm drawing a blank on this one. I have an ArrayList that contains CDs (some of them identical in name) and I want to print a String that tells how many I have of each CD. For example, a string that says "You have: (1) AlbumOne, (2) AlbumTwo, (1) AlbumThree" etc. The CDs are not sorted. How can I do this?
One way to do it is to loop thru the array, and use a map that uses the objects as keys and the values as counts. So
Map<YourObject, Integer> counter ...
As you loop thru the array, do a get on the counter for the current object in the array. If you get null, initialize the value at that bucket in the map to be 1. If you have a value, increment it. Then you can loop over the map to get your readout.
Note that if you use a Hashmap, your objects have to implement the hashcode and equals method properly. You don't have to use your object, if it has keys or some other distinguishing field, the keys in your map can be that...
//aggregate details
Map<String, Integer> albumCounts = new HashMap<String, Integer>();
for (String album : albums) {
Integer count = albumCounts.get(album);
if (count == null) {
count = 0;
}
albumCounts.put(album, count + 1);
}
//print stats
System.out.println("You have:");
for (String album : albums) {
System.out.println("(" + albumCounts.get(album) + ") " + album);
}
Don't get confused about the Map. Use of Map is appropriate to solve such a problem (as you have posted) So please visit this link (tutorial) and read/learn about Map.

Java: Getting the 500 most common words in a text via HashMap

I'm storing my wordcount into the value field of a HashMap, how can I then get the 500 top words in the text?
public ArrayList<String> topWords (int numberOfWordsToFind, ArrayList<String> theText) {
//ArrayList<String> frequentWords = new ArrayList<String>();
ArrayList<String> topWordsArray= new ArrayList<String>();
HashMap<String,Integer> frequentWords = new HashMap<String,Integer>();
int wordCounter=0;
for (int i=0; i<theText.size();i++){
if(frequentWords.containsKey(theText.get(i))){
//find value and increment
wordCounter=frequentWords.get(theText.get(i));
wordCounter++;
frequentWords.put(theText.get(i),wordCounter);
}
else {
//new word
frequentWords.put(theText.get(i),1);
}
}
for (int i=0; i<theText.size();i++){
if (frequentWords.containsKey(theText.get(i))){
// what to write here?
frequentWords.get(theText.get(i));
}
}
return topWordsArray;
}
One other approach you may wish to look at is to think of this another way: is a Map really the right conceptual object here? It may be good to think of this as being a good use of a much-neglected-in-Java data structure, the bag. A bag is like a set, but allows an item to be in the set multiple times. This simplifies the 'adding a found word' very much.
Google's guava-libraries provides a Bag structure, though there it's called a Multiset. Using a Multiset, you could just call .add() once for each word, even if it's already in there. Even easier, though, you could throw your loop away:
Multiset<String> words = HashMultiset.create(theText);
Now you have a Multiset, what do you do? Well, you can call entrySet(), which gives you a collection of Multimap.Entry objects. You can then stick them in a List (they come in a Set), and sort them using a Comparator. Full code might look like (using a few other fancy Guava features to show them off):
Multiset<String> words = HashMultiset.create(theWords);
List<Multiset.Entry<String>> wordCounts = Lists.newArrayList(words.entrySet());
Collections.sort(wordCounts, new Comparator<Multiset.Entry<String>>() {
public int compare(Multiset.Entry<String> left, Multiset.Entry<String> right) {
// Note reversal of 'right' and 'left' to get descending order
return right.getCount().compareTo(left.getCount());
}
});
// wordCounts now contains all the words, sorted by count descending
// Take the first 50 entries (alternative: use a loop; this is simple because
// it copes easily with < 50 elements)
Iterable<Multiset.Entry<String>> first50 = Iterables.limit(wordCounts, 50);
// Guava-ey alternative: use a Function and Iterables.transform, but in this case
// the 'manual' way is probably simpler:
for (Multiset.Entry<String> entry : first50) {
wordArray.add(entry.getElement());
}
and you're done!
Here you can find a guide how to sort a HashMap by the values. After the sorting you can just iterate over the first 500 entries.
Take a look at the TreeBidiMap provided by the Apache Commons Collections package. http://commons.apache.org/collections/api-release/org/apache/commons/collections/bidimap/TreeBidiMap.html
It allows you to sort the map according to both the key or the value set.
Hope it helps.
Zhongxian

Categories