After the file’s data is being read one line at a time, it’s simply a matter of getting the necessary data and manipulating it to fit my needs.

Task 1: Get the File’s Total Line Count

Getting the line count for the entire file was easy. All that involved was a new int lines = 0 declared outside the while loop, which I incremented each time the loop ran again. Request #1: done.

Task 2: Create a List of all the Names and Find the 432nd and 43243rd Names

The second request, which was to collect all the names and print the 432nd and 43243rd names from the array, required me to create an ArrayList<String> names = new ArrayList<>(); and an ArrayList<Integers> indexes = new ArrayList<>(); which I promptly added the indexes of 432 and 43243 with indexes.add(433) and indexes.add(43244) , respectively.

I had to add 1 to each index to get the correct name position in the array because I incremented my line count (starting at 0) as soon as Scanner.hasNextLine() returned true. After Scanner.nextLine() returned the previous line’s contents I could pull out the names I needed, which meant its true index (starting from index 0) was actually the index of the line count minus one. (Trust me, I triple checked this to make sure I was doing my math correctly).

I used an ArrayList for the names because it maintains the elements insertion order which means while displaying ArrayList elements the result set will always have the same order in which the elements got inserted into the List. Since I’m iterating through the file line by line, the elements will always be inserted into the list in the same order.

Here’s the full logic I used to get all the names and then print out the names if the indexes I had in my indexes ArrayList matched the lines count index.

int lines = 0; ArrayList<String> names = new ArrayList<>();



// get the 432nd and 43243 names

ArrayList<Integer> indexes = new ArrayList<>();



indexes.add(433);

indexes.add(43244);



System.out.println("Reading file using File Input Stream");



while (sc.hasNextLine()) {

String line = sc.nextLine();

lines++; // get all the names

String array1[] = line.split("\\s*\\|\\s*");

String name = array1[7]; names.add(name);

if (indexes.contains(lines)) {

System.out.println("Name: " + names.get(lines - 1) + " at

index: " + (lines - 1));

}

...

}

Request #2: done.

Task 3: Count How Many Donations Occurred in Each Month

As I approached the donation counting request, I wanted to do more than count donations by month, I wanted to count them by both month and year, as I had donations from both 2017 and 2018.

The very first thing I did was set up an initial ArrayList to hold all my dates: ArrayList<String> dates = new ArrayList<>();

Then, I took the 5th element in each line, the raw date, and used the substring() method to pull out just the month and year for each donation.

I reformatted each date into easier-to-read dates and added them to the new dates ArrayList.

String rawDate = array1[4];

String month = rawDate.substring(4, 6);

String year = rawDate.substring(0, 4);

String formattedDate = month + "-" + year;

dates.add(formattedDate);

After I’d collected all the dates, I created a HashMap to hold my dates: HashMap<String, Integer> dateMap = new HashMap<>(); , and then looped through the dates list to either add the dates as keys to the HashMap if they didn’t already exist or increment their value count, if they did exist.

Once the HashMap was made, I ran that new map through another for loop to get each object’s key and value to print out to the console. Voilà.

The date results were not sorted in any particular order, but they could be by transforming the HashMap back in to an ArrayList or LinkedList, if need be. I chose not to though, because it was not a requirement.

HashMap<String, Integer> dateMap = new HashMap<>();

for (String date : dates) {

Integer count = dateMap.get(date);

if (count == null) {

dateMap.put(date, 1);

} else {

dateMap.put(date, count + 1);

}

} for (Map.Entry<String, Integer> entry : dateMap.entrySet()) {

String key = entry.getKey();

Integer value = entry.getValue();

System.out.println("Donations per month and year: " +

entry.getKey() + " and donation count: " + entry.getValue());



}

Request #3: done.

Task 4: Identify the Most Common First Name & How Often It Occurs

The fourth request, to get all the first names only and find the count of the most commonly occurring one, was trickiest.

It required me to first, check if the names array contained a comma (there were some business names that had no commas), then split() the name on the comma and trim() any extraneous white space from it.

Once that was cleaned up, I had to check if the first half of the name had any white spaces (meaning the person had a first name and middle name or possibly a moniker like “Ms.”) and if it did, split() it again, and trim() up the first element of the newly made array (which I presumed would almost always be the first name).

If the first half of the name didn’t have a space, it was added to the firstNames ArrayList as is. That’s how I collected all the first names from the file. See the code snippet below.

// count the occurrences of first name

ArrayList<String> firstNames = new ArrayList<>();



System.out.println("Reading file using File Input Stream");



while (sc.hasNextLine()) {

String line = sc.nextLine();



// get all the names

String array1[] = line.split("\\s*\\|\\s*");

String name = array1[7];

names.add(name);



if (name.contains(", ")) {

String array2[] = (name.split(", "));

String firstHalfOfName = array2[1].trim();



if (firstHalfOfName != undefined ||

!firstHalfOfName.isEmpty()) {

if (firstHalfOfName.contains(" ")) {

String array3[] = firstHalfOfName.split(" ");

String firstName = array3[0].trim();

firstNames.add(firstName);

} else {

firstNames.add(firstHalfOfName);

}

}

}

Once I’ve collected all the first names I can, and the while loop reading the file has ended, it’s time to sort the names and find the most common one.

For this, I created another new HashMap: HashMap<String, Integer> map = new HashMap<>(); , then looped through all the names and if the name didn’t exist in the map already, it was created as the map’s key and the value was set as 1. If the name already existed in the HashMap, the value was incremented by 1.

HashMap<String, Integer> map = new HashMap<>();

for (String name : firstNames) {

Integer count = map.get(name);

if (count == null) {

map.put(name, 1);

} else {

map.put(name, count + 1);

}

}

But wait — there’s more! Once we have the HashMap, which is unordered by nature, it needs to be sorted from largest to smallest value to get the most commonly occurring first name, so I transform each entry in the HashMap into a LinkedList, which can be ordered and iterated through.

LinkedList<Entry<String, Integer>> list = new LinkedList<>(map.entrySet());

And finally, the list is sorted using the Collections.sort() method, and invoking the Comparator interface to sort the name objects according to their value counts in descending order (highest value is first). Check this out.

Collections.sort(list, new Comparator<Map.Entry<String, Integer>>()

{

public int compare(Map.Entry<String, Integer> o1,

Map.Entry<String, Integer> o2) {

return (o2.getValue()).compareTo(o1.getValue());

}

});

Once all of that has been done, the first key value pair of the LinkedList can finally be pulled out and displayed to the user. Here’s the whole shebang that’s put together once the all the first names have been read out of the file.

HashMap<String, Integer> map = new HashMap<>();

for (String name : firstNames) {

Integer count = map.get(name);

if (count == null) {

map.put(name, 1);

} else {

map.put(name, count + 1);

}

}



LinkedList<Entry<String, Integer>> list = new LinkedList<>(map.entrySet());



Collections.sort(list, new Comparator<Map.Entry<String, Integer>>()

{

public int compare(Map.Entry<String, Integer> o1,

Map.Entry<String, Integer> o2) {

return (o2.getValue()).compareTo(o1.getValue());

}

}); System.out.println("The most common first name is: " + list.get(0).getKey() + " and it occurs: " + list.get(0).getValue() + " times.");

Request #4 (and arguably the most complicated of all the tasks): done.

Great, now that I’ve given you the soliloquy of my brain’s logic in Java, I can give you much quicker overviews of the other two methods I tried for reading the text data from the files. (Because the logic portion of the code is exactly the same.)

My second solution involved two more of Java’s core methods: BufferedReader() and FileReader() .

BufferedReader reads text from a character-input stream, buffering characters so as to provide for the efficient reading of characters, arrays, and lines, and it is wrapped around the FileReader method, which is the actual method reading the specified text file. The BufferedReader makes the FileReader more efficient in its operation, that’s all.

BufferedReader’s method readLine() is what actually reads back each line of the text as it is read from the stream, allowing us to pull out the data needed.

The setup is similar to FileInputStream and Scanner; you can see how to implement BufferedReader and FileReader below.

File f = new File("src/main/resources/config/test.txt");



try (BufferedReader b = new BufferedReader(new FileReader(f))) {

String readLine = "";

do some things ... while ((readLine = b.readLine()) != null) {

do some more things...

} do some final things

}