Conceptos Esenciales en C: Gestión de Arrays, Punteros y Cadenas

Este artículo explora una variedad de temas fundamentales en la programación en C, centrándose en el manejo eficiente de arrays y punteros, la manipulación de cadenas de caracteres y la implementación de algoritmos básicos. A través de ejemplos de código comentados y explicaciones detalladas, se cubren desde la búsqueda de valores extremos en un array hasta la codificación de texto y la validación de formatos.

  1. Búsqueda de Valores Mínimo y Máximo en un Array

La siguiente sección demuestra cómo encontrar los valores mínimo y máximo dentro de un array de enteros. Se utiliza una función que recibe el array, su tamaño y dos punteros para almacenar los resultados (mínimo y máximo).

#include <stdio.h>

#define TAMANIO_ARRAY 5

// Prototipos de funciones
void obtenerEntrada(int arr[], int n);
void mostrarArray(const int arr[], int n);
void buscarMinMax(const int arr[], int n, int *resultadoMin, int *resultadoMax);

int main() {
    int numeros[TAMANIO_ARRAY];
    int valorMinimo, valorMaximo;

    printf("Ingrese %d números enteros:\n", TAMANIO_ARRAY);
    obtenerEntrada(numeros, TAMANIO_ARRAY);

    printf("Los datos ingresados son: ");
    mostrarArray(numeros, TAMANIO_ARRAY);

    printf("Procesando los datos para encontrar min/max...\n");
    buscarMinMax(numeros, TAMANIO_ARRAY, &valorMinimo, &valorMaximo);

    printf("Resultados:\n");
    printf("Mínimo = %d, Máximo = %d\n", valorMinimo, valorMaximo);

    return 0;
}

// Función para leer 'n' enteros en el array 'arr'
void obtenerEntrada(int arr[], int n) {
    for (int i = 0; i < n; ++i) {
        scanf("%d", &arr[i]);
    }
}

