0

I am reading a txt file and store the data in a hashtable, but I couldn't get the correct output. the txt file like this (part) attached image this is part of my data

And I want to store the column 1 and column 2 as the key(String type) in hashtable, and column 3 and column 4 as the value (ArrayList type) in hashtable. My code below:

private Hashtable<String, ArrayList<String[]>> readData() throws Exception {
    BufferedReader br = new BufferedReader (new FileReader("MyGridWorld.txt"));
    br.readLine();

    ArrayList<String[]> value = new ArrayList<String[]>();
    String[] probDes = new String[2];
    String key = "";

    //read file line by line
    String line = null;
    while ((line = br.readLine()) != null && !line.equals(";;")) {
        //System.out.println("line ="+line);
        String source;
        String action;

        //split by tab
        String [] splited = line.split("\\t"); 
        source = splited[0];
        action = splited[1];
        key = source+","+action;

        probDes[0] = splited[2];
        probDes[1] = splited[3];

        value.add(probDes);
        hashTableForWorld.put(key, value);
        System.out.println("hash table is like this:" +hashTableForWorld);

    }

    br.close();
    return  hashTableForWorld;


}

The output looks like this: it's a very long long line

I think maybe the hashtable is broken, but I don't know why. Thank you for reading my problem.

Xirema
  • 19,889
  • 4
  • 32
  • 68
Qing Li
  • 21
  • 1
  • 5
  • To clarify, your hash table is a mapping from a pair of strings representing the key to a pair of strings representing the object? Am I mistaken in determining this? – Xirema Jan 15 '18 at 21:28
  • try something like https://stackoverflow.com/questions/20114574/how-i-display-the-content-of-my-hash-table-in-java to print the contents of your hashtable. Also you should check prior to adding your arrays if the key already exists, then you'll want to grab the arraylist the key maps to and add your array to the list. currently you'll overwrite any existing array list of arrays for a key that already exists – RAZ_Muh_Taz Jan 15 '18 at 21:59

3 Answers3

2

The first thing we need to establish is that you have a really obvious XY-Problem, in that "what you need to do" and "how you're trying to solve it" are completely at odds with each other.

So let's go back to the original problem and try to work out what we need first.

As best as I can determine, source and action are connected, in that they represent queryable "keys" to your data structure, and probability, destination, and reward are queryable "outcomes" in your data structure. So we'll start by creating objects to represent those two concepts:

public class SourceAction implements Comparable<SourceAction>{
    public final String source;
    public final String action;

    public SourceAction() {
        this("", "");
    }

    public SourceAction(String source, String action) {
        this.source = source;
        this.action = action;
    }

    public int compareTo(SourceAction sa) {
        int comp = source.compareTo(sa.source);
        if(comp != 0) return comp;
        return action.compareto(sa.action);
    }

    public boolean equals(SourceAction sa) {
        return source.equals(sa.source) && action.equals(sa.action);
    }

    public String toString() {
        return source + ',' + action;
    }
}

public class Outcome {
    public String probability; //You can use double if you've written code to parse the probability
    public String destination;
    public String reward; //you can use double if you're written code to parse the reward

    public Outcome() {
        this("", "", "");
    }

    public Outcome(String probability, String destination, String reward) {
        this.probability = probability;
        this.destination = destination;
        this.reward = reward;
    }

    public boolean equals(Outcome o) {
        return probability.equals(o.probability) && destination.equals(o.destination) && reward.equals(o.reward);

    public String toString() {
        return probability + ',' + destination + ',' + reward;
    }
}

So then, given these objects, what sort of Data Structure can properly encapsulate the relationship between these objects, given that a SourceAction seems to have a One-To-Many relationship to Outcome objects? My suggestion is that a Map<SourceAction, List<Outcome>> represents this relationship.

private Map<SourceAction, List<Outcome>> readData() throws Exception {

It is possible to use a Hash Table (in this case, HashMap) to contain these objects, but I'm trying to keep the code as simple as possible, so we're going to stick to the more generic interface.

Then, we can reuse the logic you used in your original code to insert values into this data structure, with a few tweaks.

private Map<SourceAction, List<Outcome>> readData() {
    //We're using a try-with-resources block to eliminate the later call to close the reader
    try (BufferedReader br = new BufferedReader (new FileReader("MyGridWorld.txt"))) {
        br.readLine();//Skip the first line because it's just a header

        //I'm using a TreeMap because that makes the implementation simpler. If you absolutely 
        //need to use a HashMap, then make sure you implement a hash() function for SourceAction
        Map<SourceAction, List<Outcome>> dataStructure = new TreeMap<>();

        //read file line by line
        String line = null;
        while ((line = br.readLine()) != null && !line.equals(";;")) {
            //split by tab
            String [] splited = line.split("\\t"); 
            SourceAction sourceAction = new SourceAction(splited[0], splited[1]);

            Outcome outcome = new Outcome(splited[2], splited[3], splited[4]);

            if(dataStructure.contains(sourceAction)) {
                //Entry already found; we're just going to add this outcome to the already
                //existing list.
                dataStructure.get(sourceAction).add(outcome);
            } else {
                List<Outcome> outcomes = new ArrayList<>();
                outcomes.add(outcome);
                dataStructure.put(sourceAction, outcomes);
            }
        }
    } catch (IOException e) {//Do whatever, or rethrow the exception}
    return dataStructure;
}

Then, if you want to query for all the outcomes associated with a given source + action, you need only construct a SourceAction object and query the Map for it.

Map<SourceAction, List<Outcome>> actionMap = readData();
List<Outcome> outcomes = actionMap.get(new SourceAction("(1,1)", "Up"));
assert(outcomes != null);
assert(outcomes.size() == 3);
assert(outcomes.get(0).equals(new Outcome("0.8", "(1,2)", "-0.04")));
assert(outcomes.get(1).equals(new Outcome("0.1", "(2,1)", "-0.04")));
assert(outcomes.get(2).equals(new Outcome("0.1", "(1,1)", "-0.04")));

This should yield the functionality you need for your problem.

Xirema
  • 19,889
  • 4
  • 32
  • 68
  • Thank you Xirema for such careful solution. I found what your unique is you create class for the key and value, and I want to ask why should we do that. Why can't I just define them in the while loop? Thanks again! – Qing Li Jan 15 '18 at 23:01
  • Because at some point you will want to examine the components of the key and the components of the value (assuming your program actually does something useful with the data it read in), At that point, if you just store strings like your code does, you'll have to re-parse the data. @Xirema's approach is the correct object-oriented way to look at the problem. – Jim Garrison Jan 16 '18 at 01:08
  • Would +1 for the thorough explanation, but I do not like the argument-less constructors (they have no use here and it is certainly not the time to prepare the classes for reflection-based serialization frameworks), and I would challenge the simplicity of ```tree*``` over ```hash*```: ```hashCode()``` would be a one-liner re-using ```toString``` (or even without re-using it), while the artificial ordering of keys is harder to follow, it has no actual meaning, but it results in introducing various new interfaces and classes. The repeated stressing of _need_ is an exaggeration. – tevemadar Jan 16 '18 at 11:28
1

Hashtable and ArrayList (and other collections) do not make a copy of key and value, and thus all values you are storing are the same probDes array you are allocating at the beginning (note that it is normal that the String[] appears in a cryptic form, you would have to make it pretty yourself, but you can still see that it is the very same cryptic thing all the time).
What is sure is that you should allocate a new probDes for each element inside the loop.
Based on your data you could work with an array as value in my opinion, there is no real use for the ArrayList And the same applies to value, it has to be allocated separately upon encountering a new key:

private Hashtable<String, ArrayList<String[]>> readData() throws Exception {
    try(BufferedReader br=new BufferedReader(new FileReader("MyGridWorld.txt"))) {
        br.readLine();

        Hashtable<String, ArrayList<String[]>> hashTableForWorld=new Hashtable<>();

        //read file line by line
        String line = null;
        while ((line = br.readLine()) != null && !line.equals(";;")) {
            //System.out.println("line ="+line);
            String source;
            String action;

            //split by tab
            String[] split = line.split("\\t"); 
            source = split[0];
            action = split[1];
            String key = source+","+action;

            String[] probDesRew = new String[3];
            probDesRew[0] = split[2];
            probDesRew[1] = split[3];
            probDesRew[2] = split[4];

            ArrayList<String[]> value = hashTableForWorld.get(key);
            if(value == null){
                value = new ArrayList<>();
                hashTableForWorld.put(key, value);
            }
            value.add(probDesRew);
        }
        return hashTableForWorld;
    }
}

Besides relocating the variables to their place of actual usage, the return value is also created locally, and the reader is wrapped into a try-with-resource construct which ensures that it is getting closed even if an exception occurs (see official tutorial here).

Zoe
  • 27,060
  • 21
  • 118
  • 148
tevemadar
  • 12,389
  • 3
  • 21
  • 49
  • Thank you for your answering! But i have some understanding problem for your first sentence. So why I can't use arraylist to store the value? – Qing Li Jan 15 '18 at 22:14
  • @QingLi That was because I did not notice the repeats, sorry about that. So yes, you need the ```ArrayList```, and it has to be created separately for the different keys. See current state of the post. – tevemadar Jan 16 '18 at 10:42
1

You should change your logic for adding to your hashtable to check for the key you create. If the key exists, then grab your array list of arrays that it maps to and add your array to it. Currently you will overwrite the data.

Try this

if(hashTableForWorld.containsKey(key))
{
    value = hashTableForWorld.get(key);
    value.add(probDes);
    hashTableForWorld.put(key, value);
}
else
{
    value = new ArrayList<String[]>();
    value.add(probDes);
    hashTableForWorld.put(key, value);
}

Then to print the contents try something like this

for (Map.Entry<String, ArrayList<String[]>> entry : hashTableForWorld.entrySet()) {
    String key = entry.getKey();
    ArrayList<String[]> value = entry.getValue();

    System.out.println ("Key: " + key + " Value: ");
    for(int i = 0; i < value.size(); i++)
    {
        System.out.print("Array " + i + ": ");
        for(String val : value.get(i))
            System.out.print(val + " :: ")
        System.out.println();
    }
}
RAZ_Muh_Taz
  • 4,059
  • 1
  • 13
  • 26
  • Thank you for your answering! I tried your code, that works wired thing. The value is not match the key. Keys are read correctly, but not the value; and the first line on the console started with value, no Keys. – Qing Li Jan 15 '18 at 22:48
  • The way to print out the content has some issue for repeating. If read the first line, it gets one time; but when read second line, there're two same output; and when third time, there are three same contents. Like below: Keys: (1,1),Up Value: Array 0: 0.8 , (1,2) , Keys: (1,1),Up Value: Array 0: 0.1 , (2,1) , Array 1: 0.1 , (2,1) , Keys: (1,1),Up Value: Array 0: 0.1 , (1,1) , Array 1: 0.1 , (1,1) , Array 2: 0.1 , (1,1) , – Qing Li Jan 16 '18 at 21:57
  • Hi Raz. If these two lines are controversy? value = hashTableForWorld.get(key); value.add(probDes); – Qing Li Jan 17 '18 at 16:51
  • shouldn't be, value = ArrayList, therefore when grabbing your value from the hashtable you get back a ArrayList and then value.add(probDes) is simply adding the String array porbDes to the arraylist. I did forget to place it back in the hashtable so check my fixes – RAZ_Muh_Taz Jan 17 '18 at 16:57