I am currently working on a little "Hangman" project. I want to make it possible for the user to choose from different categories, such as "Countries" or "Food" and this got me thinking about what would be the best way to handle and sort those categories.
I saved the words to a little text file, which looks something like this:
Countries: Hungary Austria Argentina Canada;
Food: Donut Bread Hamburger;
For now, I created a multidimensional ArrayList that stores all the words, each category in an individual ArrayList in that ArrayList.
ArrayList< ArrayList<String> > words = new ArrayList< ArrayList<String> >();
// ... read words from .txt file and store it in the words-ArrayList ...
I know, that in each category the first word is the title of a category, so if I wanted to get all the titles of the categories, it would look something like this:
for( ArrayList list : words ) {
System.out.println( list.get(0) );
}
Now this method I'm using works perfectally fine, it just seems a bit too complex to me and I was wondering, if there are simpler methods to do that. I want to thank in advance for any suggestions you can give me.
Better to use Map<String, List<String>> for my money. The Map can be a HashMap, and the word category would be the key while the List (an ArrayList in the concrete form) would be the related value.
Then to extract the categories, all you'd need to do would be to extract the key set and iterate through it. e.g.,
Map<String, List<String>> mapList = new HashMap<String, List<String>>();
// fill map here...
for (String key : mapList.keySet()) {
List<String> list = mapList.get(key);
System.out.printf("%s: %s%n", key, list);
}
If you want the keys to be in a certain order, then you'd need to use one of the other concrete implementations of Map such as a TreeMap.
For a simple example:
import java.io.InputStream;
import java.util.*;
public class MapList {
public static void main(String[] args) {
Map<String, List<String>> mapList = new HashMap<String, List<String>>();
String sourcePath = "MapListData.txt";
InputStream source = MapList.class.getResourceAsStream(sourcePath);
if (source == null) {
return;
}
Scanner scan = new Scanner(source);
while (scan.hasNextLine()) {
String line = scan.nextLine().trim();
if (!line.isEmpty()) {
line = line.replace(";", "");
String[] mainTokens = line.split("\\s*:\\s*");
if (mainTokens.length == 2) {
String key = mainTokens[0];
List<String> list = new ArrayList<String>();
String[] subTokens = mainTokens[1].split("\\s+");
for (String subToken : subTokens) {
list.add(subToken);
}
mapList.put(key, list);
}
}
}
if (scan != null) {
scan.close();
}
for (String key : mapList.keySet()) {
List<String> list = mapList.get(key);
System.out.printf("%s: %s%n", key, list);
}
}
}
For me returns:
Beer: [Pilsner, Weiss, Brown_Ale, IPA]
Countries: [Hungary, Austria, Argentina, Canada]
Food: [Donut, Bread, Hamburger]
Related
I have built an ArrayList of strings from two sources:
Path p1 = Paths.get("C:/Users/Green/documents/dictionary.txt");
Scanner sc = new Scanner(p1.toFile()).useDelimiter("\\s*-\\s*");
ArrayList al = new ArrayList();
while (sc.hasNext()) {
String word = (sc.next());
al.add(word);
al.add(Translate(word));
}
The array is made up of a word from a text file dictionary read one line at a time. The second is a translation of the word. The translation Translate is a Java method that now returns a string. so I am adding two strings to the array list for as many lines that there are in the dictionary.
I can print the dictionary out and the translations....but the printout is unhelpful as it prints all the words and then all the translations....not much use to quickly look up.
for(int i=0;i<al.size();i++){
al.forEach(word ->{ System.out.println(word); });
}
Is there a way that I can either manipulate the way I add the strings to the ArrayList or how I manipulate after so that I can retrieve one word and its translation at a time.
Ideally I want to be able to sort the dictionary as the file I receive is not in alphabetic order.
I am not sure why you have to use ArrayList data structure as it is required or not.
I would suggest you use the Map for this kind of dictionary data. Map data structure will manage your data as a key which is your original word and a value which is a translated word.
Here is a simple example:
Path p1 = Paths.get("C:/Users/Green/documents/dictionary.txt");
Scanner sc = new Scanner(p1.toFile()).useDelimiter("\\s*-\\s*");
Map<String, String> dic = new HashMap<String, String>();
while (sc.hasNext()) {
String word = (sc.next());
dic.put(word, Translate(word));
}
//print out from dictionary data
for(Map.Entry<String, String> entry: dic.entrySet()){
System.out.println(dic.getKey() + " - " + dic.getValue());
}
You can use object like this
class Word {
String original;
String translation;
public Word(String original, String translation) {
this.original = original;
this.translation = translation;
}
}
Put words to list:
while (sc.hasNext()) {
String word = (sc.next());
al.add(new Word(word, Translate(word)));
}
And then:
for (Word word : al) {
}
I have an array list which holds a bunch of activities inputted by the user, each of them have a title which is inputted by the user. I want the title to be parsed and inputted into a hashmap. Now the user can perform a search, where they can type certain keywords. I need to use a hashmap to check which activity in the array list they are referring too.
The problem i am having is that i know we cant have more than one values assosiated with a particular key. For example if the title for activity one is : football game, i want football to = 1 and game to = 1 (which indicates its related to the first activity) so that when the user types in one of those words it will pull up this activity.
You could potentially use a hash map on a list of strings. Make your key be a unique integer and then store any words associated with that integer in the string list. That way "game" could be associated with "soccer game" or "hockey game". But it depends how you associate your strings with your keys I guess.
Map<From, To> looks like it can only map to single things. That is luckily not true since To can also be a List<To> or a Set<To> or even a Map again.
Using complex types as values has some downsides though. You have to create those sets/lists per entry and handle null values.
roughly like
private Map<String, List<Integer>> activityMap = new HashMap<>();
private void add(String key, Integer value) {
List<Integer> list = activityMap.get(key);
if (list == null) {
// must create and add the list
list = new ArrayList<>();
activityMap.put(key, list);
}
list.add(value);
}
private List<Integer> getAll(String key) {
List<Integer> list = activityMap.get(key);
// simpler to use if there is never null as result.
if (list == null)
list = new ArrayList<>();
return list;
}
private void remove(String key, Integer value) {
List<Integer> list = activityMap.get(key);
if (list == null)
return;
// here should probably be list.remove(Object) - it looks confusing with Integer though
for (Iterator<Integer> iterator = list.iterator(); iterator.hasNext();) {
Integer listValue = iterator.next();
if (listValue.equals(value)) {
iterator.remove();
}
}
}
Guava Multimap is an implementation of such a structure in case libraries are an option.
I am not sure what is really want to put into hashmap, and I just assume it looks like this:
"user1" => "bascketbal = 1, footdball = 2"
"user2" => "football = 3"
"user3" => "pingpong = 1"
If so, you can use Map<String, Map<String, Integer>>, e.g:
Map userActiveData = new HashMap<String, Map<String, Integer>>();
//For each user, maybe in a loop
Map<String, Integer> active = new HashMap<String, Integer>();
active.put("football", 1);
active.put("game", 2);
userActiveData.put("user1", active);
Use a list as the value of the HashMap.
Map<String, List<String>> map = new HashMap<String, List<String>>();
Pseudo code:
Parse your title and find the keywords.
If the keyword is a new one Then
add it to the HashMap as a key, and add the Activity_id as the first element of the list.
Else (keyword is already in the HashMap) Then
add Activity_id as the next element of the corresponding list.
When you search a keyword you can return the list with all the Activity_ids that matches the keyword
Example:
Input: 1 - soccer game | 2 - badminton game
This is what HashMap will look like
~KEY~ | ~VALUES(List)~
soccer | 1
badminton | 2
game | 1,2
I think this is what you want, it supports the whole key and a only part of the activity to search, please check the output :
public class ActivityManager {
Map<String, Set<String>> map = new HashMap<String, Set<String>>();
public static void main(String[] args) {
ActivityManager testMap = new ActivityManager();
testMap.addActivity("football game");
testMap.addActivity("basketball game");
Set<String> football=testMap.getActivities("football");
Set<String> basketball=testMap.getActivities("basketball");
Set<String> game=testMap.getActivities("game");
Set<String> footballGame=testMap.getActivities("football game");
System.out.println("-------football------");
printActivities(football);
System.out.println("-------game------");
printActivities(game);
System.out.println("-------basketball------");
printActivities(basketball);
System.out.println("-------football game------");
printActivities(footballGame);
}
public void addActivity(String activity) {
String[] keyWords = activity.split(" ");// split key words, change to what you want if needed
Set<String> activities=null;
for (String key : keyWords) {
activities = map.get(key);
if (activities == null) {// do not have any activities mapped to this key yet
activities = new HashSet<String>();
map.put(key, activities);
}
activities.add(activity);// put new value into it.
}
if (keyWords.length>1){
Set<String> activitiesUsingWholeKey =map.get(activity);//get the activities using the whole word
if(activitiesUsingWholeKey==null){
activitiesUsingWholeKey=new HashSet<String>();
map.put(activity, activitiesUsingWholeKey);
}
activitiesUsingWholeKey.add(activity);
}
}
public Set<String> getActivities(String key){
return this.map.get(key);
}
private static void printActivities(Set<String> activities){
for(String activity:activities)
System.out.println(activity);
}
}
Output :
-------football------
football game
-------game------
basketball game
football game
-------basketball------
basketball game
-------football game------
football game
I want to read a CSV file in Java and sort it using a particular column. My CSV file looks like this:
ABC,DEF,11,GHI....
JKL,MNO,10,PQR....
STU,VWX,12,XYZ....
Considering I want to sort it using the third column, my output should look like:
JKL,MNO,10,PQR....
ABC,DEF,11,GHI....
STU,VWX,12,XYZ....
After some research on what data structure to use to hold the data of CSV, people here suggested to use Map data structure with Integer and List as key and value pairs in this question:
Map<Integer, List<String>>
where the value, List<String> = {[ABC,DEF,11,GHI....], [JKL,MNO,10,PQR....],[STU,VWX,12,XYZ....]...}
And the key will be an auto-incremented integer starting from 0.
So could anyone please suggest a way to sort this Map using an element in the 'List' in Java? Also if you think this choice of data structure is bad, please feel free to suggest an easier data structure to do this.
Thank you.
I would use an ArrayList of ArrayList of String:
ArrayList<ArrayList<String>>
Each entry is one line, which is a list of strings.
You initialize the list by:
List<ArrayList<String>> csvLines = new ArrayList<ArrayList<String>>();
To get the nth line:
List<String> line = csvLines.get(n);
To sort you write a custom Comparator. In the Constructor of that comparator you can pass the field position used to sort.
The compare method then gets the String value on stored position and converts it to a primitive ava type depending on the position. E.g you know that at position 2 in the csv there is an Integer, then convert the String to an int. This is neccessary for corretcly sorting. You may also pass an ArrayList of Class to the constructor such that it knows which field is what type.
Then use String.compareTo() or Integer.compare(), depending on column position etc.
Edit example of working code:
List<ArrayList<String>> csvLines = new ArrayList<ArrayList<String>>();
Comparator<ArrayList<String>> comp = new Comparator<ArrayList<String>>() {
public int compare(ArrayList<String> csvLine1, ArrayList<String> csvLine2) {
// TODO here convert to Integer depending on field.
// example is for numeric field 2
return Integer.valueOf(csvLine1.get(2)).compareTo(Integer.valueOf(csvLine2.get(2)));
}
};
Collections.sort(csvLines, comp);
In Java 8 you can do
SortedMap<Integer, List<String>> collect = Files.lines(Paths.get(filename))
.collect(Collectors.groupingBy(
l -> Integer.valueOf(l.split(",", 4)[2]),
TreeMap::new, Collectors.toList()));
Note: comparing numbers as Strings is a bad idea as "100" < "2" might not be what you expect.
I would use a sorted multi-map. If you don't have one handy you can do this.
SortedMap<Integer, List<String>> linesByKey = new TreeMap<>();
public void addLine(String line) {
Integer key = Integer.valueOf(line.split(",", 4));
List<String> lines = linesByKey.get(key);
if (lines == null)
linesByKey.put(key, lines = new ArrayList<>());
lines.add(line);
}
This will produce a collection of lines, sorted by the number where lines with duplicate numbers have a preserved order. e.g. if all the lines have the same number, the order is unchanged.
You can also use a list of lists:
List<List<String>> Llp = new ArrayList<List<String>>();
Then you need to call sort that extends a custom comparator that compares the third item in the list:
Collections.sort(Llp, new Comparator<LinkedList<String>>() {
#Override
public int compare(LinkedList<String> o1, LinkedList<String> o2) {
try {
return o1.get(2).compareTo(o2.get(2));
} catch (IndexOutOfBoundsException e) {
return 0;
}
}
In the below code I have sorted the CSV file based on the second column.
public static void main(String[] args) throws IOException {
String csvFile = "file_1.csv";
String line = "";
String cvsSplitBy = ",";
List<List<String>> llp = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
while ((line = br.readLine()) != null) {
llp.add(Arrays.asList(line.split(cvsSplitBy)));
}
llp.sort(new Comparator<List<String>>() {
#Override
public int compare(List<String> o1, List<String> o2) {
return o1.get(1).compareTo(o2.get(1));
}
});
System.out.println(llp);
} catch (IOException e) {
e.printStackTrace();
}
}
I am having an arraylist of MyObjects in which i want to split my list based on the title value of my object.For Example
List<MyProduct> productList = instance.getMyProductList();
this is my list containing many products.
product = productList.get(i);
String tittle = product.getTitle();
I want to split my arraylist into several list which is having similar product title.
Please let me know.
Thanks.
With Guava:
ListMultimap<String, MyProduct> result = Multimaps.index(productList, new Function<String, Product>() {
#Override
public String apply(Product input) {
return input.getTitle();
}
});
With plain old Java collections:
Map<String, List<MyProduct>> result = new HashMap<>();
for (MyProduct p : productList) {
List<MyProduct> list = result.get(p.getTitle());
if (list == null) {
list = new ArrayList<>();
result.put(p.getTitle(), list);
}
list.add(p);
}
Both assume that a "similar" title is actually an "equal" title.
I am piping in a file. I am tracking word pairs from the file. Using a treemap the keys are all sorted. However, when i add words to those keys they are not sorted.
here is the part i need help on in the process function:
private static void process(){
if(!result.containsKey(thisWord)){
result.put(thisWord, new ArrayList<String>());
}
// Add nextWord to the list of adjacent words to thisWord:
result.get(thisWord).add(nextWord); // nextword is not sorted within the key
thisword is sorted
nextWord is not..
Can i use Collections.sort(result); somehow?
im just not sure how i get to the nextWord within the result to do that.
or, is there no way to do it within my situation. I would rather not change things unless you recommend it.
This is the program
import java.util.Map.Entry;
import java.util.TreeSet;
import java.io.*;
import java.util.*;
public class program1 {
private static List<String> inputWords = new ArrayList<String>();
private static Map<String, List<String>> result = new TreeMap<String, List<String>>();
public static void main(String[] args) {
collectInput();
process();
generateOutput();
}
private static void collectInput(){
Scanner sc = new Scanner(System.in);
String word;
while (sc.hasNext()) { // is there another word?
word = sc.next(); // get next word
if (word.equals("---"))
{
break;
}
inputWords.add(word);
}
}
private static void process(){
// Iterate through every word in our input list
for(int i = 0; i < inputWords.size() - 1; i++){
// Create references to this word and next word:
String thisWord = inputWords.get(i);
String nextWord = inputWords.get(i+1);
// If this word is not in the result Map yet,
// then add it and create a new empy list for it.
if(!result.containsKey(thisWord)){
result.put(thisWord, new ArrayList<String>());
}
// Add nextWord to the list of adjacent words to thisWord:
result.get(thisWord).add(nextWord); // need to sort nextword
// Collections.sort(result);
}
}
private static void generateOutput()
{
for(Entry e : result.entrySet()){
System.out.println(e.getKey() + ":");
// Count the number of unique instances in the list:
Map<String, Integer> count = new HashMap<String, Integer>();
List<String> words = (List)e.getValue();
for(String s : words){
if(!count.containsKey(s)){
count.put(s, 1);
}
else{
count.put(s, count.get(s) + 1);
}
}
// Print the occurances of following symbols:
for(Entry f : count.entrySet()){
System.out.println(" " + f.getKey() + ", " + f.getValue() );
}
}
System.out.println();
}
}
If you want the collection of "nextword"s sorted, why not use a TreeSet rather than an ArrayList? The only reason I can see against it is if you might have duplicates. If duplicates are allowed, then yes, use Collections.sort on the ArrayList when you're done adding to them. Or look in the Apache Commons or Google collection classes - I don't know them off the top of my head, but I'm sure there is a sorted List that allows duplicates in one or both of them.
result.get(thisWord).add(nextWord);
Collections.sort(result.get(thisWord));
Y Don't you try some thing like this
Collections.sort(inputWords);