java: ¿Leer varias líneas de un archivo y combinarlas en una sola, según un patrón de inicio y fin?

CorePress2024-01-24  10

Estoy escribiendo un programa para intentar limpiar datos de un archivo de texto que tengo. El archivo contiene mensajes de texto entre amigos y yo, por lo que tiene este formato:

06/07/2016, 21:44 - Friend 1: Sure. 

So there's usually a date set by the Commissioners which serves as a deadline. If you haven't applied for tax back before that date, you won't be eligible for a refund.
06/07/2016, 21:44 - Friend 1: Any further questions?
06/07/2016, 21:45 - Friend 1: Just to clarify, one must apply before, not after, said date.
06/07/2016, 21:42 - Friend 2: Still getting my head around this. Could you explain the deadline thing once more
06/07/2016, 21:46 - Friend 3: All I can say is that I've some fantastic friends that will always endeavour me!
06/07/2016, 21:47 - Friend 3: I truly appreciate this
28/12/2016, 19:04 - Friend 4: Woo party not in mine and eds 🥂🎉🎉
28/12/2016, 19:14 - Friend 1: You going?
Steve?
28/12/2016, 19:15 - Friend 5: got ppl renting in house til end of January

Así que todo esto está almacenado en un archivo .txt, y quiero limpiar los datos y convertirlos a un archivo .csv que esencialmente contenga las columnas Fecha, Hora, Nombre, Texto

Estaba intentando recorrer el archivo, dividir la línea y escribirla en un nuevo archivo CSV, por ejemplo, esta línea en el archivo:

06/07/2016, 21:44 - Friend 1: Sure. 

So there's usually a date set by the Commissioners which serves as a deadline. If you haven't applied for tax back before that date, you won't be eligible for a refund.

se combinaría en una línea como esta:

06/07/2016, 21:44 - Friend 1: Sure. So there's usually a date set by the Commissioners which serves as a deadline. If you haven't applied for tax back before that date, you won't be eligible for a refund.

Sé que cada mensaje nuevo comienza con el mismo patrón de fecha en el formato dd/mm/aaaa. Así que lo estoy usando para determinar cuándo aparece un mensaje nuevo

En este momento no estoy trabajando en escribirlo en un archivo CSV, solo reformarloeditar el texto en el formato correcto antes de realizar más procesamiento en él. Pero para el ejemplo de entrada que proporcioné anteriormente, el resultado es:

06/07/2016, 21:44 - Friend 1: Sure.   So there's usually a date set by the Commissioners which serves as a deadline. If you haven't applied for tax back before that date, you won't be eligible for a refund.
06/07/2016, 21:44 - Friend 1: Any further questions?
06/07/2016, 21:45 - Friend 1: Just to clarify, one must apply before, not after, said date.
06/07/2016, 21:42 - Friend 2: Still getting my head around this. Could you explain the deadline thing once more
06/07/2016, 21:46 - Friend 3: All I can say is that I've some fantastic friends that will always endeavour me!
06/07/2016, 21:47 - Friend 3: I truly appreciate this
28/12/2016, 19:04 - Friend 4: Woo party not in mine and eds 🥂🎉🎉
28/12/2016, 19:14 - Friend 1: You going?

Steve?
28/12/2016, 19:15 - Friend 5: got ppl renting in house til end of January

Puedes ver que funcionó en el primer caso, pero no en el segundo, y estoy teniendo problemas para encontrar una solución para solucionar este problema. Mi código está a continuación, ¿alguien puede ofrecerme algún consejo sobre cómo resolver esto?

