I'm having trouble with a homework, the thing is I have around 40.000 HashMap<Character,Character> objects stored in a ArrayList<HashMap<Character,Character>>object.
Each object HashMap<Character,Character> object represent a key for a substitution cipher. So one HashMap object might be: a->a; b->b; c->c; d->d; e->e; f->f; g->h; h->g
which means that the g and h are swapped during encription/decryption.
The problem is that I'm coding a brute force attack on this cipher, so I'm looping over those 40.000 keys.
When the program detects that say g->h It's a wrong substitution, I want to delete all the HashMap objects that contain this entry, not only the current element that I'm working with inside the loop and thus be able to avoid checking the keys one by one.
So far I've tried the following, but its getting stuck no exception or anything just doesnt finish, not even while debugging:
ArrayList<HashMap<Character,Character>> all_keys = new ...`
all_keys = generateAllCombinations();`
ArrayList<HashMap<Character,Character>> keys_to_delete = new ...`
for(HashMap<Character,Character> key:all_keys){`
all_keys.removeAll(keys_to_delete);
\\other stuff going on...`
if (!letters[0].getChar().equals(Constants.CHAR_E)){
Character invChar = key.get(Constants.CHAR_E);
for(HashMap<Character,Character> key2 : all_keys){
if(key2.get(Constants.CHAR_E).equals(invChar)){
keys_to_delete.add(key2);
}
}
}
}
It was lready mentioned that you have to be careful when removing elements from a collection that you are currently iterating over. This may easily cause a ConcurrentModificationException. For "complicated" cases where you can not easily use an iterator, a pattern that can often be applied is that instead of doing something like
Collection<Element> collection = ...
for (Element element : collection)
{
if (hasToBeRemoved(element))
{
// Will cause a ConcurrentModificationException!
collection.remove(element);
}
}
you collect the elements to be removed, and remove them all at once
Collection<Element> collection = ...
Collection<Element> elementsToRemove = new List<Element>();
for (Element element : collection)
{
if (hasToBeRemoved(element))
{
elementsToRemove.add(element);
}
}
collection.removeAll(elementsToRemove);
According to the code, you seem to have tried something similar, with your keys_to_delete, but that's not entirely clear.
And as mentioned in the comment, you should consider dedicated data structures for the substitutions etc. But even if you wish to stick to Lists and Maps for this purpose, you should always use the interface in the declarations. So instead of
ArrayList<HashMap<Character,Character>> allKeys = ...
you should write
List<Map<Character,Character>> allKeys = ...
However, regarding the actual question: It seems like the main issue can be resolved by introducing a method like computeKeysContaining(maps, entry) that returns all maps from a given collection that contain a certain entry.
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapListKeyRemoval
{
public static void main(String[] args)
{
List<Map<Character,Character>> allKeys = generateAllCombinations();
print("All keys", allKeys);
Set<Map<Character,Character>> keysToDelete =
new LinkedHashSet<Map<Character,Character>>();
for (Map<Character, Character> key : allKeys)
{
for (Entry<Character, Character> entry : key.entrySet())
{
if (isInvalidMapping(entry))
{
System.out.println("Invalid mapping: "+entry);
Set<Map<Character, Character>> keysWithInvalidMapping =
computeKeysContaining(allKeys, entry);
print("Keys with invalid mapping", keysWithInvalidMapping);
keysToDelete.addAll(keysWithInvalidMapping);
}
}
}
print("Keys to delete", keysToDelete);
allKeys.removeAll(keysToDelete);
print("All keys after removal", allKeys);
}
private static void print(
String message, Iterable<Map<Character,Character>> keys)
{
System.out.println(message);
for (Map<Character, Character> key : keys)
{
System.out.println(key);
}
}
private static Set<Map<Character, Character>> computeKeysContaining(
List<Map<Character,Character>> allKeys,
Entry<Character, Character> entry)
{
Set<Map<Character,Character>> keysContainingEntry =
new LinkedHashSet<Map<Character,Character>>();
for (Map<Character, Character> key : allKeys)
{
Object value = key.get(entry.getKey());
if (value != null && value.equals(entry.getValue()))
{
keysContainingEntry.add(key);
}
}
return keysContainingEntry;
}
private static boolean isInvalidMapping(Entry<Character, Character> entry)
{
return entry.getKey().equals('g') && entry.getValue().equals('h');
}
private static List<Map<Character, Character>> generateAllCombinations()
{
List<Map<Character, Character>> result =
new ArrayList<Map<Character,Character>>();
result.add(createMapping('f','i','r','s','t','-','g','h'));
result.add(createMapping('s','e','c','o','n','d','g','x'));
result.add(createMapping('t','h','i','r','d','-','g','h'));
result.add(createMapping('f','o','u','r','t','h','g','x'));
return result;
}
private static Map<Character, Character> createMapping(char ... c)
{
Map<Character, Character> map =
new LinkedHashMap<Character, Character>();
for (int i=0; i<c.length/2; i++)
{
map.put(c[i*2+0], c[i*2+1]);
}
return map;
}
}
(Apart from that, I wonder why the people who are answering questions are those who (have to) create a https://stackoverflow.com/help/mcve even for such simple questions, but don't want to speculate about the reasons here)
Use Iterator. My Solution is working but may be it requires tuning for performance.
Iterator<Map<String,String>> all_keys_iterator = all_keys.iterator();
List<Map<String,String>> all_keys_new = new ArrayList<Map<String,String>> ();
while(all_keys_iterator.hasNext()) {
Map<String,String> copyMap = new HashMap<String,String> ();
Map<String,String> all_keys_map = all_keys_iterator.next();
for(String key: all_keys_map.keySet()) {
if (!key.equalsIgnoreCase("key1")){
copyMap.put(key, all_keys_map.get(key));
}
}
if(!copyMap.isEmpty()) {
all_keys_iterator.remove();
all_keys_new.add(copyMap);
}
}
all_keys.addAll(all_keys_new);
for(Map<String,String> map: all_keys) {
for(String key: map.keySet()) {
System.out.println("key: " + key + " Value: " + map.get(key));
}
}
Related
tldr: How can I search for an entry in multiple (read-only) Java HashMaps at the same time?
The long version:
I have several dictionaries of various sizes stored as HashMap< String, String >. Once they are read in, they are never to be changed (strictly read-only).
I want to check whether and which dictionary had stored an entry with my key.
My code was originally looking for a key like this:
public DictionaryEntry getEntry(String key) {
for (int i = 0; i < _numDictionaries; i++) {
HashMap<String, String> map = getDictionary(i);
if (map.containsKey(key))
return new DictionaryEntry(map.get(key), i);
}
return null;
}
Then it got a little more complicated: my search string could contain typos, or was a variant of the stored entry. Like, if the stored key was "banana", it is possible that I'd look up "bannana" or "a banana", but still would like the entry for "banana" returned. Using the Levenshtein-Distance, I now loop through all dictionaries and each entry in them:
public DictionaryEntry getEntry(String key) {
for (int i = 0; i < _numDictionaries; i++) {
HashMap<String, String> map = getDictionary(i);
for (Map.Entry entry : map.entrySet) {
// Calculate Levenshtein distance, store closest match etc.
}
}
// return closest match or null.
}
So far everything works as it should and I'm getting the entry I want. Unfortunately I have to look up around 7000 strings, in five dictionaries of various sizes (~ 30 - 70k entries) and it takes a while. From my processing output I have the strong impression my lookup dominates overall runtime.
My first idea to improve runtime was to search all dictionaries parallely. Since none of the dictionaries is to be changed and no more than one thread is accessing a dictionary at the same time, I don't see any safety concerns.
The question is just: how do I do this? I have never used multithreading before. My search only came up with Concurrent HashMaps (but to my understanding, I don't need this) and the Runnable-class, where I'd have to put my processing into the method run(). I think I could rewrite my current class to fit into Runnable, but I was wondering if there is maybe a simpler method to do this (or how can I do it simply with Runnable, right now my limited understanding thinks I have to restructure a lot).
Since I was asked to share the Levenshtein-Logic: It's really nothing fancy, but here you go:
private int _maxLSDistance = 10;
public Map.Entry getClosestMatch(String key) {
Map.Entry _closestMatch = null;
int lsDist;
if (key == null) {
return null;
}
for (Map.Entry entry : _dictionary.entrySet()) {
// Perfect match
if (entry.getKey().equals(key)) {
return entry;
}
// Similar match
else {
int dist = StringUtils.getLevenshteinDistance((String) entry.getKey(), key);
// If "dist" is smaller than threshold and smaller than distance of already stored entry
if (dist < _maxLSDistance) {
if (_closestMatch == null || dist < _lsDistance) {
_closestMatch = entry;
_lsDistance = dist;
}
}
}
}
return _closestMatch
}
In order to use multi-threading in your case, could be something like:
The "monitor" class, which basically stores the results and coordinates the threads;
public class Results {
private int nrOfDictionaries = 4; //
private ArrayList<String> results = new ArrayList<String>();
public void prepare() {
nrOfDictionaries = 4;
results = new ArrayList<String>();
}
public synchronized void oneDictionaryFinished() {
nrOfDictionaries--;
System.out.println("one dictionary finished");
notifyAll();
}
public synchronized boolean isReady() throws InterruptedException {
while (nrOfDictionaries != 0) {
wait();
}
return true;
}
public synchronized void addResult(String result) {
results.add(result);
}
public ArrayList<String> getAllResults() {
return results;
}
}
The Thread it's self, which can be set to search for the specific dictionary:
public class ThreadDictionarySearch extends Thread {
// the actual dictionary
private String dictionary;
private Results results;
public ThreadDictionarySearch(Results results, String dictionary) {
this.dictionary = dictionary;
this.results = results;
}
#Override
public void run() {
for (int i = 0; i < 4; i++) {
// search dictionary;
results.addResult("result of " + dictionary);
System.out.println("adding result from " + dictionary);
}
results.oneDictionaryFinished();
}
}
And the main method for demonstration:
public static void main(String[] args) throws Exception {
Results results = new Results();
ThreadDictionarySearch threadA = new ThreadDictionarySearch(results, "dictionary A");
ThreadDictionarySearch threadB = new ThreadDictionarySearch(results, "dictionary B");
ThreadDictionarySearch threadC = new ThreadDictionarySearch(results, "dictionary C");
ThreadDictionarySearch threadD = new ThreadDictionarySearch(results, "dictionary D");
threadA.start();
threadB.start();
threadC.start();
threadD.start();
if (results.isReady())
// it stays here until all dictionaries are searched
// because in "Results" it's told to wait() while not finished;
for (String string : results.getAllResults()) {
System.out.println("RESULT: " + string);
}
I think the easiest would be to use a stream over the entry set:
public DictionaryEntry getEntry(String key) {
for (int i = 0; i < _numDictionaries; i++) {
HashMap<String, String> map = getDictionary(i);
map.entrySet().parallelStream().foreach( (entry) ->
{
// Calculate Levenshtein distance, store closest match etc.
}
);
}
// return closest match or null.
}
Provided you are using java 8 of course. You could also wrap the outer loop into an IntStream as well. Also you could directly use the Stream.reduce to get the entry with the smallest distance.
Maybe try thread pools:
ExecutorService es = Executors.newFixedThreadPool(_numDictionaries);
for (int i = 0; i < _numDictionaries; i++) {
//prepare a Runnable implementation that contains a logic of your search
es.submit(prepared_runnable);
}
I believe you may also try to find a quick estimate of strings that completely do not match (i.e. significant difference in length), and use it to finish your logic ASAP, moving to next candidate.
I have my strong doubts that HashMaps are a suitable solution here, especially if you want to have some fuzzing and stop words. You should utilize a proper full text search solutions like ElaticSearch or Apache Solr or at least an available engine like Apache Lucene.
That being said, you can use a poor man's version: Create an array of your maps and a SortedMap, iterate over the array, take the keys of the current HashMap and store them in the SortedMap with the index of their HashMap. To retrieve a key, you first search in the SortedMap for said key, get the respective HashMap from the array using the index position and lookup the key in only one HashMap. Should be fast enough without the need for multiple threads to dig through the HashMaps. However, you could make the code below into a runnable and you can have multiple lookups in parallel.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
public class Search {
public static void main(String[] arg) {
if (arg.length == 0) {
System.out.println("Must give a search word!");
System.exit(1);
}
String searchString = arg[0].toLowerCase();
/*
* Populating our HashMaps.
*/
HashMap<String, String> english = new HashMap<String, String>();
english.put("banana", "fruit");
english.put("tomato", "vegetable");
HashMap<String, String> german = new HashMap<String, String>();
german.put("Banane", "Frucht");
german.put("Tomate", "Gemüse");
/*
* Now we create our ArrayList of HashMaps for fast retrieval
*/
List<HashMap<String, String>> maps = new ArrayList<HashMap<String, String>>();
maps.add(english);
maps.add(german);
/*
* This is our index
*/
SortedMap<String, Integer> index = new TreeMap<String, Integer>(String.CASE_INSENSITIVE_ORDER);
/*
* Populating the index:
*/
for (int i = 0; i < maps.size(); i++) {
// We iterate through or HashMaps...
HashMap<String, String> currentMap = maps.get(i);
for (String key : currentMap.keySet()) {
/* ...and populate our index with lowercase versions of the keys,
* referencing the array from which the key originates.
*/
index.put(key.toLowerCase(), i);
}
}
// In case our index contains our search string...
if (index.containsKey(searchString)) {
/*
* ... we find out in which map of the ones stored in maps
* the word in the index originated from.
*/
Integer mapIndex = index.get(searchString);
/*
* Next, we look up said map.
*/
HashMap<String, String> origin = maps.get(mapIndex);
/*
* Last, we retrieve the value from the origin map
*/
String result = origin.get(searchString);
/*
* The above steps can be shortened to
* String result = maps.get(index.get(searchString).intValue()).get(searchString);
*/
System.out.println(result);
} else {
System.out.println("\"" + searchString + "\" is not in the index!");
}
}
}
Please note that this is a rather naive implementation only provided for illustration purposes. It doesn't address several problems (you can't have duplicate index entries, for example).
With this solution, you are basically trading startup speed for query speed.
Okay!!..
Since your concern is to get faster response.
I would suggest you to divide the work between threads.
Lets you have 5 dictionaries May be keep three dictionaries to one thread and rest two will take care by another thread.
And then witch ever thread finds the match will halt or terminate the other thread.
May be you need an extra logic to do that dividing work ... But that wont effect your performance time.
And may be you need little more changes in your code to get your close match:
for (Map.Entry entry : _dictionary.entrySet()) {
you are using EntrySet But you are not using values anyway it seems getting entry set is a bit expensive. And I would suggest you to just use keySet since you are not really interested in the values in that map
for (Map.Entry entry : _dictionary.keySet()) {
For more details on the proformance of map Please read this link Map performances
Iteration over the collection-views of a LinkedHashMap requires time proportional to the size of the map, regardless of its capacity. Iteration over a HashMap is likely to be more expensive, requiring time proportional to its capacity.
I am trying to build a many to one key value pair in java. Till now all I have manged is this
public class KeyStore {
int i=1;
Map<Integer,String> map1=new HashMap<Integer,String>();
Map<String,List<Integer>> map2=new HashMap<String,List<Integer>>();
public synchronized int put(String blobString) {
if(map1.containsValue(blobString)){
int r=blobString.hashCode()+i*blobString.hashCode();
i++;
map1.put(r, blobString);
List<Integer> j=map2.get(blobString);
List<Integer> k=j;
map2.remove(blobString);
k.add(r);
map2.put(blobString, k);
return r;
}
else{
map1.put(blobString.hashCode(),blobString);
List<Integer> x=new ArrayList<Integer>();
x.add(blobString.hashCode());
map2.put(blobString,x);
return blobString.hashCode();
}
}
public synchronized String get(int objectId) {
return map1.get(objectId);
}
What this does is if i put
ks.put("abc")
ks.put("abc")
Here ks is an instant of the class containing the above methods.
it results in
{1916062554=abc, 958031277=abc}
But what I want is
191602554,958031277=abc
and if i use get() on either of these keys it should output the value abc. Also delete() should delete the most recent key and not harm the other keys.
I thought of using
Map<ArrayList<Integer>,String> keystore=new HashMap<ArrayListInteger>,String>();
but I dont know how to implement the put method i.e how to insert a key in a map of lists. Need help with this.
EDIT 1
I am able to make the get and put methods work. Struggling with the delete method. Wrote it some what like this
Map<Integer,String> map1=new HashMap<Integer,String>();
Map<String,List<Integer>> map2=new HashMap<String,List<Integer>>();
public synchronized void delete(int objectId) {
map1.remove(objectId);
Iterator<Entry<String, List<Integer>>> it = map2.entrySet().iterator();
loop1: while (it.hasNext()) {
#SuppressWarnings("rawtypes")
Map.Entry pairs = (Map.Entry)it.next();
#SuppressWarnings("unchecked")
List<Integer> z=(List<Integer>) pairs.getValue();
if(z.contains(objectId)){
//System.out.println(z.size());
String key=(String) pairs.getKey();
System.out.println(z+" "+key);
if(z.size()==1){
map2.remove(key);
break loop1;
}
else{
z.remove(objectId);
map2.remove(key);
map2.put(key, z);
break loop1;
}
}
}
}
Basically map1 contains the mappings
123=>abc,456=>abc
and map2 contains
abc=>[123,456]
I am getting an arrayindexoutofbound exception. What I am trying in the delete method is to iterate across each blob String and then check in the list of values associated with the blobstring whetehr the required objectID is present. if it is then I remove that object id from the list and append the new mapping. Any help?
EDIT 2
The updated and working get and put methods are given above.
The Map JavaDoc says:
A map cannot contain duplicate keys; each key can map to at most one value.
But you can get around this by making the value a list of strings:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
<P>{#code java MultiValueHashMap}</P>
**/
public class MultiValueHashMap {
public static final void main(String[] ignored) {
Map<Integer,List<String>> mapOfIntStrs = new HashMap<Integer,List<String>>();
//Add elements
addStringToMap(mapOfIntStrs, 1, "one");
addStringToMap(mapOfIntStrs, 1, "two");
addStringToMap(mapOfIntStrs, 1, "three");
addStringToMap(mapOfIntStrs, 2, "four");
addStringToMap(mapOfIntStrs, 2, "five");
//Output
Set<Integer> keyNumSet = mapOfIntStrs.keySet();
Iterator<Integer> keyNumItr = keyNumSet.iterator();
while(keyNumItr.hasNext()) {
Integer keyNum = keyNumItr.next();
List<String> strList = mapOfIntStrs.get(keyNum);
System.out.println(keyNum);
for(String s : strList) {
System.out.println(" " + s);
}
}
}
private static final void addStringToMap(Map<Integer,List<String>> mapTo_addTo, int keyNum, String value) {
if(mapTo_addTo.containsKey(keyNum)) {
mapTo_addTo.get(keyNum).add(value);
} else {
List<String> strList = new ArrayList<String>();
strList.add(value);
mapTo_addTo.put(keyNum, strList);
}
}
}
Output:
[C:\java_code\]java MultiValueHashMap
1
one
two
three
2
four
five
Regarding multiple keys per value, you could certainly do this, although I'm not sure it's recommended. According to the HashMap API:
The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.
And the Hashtable API:
To successfully store and retrieve objects from a hashtable, the objects used as keys must implement the hashCode method and the equals method.
So while this would work with ArrayList<Integer> keys, for anything with customized keys, containing non-standard classes, unless you are correctly implementing hashCode() for those objects, the HashMap may not function properly.
It seems like you need a couple of data structures as fields in your class:
stringMap: Map<Integer,String>
{1916062554=abc, 958031277=abc}, etc.
because for get you want to look up items by key
keys: Map<String,List<Integer>>
{ "abc" = {1916062554, 958031277}
because for delete you want to know the keys for a given item, in order.
To add to the Map:
public void put(String item) {
List<Integer> list = getOrCreateList(item,keys);
int key = calculateKey(item,list);
list.add(key);
stringMap.put(key,item);
}
private static List<Integer> getOrCreateList(String item, Map<String,List<Integer>> map) {
List<Integer> list = map.get(item);
if(list == null) {
list = new ArrayList<Integer>();
map.put(item,list);
}
return list;
}
To get from the map is easy:
public String get(int key) {
return stringMap.get(key);
}
To delete from the map -- if I understand your requirements correctly -- you need to find the most recent key in the list corresponding to the key provided...
public void delete(int key) {
String item = stringMap.get(key);
if(item == null) {
// ... deal with
}
List<Integer> keys = keys.get(item);
// lazily using methods which don't exist in the Java API
// but which illustrate the point.
keys.removeLast();
if(keys.isEmpty()) {
stringMap.remove(key);
list.remove(item);
}
}
Here is what I am trying to do.
I am reading in a list of words with each having a level of complexity. Each line has a word followed by a comma and the level of the word. "watch, 2" for example. I wish to put all of the words of a given level into a set to ensure their uniqueness in that level. There are 5 levels of complexity, so ideally I'd like an array with 5 elements, each of which is a set.
I can then add words to each of the sets as I read them in. Later on, I wish to pull out a random word of a specified level.
I'm happy with everything except how to create an array of sets. I've read several other posts here that seem to agree that this can't be done exactly as I would hope, but I can't find a good work around. (No, I'm not willing to have 5 sets in a switch statement. Goes against the grain.)
Thanks.
You can use a map . Use level as key and value as the set which contains the words. This will help you to pull out the value for a given level, When a random word is requested from a level, get the value(set in this case) using the key which is the level and pick a random value from that. This will also scale if you increase the number of levels
public static void main(String[] args) {
Map<Integer, Set<String>> levelSet = new HashMap();
//Your code goes here to get the level and word
//
String word="";
int level=0;
addStringToLevel(levelSet,word,level);
}
private static void addStringToLevel(Map<Integer, Set<String>> levelSet,
String word, int level) {
if(levelSet.get(level) == null)
{
// this means this is the first string added for this level
// so create a container to hold the object
levelSet.put(level, new HashSet());
}
Set<String> wordContainer = levelSet.get(level);
wordContainer.add(word);
}
private static String getStringFromLevel(Map<Integer, Set<String>> levelSet,
int level) {
if(levelSet.get(level) == null)
{
return null;
}
Set<String> wordContainer = levelSet.get(level);
return "";// return a random string from wordContainer`
}
If you are willing to use Guava, try SetMultimap. It will take care of everything for you.
SetMultimap<Integer, String> map = HashMultimap.create();
map.put(5, "value");
The collection will take care of creating the inner Set instances for you unlike the array or List solutions which require either pre-creating the Sets or checking that they exist.
Consider using a List instead of an array.
Doing so might make your life easier.
List<Set<String>> wordSetLevels = new ArrayList();
// ...
for ( i = 0; i < 5; i++ ) {
wordSetLevels.add(new HashSet<String>());
}
wordSetLevels = Collections.unmodifiableList(wordSetLevels);
// ...
wordSetLevels.get(2).add("watch");
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Main {
private Set<String>[] process(List<String> words) {
#SuppressWarnings("unchecked")
Set<String>[] arrayOfSets = new Set[5];
for(int i=0; i<arrayOfSets.length; i++) {
arrayOfSets[i] = new HashSet<String>();
}
for(String word: words) {
int index = getIndex(word);
String val = getValue(word);
arrayOfSets[index].add(val);
}
return arrayOfSets;
}
private int getIndex(String str) {
//TODO Implement
return 0;
}
private String getValue(String str) {
//TODO Implement
return "";
}
}
I have bunch of log files and I want to process them in java, but I want to sort them first so I can have more human readable results.
My Log Class :
public class Log{
//only relevant fields here
private String countryCode;
private AccessType accessType;
...etc..
}
AccessType is Enum, which has values WEB, API, OTHER.
I'd like to group Log objects by both countryCode and accessType, so that end product would be log list.
I got this working for grouping Logs into log list by countryCode like this :
public List<Log> groupByCountryCode(String countryCode) {
Map<String, List<Log>> map = new HashMap<String, List<Log>>();
for (Log log : logList) {
String key = log.getCountryCode();
if (map.get(key) == null) {
map.put(key, new ArrayList<Log>());
}
map.get(key).add(log);
}
List<Log> sortedByCountryCodeLogList = map.get(countryCode);
return sortedByCountryCodeLogList;
}
from this #Kaleb Brasee example :
Group by field name in Java
Here is what I've been trying for some time now, and really stuck now ..
public List<Log> groupByCountryCode(String countryCode) {
Map<String, Map<AccessType, List<Log>>> map = new HashMap<String, Map<AccessType, List<Log>>>();
AccessType mapKey = null;
List<Log> innerList = null;
Map<AccessType, List<Log>> innerMap = null;
// inner sort
for (Log log : logList) {
String key = log.getCountryCode();
if (map.get(key) == null) {
map.put(key, new HashMap<AccessType, List<Log>>());
innerMap = new HashMap<AccessType, List<Log>>();
}
AccessType innerMapKey = log.getAccessType();
mapKey = innerMapKey;
if (innerMap.get(innerMapKey) == null) {
innerMap.put(innerMapKey, new ArrayList<Log>());
innerList = new ArrayList<Log>();
}
innerList.add(log);
innerMap.put(innerMapKey, innerList);
map.put(key, innerMap);
map.get(key).get(log.getAccessType()).add(log);
}
List<Log> sortedByCountryCodeLogList = map.get(countryCode).get(mapKey);
return sortedByCountryCodeLogList;
}
I'm not sure I know what I'm doing anymore
Your question is confusing. You want to sort the list, but you are creating many new lists, then discarding all but one of them?
Here is a method to sort the list. Note that Collections.sort() uses a stable sort. (This means that the original order of items within a group of country code and access type is preserved.)
class MyComparator implements Comparator<Log> {
public int compare(Log a, Log b) {
if (a.getCountryCode().equals(b.getCountryCode()) {
/* Country code is the same; compare by access type. */
return a.getAccessType().ordinal() - b.getAccessType().ordinal();
} else
return a.getCountryCode().compareTo(b.getCountryCode());
}
}
Collections.sort(logList, new MyComparator());
If you really want to do what your code is currently doing, at least skip the creation of unnecessary lists:
public List<Log> getCountryAndAccess(String cc, AccessType access) {
List<Log> sublist = new ArrayList<Log>();
for (Log log : logList)
if (cc.equals(log.getCountryCode()) && (log.getAccessType() == access))
sublist.add(log);
return sublist;
}
If you're able to use it, Google's Guava library has an Ordering class that might be able to help simplify things. Something like this might work:
Ordering<Log> byCountryCode = new Ordering<Log>() {
#Override
public int compare(Log left, Log right) {
return left.getCountryCode().compareTo(right.getCountryCode());
}
};
Ordering<Log> byAccessType = new Ordering<Log>() {
#Override
public int compare(Log left, Log right) {
return left.getAccessType().compareTo(right.getAccessType());
}
};
Collections.sort(logList, byCountryCode.compound(byAccessType));
You should create the new inner map first, then add it to the outer map:
if (map.get(key) == null) {
innerMap = new HashMap<AccessType, List<Log>>();
map.put(key, innerMap);
}
and similarly for the list element. This avoids creating unnecessary map elements which will then be overwritten later.
Overall, the simplest is to use the same logic as in your first method, i.e. if the element is not present in the map, insert it, then just get it from the map:
for (Log log : logList) {
String key = log.getCountryCode();
if (map.get(key) == null) {
map.put(key, new HashMap<AccessType, List<Log>>());
}
innerMap = map.get(key);
AccessType innerMapKey = log.getAccessType();
if (innerMap.get(innerMapKey) == null) {
innerMap.put(innerMapKey, new ArrayList<Log>());
}
innerMap.get(innerMapKey).add(log);
}
Firstly, it looks like you're adding each log entry twice with the final line map.get(key).get(log.getAccessType()).add(log); inside your for loop. I think you can do without that, given the code above it.
After fixing that, to return your List<Log> you can do:
List<Log> sortedByCountryCodeLogList = new ArrayList<Log>();
for (List<Log> nextLogs : map.get(countryCode).values()) {
sortedByCountryCodeLogList.addAll(nextLogs);
}
I think that code above should flatten it down into one list, still grouped by country code and access type (not in insertion order though, since you used HashMap and not LinkedHashMap), which I think is what you want.
I have the following code which adds some arrays to a hashmap but then I want access those arrays to do some work on them later. I've gotten this far but can't figure the rest out to make it work....
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
public static void main(String[] args) {
String[][] layer1 = {
{"to1", "TYPE1", "start"},
{"to2", "TYPE1", "start"}
};
String[][] layer2 = {
{"to3", "TYPE2" ,"item1"},
{"to3", "TYPE2" ,"item2"}
};
HashMap<String,Object> hashMap = new HashMap<String,Object>();
hashMap.put("layer1", layer1);
hashMap.put("layer2", layer2);
Iterator<Entry<String, Object>> iterator = hashMap.entrySet().iterator();
while(iterator.hasNext()){
hashMap.values().toArray();
for (???) {
// lets print array here for example
}
}
}
Smells like homework, but a few suggestions -
there's no reason for your Hashmap to be of the form <String,Object> - make it <String, String[][]> , as that's what you're storing.
You're iterating twice. You either
- iterate through the map, either the keys, values or entries. Each item is an iterator return value, e.g.
for (String[][] s:map.values()){
...
}
hashmap.values.toArray gives you all of the contents, which is the same thing your iterator is doing.
if you're only iterating through the contents, then you're not really using a map, as you're never making use of the fact that your values are available by key.
... just mixing too many languages to learn atm
Can I suggest that you need to stop and take the time to learn Java properly. Your code looks like you are trying to write "perlish" ... associative arrays and arrays instead of proper types. The end result is Java code that is slow and fragile compared with code that is designed and written using the Java mindset.
It might seem like you are being productive, but what you are producing is likely to be problematic going forward.
Your while loop should look like -
while(iterator.hasNext()){
String[][] arr = (String[][])iterator.next().getValue();
for (String[] strings : arr) {
for (String string : strings) {
System.out.println(string);
}
}
}
Your code is bad formed here:
while(iterator.hasNext()){
hashMap.values().toArray();
for (???) {
// lets print array here for example
}
}
You try to iterate using the Iterator "iterator" but next you call to
hashMap.values().toArray();
To get the next item of the loop you need to use iterator.next(); to fetch it. Also is good to change the "Object" by String[][] or to List<String[]> or List<List<String>>.
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Map.Entry;
public static void main(String[] args) {
String[][] layer1 = {
{"to1", "TYPE1", "start"},
{"to2", "TYPE1", "start"}
};
Map<String,String[][]> map= new HashMap<String,String[][]>();
map.put("layer1", layer1);
Iterator<Entry<String, String[][]>> iterator = map.entrySet().iterator();
while(iterator.hasNext()){
Entry<String, String[][]> entry = iterator.next();
System.out.println("Key:" + entry.getKey());
String[][] value = entry.getValue();
for(int x=0;x<value.length;x++){
for(int y=0;y<value[x].length;y++){
System.out.println("String[" + x + "][" + y + "]:" + value[x][y]);
}
}
}
}
Also you can use "for each" loop to simplify the code insted using the "while":
for (Entry<String, String[][]> entry : map.entrySet()){
System.out.println("Key:" + entry.getKey());
String[][] value = entry.getValue();
for(int x=0;x<value.length;x++){
for(int y=0;y<value[x].length;y++){
System.out.println("String[" + x + "][" + y + "]:" + value[x][y]);
}
}
}
Or if you only need the values:
for (Entry<String, String[][]> entry : map.values()){
String[][] value = entry.getValue();
for(int x=0;x<value.length;x++){
for(int y=0;y<value[x].length;y++){
System.out.println("String[" + x + "][" + y + "]:" + value[x][y]);
}
}
}