// Función para imprimir los elementos del array 'arr'
void mostrarArray(const int arr[], int n) {
    for (int i = 0; i < n; ++i) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// Función para encontrar los valores mínimo y máximo en el array
void buscarMinMax(const int arr[], int n, int *resultadoMin, int *resultadoMax) {
    if (n <= 0) { // Manejo de caso de array vacío
        *resultadoMin = 0; // O algún valor de error/default
        *resultadoMax = 0;
        return;
    }

    *resultadoMin = arr[0];
    *resultadoMax = arr[0];

    for (int i = 1; i < n; ++i) {
        if (arr[i] < *resultadoMin) {
            *resultadoMin = arr[i];
        } else if (arr[i] > *resultadoMax) {
            *resultadoMax = arr[i];
        }
    }
}

La función buscarMinMax utiliza punteros para modificar directamente las variables valorMinimo y valorMaximo declaradas en main, permitiendo devolver múltiples resultados desde una función.

  1. Retorno de Punteros desde Funciones para Referenciar Elementos de Arrays

En este ejemplo, una función devuelve un puntero al elemento de mayor valor dentro de un array. Es crucial comprender que el puntero devuelto apunta a una ubicación de memoria válida dentro del array original, que no debe ser liberada prematuramente.

#include <stdio.h>

#define CANT_ELEMENTOS 5

// Prototipos
void obtenerNumeros(int arr[], int tam);
void imprimirNumeros(const int arr[], int tam);
int *encontrarMayor(int arr[], int tam);

int main() {
    int elementos[CANT_ELEMENTOS];
    int *punteroAlMayor;

    printf("Por favor, ingrese %d números:\n", CANT_ELEMENTOS);
    obtenerNumeros(elementos, CANT_ELEMENTOS);

    printf("Los números ingresados son: ");
    imprimirNumeros(elementos, CANT_ELEMENTOS);

    printf("Buscando el número mayor...\n");
    punteroAlMayor = encontrarMayor(elementos, CANT_ELEMENTOS);

    printf("Resultado: El valor máximo es = %d\n", *punteroAlMayor);

    return 0;
}

// Lee números en el array
void obtenerNumeros(int arr[], int tam) {
    for (int i = 0; i < tam; ++i) {
        scanf("%d", &arr[i]);
    }
}

// Imprime los números del array
void imprimirNumeros(const int arr[], int tam) {
    for (int i = 0; i < tam; ++i) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// Devuelve un puntero al elemento más grande en el array
int *encontrarMayor(int arr[], int tam) {
    int indiceMayor = 0; // Suponemos que el primer elemento es el mayor

    for (int i = 1; i < tam; ++i) {
        if (arr[i] > arr[indiceMayor]) {
            indiceMayor = i; // Actualiza el índice si se encuentra un valor mayor
        }
    }

    return &arr[indiceMayor]; // Devuelve la dirección de memoria del elemento mayor
}

Esta implementación es válida porque el puntero devuelto apunta a una ubicación dentro de un array que fue declarado en la función llamadora (main), por lo que su vida útil es gestionada externamente y el puntero no apunta a memoria ya liberada.

  1. Intercambio de Cadenas: Contenido vs. Punteros

Las cadenas en C pueden ser manejadas como arrays de caracteres o como punteros a caracteres. La forma en que se intercambian difiere significativamente dependiendo de su declaración.

3.1. Intercambio del Contenido de Cadenas (Arrays de Caracteres)

Cuando las cadenas son arrays, el intercambio implica copiar el contenido de un array a otro utilizando funciones como strcpy, ya que los arrays no pueden ser asignados directamente. También se examina la diferencia entre sizeof y strlen.

#include <stdio.h>
#include <string.h>

#define LONG_MAX_CADENA 80

int main() {
    char cadenaA[LONG_MAX_CADENA] = "La programacion es un arte.";
    char cadenaB[LONG_MAX_CADENA] = "Desarrollar software es crear.";
    char bufferTemporal[LONG_MAX_CADENA];

    printf("Comparación de sizeof() vs. strlen():\n");
    printf("sizeof(cadenaA) = %zu bytes (incluye '\\0')\n", sizeof(cadenaA));
    printf("strlen(cadenaA) = %zu caracteres (no incluye '\\0')\n", strlen(cadenaA));

    printf("\nAntes del intercambio:\n");
    printf("Cadena A: %s\n", cadenaA);
    printf("Cadena B: %s\n", cadenaB);

    printf("\nRealizando el intercambio de contenido...\n");
    strcpy(bufferTemporal, cadenaA); // Copia el contenido de cadenaA a bufferTemporal
    strcpy(cadenaA, cadenaB);        // Copia el contenido de cadenaB a cadenaA
    strcpy(cadenaB, bufferTemporal); // Copia el contenido de bufferTemporal a cadenaB

    printf("\nDespués del intercambio:\n");
    printf("Cadena A: %s\n", cadenaA);
    printf("Cadena B: %s\n", cadenaB);

    return 0;
}

  • sizeof(cadenaA) devuelve el tamaño total del array en bytes (incluyendo el espacio para el terminador nulo \0, si el array es lo suficientemente grande para contenerlo más allá del contenido inicial).
  • strlen(cadenaA) devuelve la longitud de la cadena de caracterse hasta el primer \0, excluyéndolo.
  • No se puede usar cadenaA = cadenaB; directamente para arrays. La asignación de arrays no está permitida en C; en su lugar, se debe copiar el contenido elemento a elemento o usar funciones como strcpy.

3.2. Intercambio de Punteros a Cadenas

Cuando las cadenas son gessionadas a través de punteros a literales de cadena, el intercambio es mucho más simple, ya que solo se intercambian las direcciones de memoria a las que apuntan los punteros, no el contenido de las cadenas en sí.

#include <stdio.h>
#include <string.h> // Necesario para strlen, aunque no se usa en la lógica de swap de punteros

int main() {
    char *ptrCadenaA = "Explorando el universo.";
    char *ptrCadenaB = "Descubriendo nuevos mundos.";
    char *punteroTemporal;

    printf("Comparación de sizeof() vs. strlen() para punteros:\n");
    printf("sizeof(ptrCadenaA) = %zu bytes (tamaño del puntero)\n", sizeof(ptrCadenaA));
    printf("strlen(ptrCadenaA) = %zu caracteres (longitud de la cadena apuntada)\n", strlen(ptrCadenaA));

    printf("\nAntes del intercambio:\n");
    printf("Puntero A apunta a: %s\n", ptrCadenaA);
    printf("Puntero B apunta a: %s\n", ptrCadenaB);

    printf("\nRealizando el intercambio de punteros...\n");
    punteroTemporal = ptrCadenaA; // punteroTemporal ahora apunta a lo que apuntaba ptrCadenaA
    ptrCadenaA = ptrCadenaB;      // ptrCadenaA ahora apunta a lo que apuntaba ptrCadenaB
    ptrCadenaB = punteroTemporal; // ptrCadenaB ahora apunta a lo que apuntaba punteroTemporal

    printf("\nDespués del intercambio:\n");
    printf("Puntero A apunta a: %s\n", ptrCadenaA);
    printf("Puntero B apunta a: %s\n", ptrCadenaB);

    return 0;
}

  • Para un puntero a char (char *ptrCadenaA), sizeof(ptrCadenaA) devuelve el tamaño en bytes del propio puntero (generalmente 4 u 8 bytes en sistemas de 32 o 64 bits), no la longitud de la cadena a la que apunta.
  • strlen(ptrCadenaA) sigue devolviendo la longitud de la cadena terminada en nulo a la que apunta el puntero.
  • El intercambio de punteros solo reasigna las direcciones de memoria que almacenan los punteros, haciendo que apunten a diferentes literales de cadena. El contenido de las cadenas literales en sí permanece inalterado.
  1. Acceso a Arrays Bidimensionales Usando Punteros

Este segmento explora diferentes métodos para acceder a los elementos de un array bidimensional en C: a través de la notación de subíndices de array, un puntero a un entero y un puntero a un array (fila).

#include <stdio.h>

int main() {
    int matrizBidimensional[2][4] = {{10, 20, 30, 40}, {50, 60, 70, 80}};
    int i, j;
    int *punteroElementos;      // Puntero a un entero (puede recorrer elementos individuales)
    int (*punteroFilas)[4];     // Puntero a un array de 4 enteros (puede recorrer filas)

    printf("1. Acceso directo con subíndices de array:\n");
    for (i = 0; i < 2; ++i) {
        for (j = 0; j < 4; ++j)
            printf("%d ", matrizBidimensional[i][j]);
        printf("\n");
    }

    printf("\n2. Acceso indirecto con puntero a elementos (int *punteroElementos):\n");
    // punteroElementos apunta al primer elemento del array
    for (punteroElementos = &matrizBidimensional[0][0], i = 0; punteroElementos < &matrizBidimensional[0][0] + 8; ++punteroElementos, ++i) {
        printf("%d ", *punteroElementos);
        if ((i + 1) % 4 == 0) // Para formatear la salida en filas de 4
            printf("\n");
    }
                          
    printf("\n3. Acceso indirecto con puntero a filas (int (*punteroFilas)[4]):\n");
    // punteroFilas apunta a la primera fila (que es un array de 4 enteros)
    for (punteroFilas = matrizBidimensional; punteroFilas < matrizBidimensional + 2; ++punteroFilas) {
        for (j = 0; j < 4; ++j)
            printf("%d ", (*punteroFilas)[j]); // Desreferencia punteroFilas para obtener la fila, luego accede por índice
        printf("\n");
    }

    return 0;
}

  • La declaración int (*ptr)[N] define un puntero a un array de N enteros. Esto es útil para iterar sobre filas de un array bidimensional.
  • La declaración int *ptr[] (o int *ptr[N]) define un array de N punteros a enteros. Esto es distinto del caso anterior y se usa típicamente para arrays de cadenas (donde cada elemento es un puntero a una cadena).
  1. Reeemplazo de Caracteres en una Cadena

Esta sección muestra cómo implementar una función para reemplazar todas las ocurrencias de un carácter específico por otro dentro de una cadena. La modificación se realiza directamente en la cadena original.

#include <stdio.h>
#define MAX_BUFFER 80

// Prototipo de la función de reemplazo
void sustituirCaracteres(char *cadena, char caracterAntiguo, char caracterNuevo);

int main() {
    char textoOriginal[MAX_BUFFER] = "Esto es una cadena de prueba con muchos caracteres 'e'.";

    printf("Texto original:\n%s\n", textoOriginal);

    // Llamada a la función para reemplazar 'e' por '*'
    sustituirCaracteres(textoOriginal, 'e', '*'); 

    printf("Texto después del reemplazo:\n%s\n", textoOriginal);

    return 0;
}

// Implementación de la función para reemplazar caracteres
void sustituirCaracteres(char *cadena, char caracterAntiguo, char caracterNuevo) {
    // Itera a través de la cadena hasta encontrar el terminador nulo '\0'
    while (*cadena != '\0') {
        if (*cadena == caracterAntiguo) {
            *cadena = caracterNuevo; // Reemplaza el carácter si coincide
        }
        cadena++; // Avanza al siguiente carácter
    }
}

  • La función sustituirCaracteres toma un puntero a la cadena y los caracteres a reemplazar.
  • El bucle while (*cadena) es una forma idiomática de iterar sobre una cadena en C hasta que se encuentra el terminador nulo.
  • Sí, la función modifica la cadena original porque recibe un puntero a ella, permitiendo cambios in situ.
  1. Truncamiento de Cadenas por un Carácter Delimitador

Este ejemplo presenta una función que trunca una cadena en la primera aparición de un carácter dado. El truncamiento se logra reemplazando el carácter delimitador con el terminador nulo \0.

#include <stdio.h>
#include <string.h> // Para usar strcspn y limpiar la entrada
#define MAX_LONGITUD_CADENA 80

// Prototipo de la función de truncamiento
void truncarCadena(char *cadena, char delimitador);

int main() {
    char bufferEntrada[MAX_LONGITUD_CADENA];
    char caracterDelimitador;

    printf("Ingrese una cadena de texto: ");
    fgets(bufferEntrada, MAX_LONGITUD_CADENA, stdin);
    bufferEntrada[strcspn(bufferEntrada, "\n")] = 0; // Eliminar el salto de línea de fgets

    printf("Ingrese un carácter para truncar: ");
    caracterDelimitador = getchar(); // Lee un solo carácter

    printf("Realizando el truncamiento de la cadena...\n");
    truncarCadena(bufferEntrada, caracterDelimitador);

    printf("Cadena después del truncamiento: %s\n", bufferEntrada);

    return 0;
}

// Implementación de la función de truncamiento
void truncarCadena(char *cadena, char delimitador) {
    while (*cadena != '\0') { // Itera hasta el final de la cadena
        if (*cadena == delimitador) {
            *cadena = '\0'; // Reemplaza el delimitador con el terminador nulo
            return;         // Finaliza la función después del primer truncamiento
        }
        cadena++;           // Avanza al siguiente carácter
    }
}

La función truncarCadena modifica la cadena directamente al encontrar la primera ocurrencia del carácter delimitador y lo reemplaza con \0. Esto provoca que funciones como printf ignoren cualquier contenido posterior, simulando un truncamiento.

  1. Ordenación de Arrays de Punteros a Cadenas (Ordenación de Burbuja)

Este ejemplo ilustra cómo ordenar un array de punteros a cadenas alfabéticamente utilizando el algoritmo de ordenación de burbuja. La clave es comparar las cadenas con strcmp y luego intercambiar los punteros, no el contenido de las cadenas.

#include <stdio.h>
#include <string.h> // Para strcmp

// Prototipo de la función de ordenación
void ordenarCadenasBurbuja(char *listaCadenas[], int cantidad);

int main() {
    char *nombresCursos[] = {
        "Programacion en C",
        "Programacion Orientada a Objetos en C++",
        "Sistemas Operativos",
        "Estructuras de Datos y Algoritmos"
    };
    int numCursos = sizeof(nombresCursos) / sizeof(nombresCursos[0]);

    printf("Cadenas antes de ordenar:\n");
    for (int i = 0; i < numCursos; i++) {
        printf("%s\n", nombresCursos[i]);
    }

    ordenarCadenasBurbuja(nombresCursos, numCursos);

    printf("\nCadenas después de ordenar (Burbuja):\n");
    for (int i = 0; i < numCursos; i++) {
        printf("%s\n", nombresCursos[i]);
    }
    return 0;
}

// Implementación del algoritmo de Ordenación de Burbuja para un array de punteros a cadenas
void ordenarCadenasBurbuja(char *listaCadenas[], int cantidad) {
    char *punteroTemporal; // Para el intercambio de punteros

    for (int i = 0; i < cantidad - 1; ++i) {
        for (int j = 0; j < cantidad - 1 - i; ++j) {
            // Compara dos cadenas adyacentes
            if (strcmp(listaCadenas[j], listaCadenas[j + 1]) > 0) {
                // Si la cadena actual es "mayor" (viene después alfabéticamente) que la siguiente, intercámbialas
                punteroTemporal = listaCadenas[j];
                listaCadenas[j] = listaCadenas[j + 1];
                listaCadenas[j + 1] = punteroTemporal;
            }
        }
    }
}

  1. Ordenación de Arrays de Punteros a Cadenas (Ordenación por Selección)

Este ejemplo también ordena un array de punteros a cadenas, pero utilizando el algoritmo de ordenación por selección. La lógica es encontrar la cadena más pequeña en la parte no ordenada del array y colocar su puntero en la posición correcta.

#include <stdio.h>
#include <string.h> // Para strcmp

// Prototipo de la función de ordenación
void ordenarCadenasSeleccion(char *coleccionCadenas[], int elementos);

int main() {
    char *temas[] = {
        "Base de Datos SQL",
        "Redes de Computadoras",
        "Desarrollo Web Frontend",
        "Inteligencia Artificial Fundamentos"
    };
    int numTemas = sizeof(temas) / sizeof(temas[0]);

    printf("Temas antes de ordenar:\n");
    for (int i = 0; i < numTemas; i++) {
        printf("%s\n", temas[i]);
    }

    ordenarCadenasSeleccion(temas, numTemas);

    printf("\nTemas después de ordenar (Selección):\n");
    for (int i = 0; i < numTemas; i++) {
        printf("%s\n", temas[i]);
    }
    return 0;
}

// Implementación del algoritmo de Ordenación por Selección para un array de punteros a cadenas
void ordenarCadenasSeleccion(char *coleccionCadenas[], int elementos) {
    int indiceMinimo;
    char *tempPtr; // Para el intercambio de punteros

    for (int i = 0; i < elementos - 1; i++) {
        indiceMinimo = i; // Suponemos que el elemento actual es el mínimo

        // Buscamos el elemento mínimo en el resto del array
        for (int j = i + 1; j < elementos; j++) {
            if (strcmp(coleccionCadenas[j], coleccionCadenas[indiceMinimo]) < 0) {
                indiceMinimo = j; // Encontramos un elemento menor, actualizamos el índice
            }
        }

        // Si el elemento mínimo no es el actual, realizamos el intercambio de punteros
        if (indiceMinimo != i) {
            tempPtr = coleccionCadenas[i];
            coleccionCadenas[i] = coleccionCadenas[indiceMinimo];
            coleccionCadenas[indiceMinimo] = tempPtr;
        }
    }
}

  1. Validación de Formato de Identificación

Este código implementa una función básica para validar el formato de una cadena que representa un número de identificación. La validación se centra en la longitud de la cadena y en si contiene solo caracteres numéricos o una 'X' al final, según un formato específico.

#include <stdio.h>
#include <string.h> // Para strlen

#define CANT_IDS 5
#define LONG_ID_VALIDA 18

// Prototipo de la función de validación
int validarFormatoIdentificacion(const char *idCadena);

int main() {
    char *ejemplosID[CANT_IDS] = {
        "123456789012345678", // Válido
        "98765432109876543X", // Válido con 'X'
        "1122334455667789",   // Muy corto
        "ABCDE1234567890123", // Caracteres inválidos
        "00000000000000000Y"  // Carácter final inválido
    };

    printf("Verificación de formato de IDs:\n");
    for (int i = 0; i < CANT_IDS; ++i) {
        if (validarFormatoIdentificacion(ejemplosID[i])) {
            printf("%s\t-> Válido\n", ejemplosID[i]);
        } else {
            printf("%s\t-> Inválido\n", ejemplosID[i]);
        }
    }
    return 0;
}

// Implementación de la función para verificar el formato del ID
// Retorna 1 si el formato es válido, 0 en caso contrario.
int validarFormatoIdentificacion(const char *idCadena) {
    size_t longitudActual = strlen(idCadena);

    // 1. Verificar la longitud de la cadena
    if (longitudActual != LONG_ID_VALIDA) {
        return 0; // Longitud incorrecta
    }

    // 2. Verificar que todos los caracteres sean dígitos, excepto el último que puede ser 'X'
    for (int i = 0; i < longitudActual; ++i) {
        char caracter = idCadena[i];
        if (i == longitudActual - 1) { // Es el último carácter
            if (!((caracter >= '0' && caracter <= '9') || caracter == 'X')) {
                return 0; // Último carácter inválido
            }
        } else { // Caracteres anteriores al último
            if (!(caracter >= '0' && caracter <= '9')) {
                return 0; // Carácter no numérico
            }
        }
    }

    return 1; // El formato es válido
}

Esta función valida el ID según las siguientes reglas:

  • La longitud total de la cadena debe ser de 18 caracteres.
  • Todos los caracteres, excepto el último, deben ser dígitos (0-9).
  • El último carácter puede ser un dígito (0-9) o la letra mayúscula 'X'.
  1. Codificación y Decodificación Simple (Cifrado César)

Este ejemplo implementa una versión simplificada del Cifrado César, donde cada letra del alfabeto se desplaza una posición. 'z' se convierte en 'a' y 'Z' en 'A' para el proceso de codificación, y viceversa para la decodificación. Otros caracteres permanecen sin cambios.

#include <stdio.h>
#include <string.h> // Para fgets, strcspn
#define MAX_MENSAJE_LENGTH 80

// Prototipos de funciones de codificación/decodificación
void cifrarMensaje(char *mensaje);
void descifrarMensaje(char *mensaje);

int main() {
    char textoSecreto[MAX_MENSAJE_LENGTH];

    printf("Ingrese un texto en inglés: ");
    fgets(textoSecreto, MAX_MENSAJE_LENGTH, stdin);
    textoSecreto[strcspn(textoSecreto, "\n")] = 0; // Eliminar el salto de línea

    printf("Texto codificado: ");
    cifrarMensaje(textoSecreto); // Llama a la función de codificación
    printf("%s\n", textoSecreto);

    printf("Texto decodificado: ");
    descifrarMensaje(textoSecreto); // Llama a la función de decodificación
    printf("%s\n", textoSecreto);
    
    return 0;
}

/*
Función: cifrarMensaje
Propósito: Codifica una cadena aplicando un desplazamiento de +1 a las letras.
Reglas de codificación:
- Para letras de 'a' a 'y' o de 'A' a 'Y', se reemplazan por el siguiente carácter.
- 'z' se reemplaza por 'a'.
- 'Z' se reemplaza por 'A'.
- Otros caracteres (no letras) se mantienen sin cambios.
*/
void cifrarMensaje(char *mensaje) {
    while (*mensaje != '\0') {
        if (*mensaje >= 'a' && *mensaje < 'z') {
            *mensaje = *mensaje + 1;
        } else if (*mensaje == 'z') {
            *mensaje = 'a';
        } else if (*mensaje >= 'A' && *mensaje < 'Z') {
            *mensaje = *mensaje + 1;
        } else if (*mensaje == 'Z') {
            *mensaje = 'A';
        }
        mensaje++; // Avanza al siguiente carácter
    }
}

/*
Función: descifrarMensaje
Propósito: Decodifica una cadena aplicando un desplazamiento de -1 a las letras.
Reglas de decodificación:
- Para letras de 'b' a 'z' o de 'B' a 'Z', se reemplazan por el carácter anterior.
- 'a' se reemplaza por 'z'.
- 'A' se reemplaza por 'Z'.
- Otros caracteres (no letras) se mantienen sin cambios.
*/
void descifrarMensaje(char *mensaje) {
    while (*mensaje != '\0') {
        if (*mensaje > 'a' && *mensaje <= 'z') {
            *mensaje = *mensaje - 1;
        } else if (*mensaje == 'a') {
            *mensaje = 'z';
        } else if (*mensaje > 'A' && *mensaje <= 'Z') {
            *mensaje = *mensaje - 1;
        } else if (*mensaje == 'A') {
            *mensaje = 'Z';
        }
        mensaje++; // Avanza al siguiente carácter
    }
}

El código maneja el cifrado y descifrado desplazando caracteres alfabéticos una posición. Es importante considerar los casos especiales de 'z' y 'Z' para asegurar que el ciclo alfabético se complete correctamente ('z' -> 'a', 'a' -> 'z').

Etiquetas: C Pointers Arrays Strings algorithms

Publicado el 7-3 09:07