Código

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class App {

    private static String line;
    private static final String regex = "^\d{2}\/\d{2}\/\d{4}";
    private static Pattern pattern;

    public static void main(String[] args) {

        pattern = Pattern.compile(regex);

        try {
            BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/WhatsAppChat2.txt"));
            while ((line = reader.readLine()) != null) {
                StringBuilder sb = new StringBuilder();
                boolean isNewMessage = identifyNewMessage();

                //If message is split over multiple lines, it is combined into one line
                if(isNewMessage) {
                    sb.append(line);    
                    while ((line = reader.readLine()) != null) {
                        String text = line;
                        isNewMessage = identifyNewMessage();
                        if(!isNewMessage) {
                            sb.append(" " + line);
                        }
                        else {
                            break;
                        }
                    }
                }

                System.out.println(sb.toString());
                System.out.println(line);
                //formatText(sb.toString());
                //formatText(line);
            }
            reader.close();
        } 
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Checks if file line is a new message or not
     * @return      - True if it is a message message, False if not
     */
    private static boolean identifyNewMessage() {

        Matcher m = pattern.matcher(line);
        if(m.find()) {
            return true;
        }
        else {
            return false;
        }
    }
}

Es mejor leer el archivo completo en una cadena y luego ejecutar una expresión regular en la cadena. Esta lectura línea por línea, es un ejercicio de inutilidad, utilizado por masoquistas.

usuario557597

7/03/2018 a las 19:09

@sln: Vea mi respuesta a continuación para no masoquistas (¿sádicos entonces?)

- enero

8 de marzo de 2018 a las 3:18



------------------------------------

Si la memoria y la velocidad no son un problema (dudo que lo sean con un registro de discusión), lo haría de esta manera:

Deque<String> mergedLines = new LinkedList<> ();

while ((line = reader.readLine()) != null) {
  if (!identifyNewMessage()) {
    String currentLine = mergedLines.removeLast();
    line = currentLine + " " + line;
  }
  mergedLines.add(line);
}

Ahora puedes iterar sobre la lista y hacer lo que necesites con las líneas.

Tenga en cuenta que el código generará una excepción si el fLa primera línea no es un mensaje nuevo.

4

¡Parece que funciona muy bien! Necesitaré realizar algunas pruebas más para confirmarlo. ¿Podría simplemente explicar por qué está utilizando una implementación LinkedList de la interfaz Deque? No estoy muy familiarizado con él, así que tengo curiosidad por saber por qué usaste esta implementación.

-Eoin

7/03/2018 a las 21:00

Necesitaba eliminar y volver a colocar el último elemento en O(1), lo cual hace LinkedList. Podría haber escrito LinkedList<String> fusionadasLines = nueva ListaEnlazada<> ();, pero todo lo que necesitaba era la interfaz Deque que proporciona el método removeLast, así que opté por el mínimo común denominador.

-asilias

8 de marzo de 2018 a las 0:43

Vale, gracias por la aclaración. ¿Podrías haber usado también arraylist? ¿O no te permite eliminar y volver a colocar el último elemento en O(1)?

– Eoin

9/03/2018 a las 17:09

@Eoin usar ArrayList también está bien en cuanto a rendimiento, pero no tiene un método removeLast(), por lo que necesitarías escribir mergedLines.remove(mergedLines.size() - 1), lo cual me parece menos expresivo.

-asilias

9 marzo 2018 a las 17:28



------------------------------------

Con este patrón:

^(\d{2}\/\d{2}\/\d{4}), (\d{2}:\d{2}) - (.*):(.*)$

Deberías poder seleccionar 4 grupos de captura.

1- La fecha como 99/99/9999 2- La hora es 99:99 3- El nombre del amigo (cualquiera)después de este guión con un espacio a continuación y el carácter ':'. 4- Los comentarios que vienen después del carácter ':' hasta el final de la frase.

Al leer cada grupo de captura, puede formatear la salida del archivo csv.

Ten en cuenta que el patrón asume los espacios en blanco tal como los escribiste en el ejemplo.



------------------------------------

Podrías usar

^
(?P<date>\d{2}[^-]+)\s+-\s+
(?P<friend>[^:]+):
(?P<msg>[\s\S]+?(?=^\d{2}|\Z))

Desglosado:

^                              # start of the line
(?P<date>\d{2}[^-]+)\s+-\s+    # two digits, followed by anything not a -
(?P<friend>[^:]+):             # the friendly neighborhood group
(?P<msg>[\s\S]+?(?=^\d{2}|\Z)) # match anything up to either 
                               # a new date or the very end of the string

Vea una demostración en regex101.com (y tenga en cuenta los modificadores; además, las barras invertidas deben tener caracteres de escape en Java). Como señala @assylias, es necesario leer el archivo completo como un stsuena antes.

1

No estoy seguro de entender tu respuesta, simplemente porque usar la solución de @assylias parece funcionar bien para mí. ¿Estás diciendo que podría usar el tuyo junto con el de ellos? ¿O el tuyo iba a usarse por separado?

-Eoin

7 marzo 2018 a las 20:58

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare