简体   繁体   中英

read txt file and store data in a hashtable in java

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.

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.

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 ).

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();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM