Search code examples
javastringarraylistio

Abstracting Java Strings data from ArrayList and manipulating it using I/O files


I am a beginner. I am trying to learn 1) Read data from a file and store inside ArrayList 2) Manipulate String data 3) Output results to a new file using ArrayList. I have figured out Part 1 and Part 3 and I have provided the code. I am having problems manipulating strings. I am sure there is an easier solution but I am not able to see it even after spending many hours. I know I need to use either charAt(i) or note.substring() or both. Maybe a nested loop?

// Part 1 Reading notes from a file and storing it into ArrayList named list.
    BufferedReader bufReader = new BufferedReader(new FileReader("read1.txt"));
    List<String> list = new ArrayList<>();
    String line = bufReader.readLine();
    while (line != null) {
        list.add(line);
        line = bufReader.readLine();
    }
    bufReader.close();
    list.forEach(System.out::println);//Print list to the Screen

    read1.txt  file   
    E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C#
    C# E F# G# G# BAAG#   C# E F# G# C# B A G#

    May have lyrics here which needs to be avoided
    C# E G# C# B A G# G# B A G# F# A F# B G# F# D#

PART 2 This is where I'm having a problem. list[0] has many different musical notes with and without #. How do I sperate each note with "," so result would look something like following. I want to keep the format same. I don't want to remove the whitespaces.

    E,  F#,G#, F#,G#, E,   E,F#,D#, E, C#,    E, D#, C#, B,B,  C#,C#
    C#, E, F#, G#, G#, B,A,A,G#,   C#, E, F#, G#, C#, B, A, G#,

    May have lyrics here which needs to be avoided
    C#, E, G#, C#, B, A, G#, G#, B, A, G#, F#, A, F#, B, G#, F#, D#

I want to store above result into another ArrayList named printToFile and I want to output the result using following code.

//PART 3 This code will write List to a desired new file(write2.txt)
    List<String> printToFile = new ArrayList<>();
    //here We are assuming that results have been populated into printToFile
    BufferedWriter bufWriter = new BufferedWriter(new FileWriter("write2.txt"));

    for (String wline : printToFile) {
        bufWriter.write(wline);
        bufWriter.newLine();
    }
    bufWriter.close();

Can someone pls teach me how to do Part2. I have tested Part1 and Part3. They work. For me, this is a learning tutorial so I will WELCOME different suggestions and ideas. Thanks


Solution

  • You may try replaceAll with a regular expression to replace each note [A-G] followed by optional # and removing trailing comma:

    String line = "E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C#";
    String str = line.replaceAll("([A-G]#?)", "$1,");
    if (str.endsWith(",")) {
        str = str.substring(0, str.length() - 1);
    }
    System.out.println(line);
    System.out.println(str);
    

    Output:

    E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C#
    E,  F#,G#, F#,G#, E,   E,F#,D#, E, C#,    E, D#, C#, B,B,  C#,C#
    

    note: if the input data contain other types of notes flat or sharp, the regular expression needs to be updated accordingly.

    update
    More advanced regular expression enables proper insertion of commas after each note except the last one (without removal of the trailing comma with additional code).

    String str2 = line.replaceAll("([A-G]#?+(?!$))", "$1,");
    System.out.println(str2);
    

    ([A-G]#?+(?!$)):

    • 1st Capturing Group ([A-G]#?+(?!$))
      • Match a single character present in the list below [A-G]
        A-G a single character in the range between A (index 65) and G (index 71) (case sensitive)
      • #?+ matches the character # literally (case sensitive)
        ?+ Quantifier — Matches between zero and one times, as many times as possible, without giving back (possessive)
      • Negative Lookahead (?!$)
        Assert that the Regex below does not match
        $ asserts position at the end of a line

    online demo

    It also may be needed to verify if the given input string looks like a string of notes and then apply this modification to avoid inserting commas after single-letter words which might happen in the lyrics.

    The matching regular expression is: ^(\s*[A-G]#?\s*)+$ - it checks if there is at least one note surrounded with optional whitespaces \s*.

    So, the final solution would look like:

    private static String insertCommasIfLineOfNotes(String line) {
        if (null == line || !line.matches("^(\\s*[A-G]#?\\s*)+$")) {
            return line;
        }
        return line.replaceAll("([A-G]#?+(?!$))", "$1,");
    }
    
    // test
    List<String> lines = Arrays.asList(
            "E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C#", 
            "A Perfect Day Elise",
            "A B C D E F G H I J K L M N O P"
    );
    lines.stream()
         .map(MyClass::insertCommasIfLineOfNotes)
         .forEach(System.out::println);
    
    // output
    E,  F#,G#, F#,G#, E,   E,F#,D#, E, C#,    E, D#, C#, B,B,  C#,C#
    A Perfect Day Elise
    A B C D E F G H I J K L M N O P
    

    update 2
    If it is needed to get a list of substrings in the line of notes, containing plain notes A..G optionally followed by #, or sequences of 1 or more whitespaces, the following methods can be implemented.
    refactored to add whitespace sequences to appropriate note entry to facilitate joining of the notes into single string

    private static List<String> buildListOfNotes(String line) {
        if (null == line || !line.matches("^(\\s*[A-G]#?\\s*)+$")) {
            return Arrays.asList(line); // for lyrics line
        }
        List<String> result = new ArrayList<>();
          
        int spaceStart = -1;
        int spaceEnd = 0;
        for(int i = 0; i < line.length() - 1; i++) {
            char c = line.charAt(i);
            boolean note = isNote(c);
            
            if (isNote(c)) {
                String prefix = "";
                if (spaceStart > -1) {
                    // add existing whitespace as a prefix to current note
                    int prevResult = result.size() - 1;
                    prefix = line.substring(spaceStart, spaceEnd);
                    spaceStart = -1;
                }
                if (line.charAt(i + 1) == '#') {
                    result.add(prefix + line.substring(i, i + 2));
                    i++;
                } else {
                    result.add(prefix + line.substring(i, i + 1));
                    if (!isNote(line.charAt(i + 1))) {
                        spaceStart = i + 1;
                    }
                }
            } else {
                if (spaceStart == -1) {
                    spaceStart = i;
                    spaceEnd = i + 1;
                } else {
                    spaceEnd++;
                }
            }
          }
          if (spaceStart > -1) {
              // add trailing whitespace if available to the last note
              int prevResult = result.size() - 1;
              result.set(prevResult, result.get(prevResult) + line.substring(spaceStart));
          }
          return result;
      }
      
      private static boolean isNote(char c) {
          return c >= 'A' && c <= 'G';
      }
    
    // testing method to retrieve list of substring
    List<String> lines = Arrays.asList(
            "E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C#",
            "E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C#    ",
            "   E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C",
            "   E  F#G# F#G# E   EF#D# E C#    E D# C# BB  C#C ", 
            "A Perfect Day Elise",
            "A B C D E F G H I J K L M N O P"
    );
    lines.stream()
         .map(MyClass::buildListOfNotes)
         .forEach(System.out::println);
    

    Test output ("note" entry may include whitespace sequence):

    [E,  F#, G#,  F#, G#,  E,   E, F#, D#,  E, C#,     E, D#,  C#,  B, B, C#, C#]
    [E,  F#, G#,  F#, G#,  E,   E, F#, D#,  E, C#,     E, D#,  C#,  B, B, C#, C#    ]
    [   E,  F#, G#,  F#, G#,  E,   E, F#, D#,  E, C#,     E, D#,  C#,  B, B, C#]
    [   E,  F#, G#,  F#, G#,  E,   E, F#, D#,  E, C#,     E, D#,  C#,  B, B, C#, C ]
    [A Perfect Day Elise]
    [A B C D E F G H I J K L M N O P]
    

    Then the array of notes can be easily converted into String using String.join:

    // lineToWrite will contain original whitespaces
    String lineToWrite = String.join(",", buildListOfNotes(line));
    
    // cleanLine will contain notes without extra spacing
    String cleanLine = buildListOfNotes(line).stream()
                                             .map(String::trim)
                                             .collect(Collectors.joining(", "));