Lendo um arquivo CSV em um array

Este artigo dá sequência à discussão iniciada em OCaml handles, onde exploramos um pouco a ideia de processo e out_channel. Neste artigo, vamos explorar in_channel. Quando mudamos, é comum embalar nossos pertences em caixas; podemos embalar vários itens em uma caixa, o que torna fácil o transporte, imagine o trabalho que seria carregar uma pilha de itens. De modo análogo, na memória do computador, é preferível tratar itens em massa, assim podemos lidar com vários elementos de uma vez.

Anteriormente, fizemos uso do out_channel para escrever em um arquivo. Existe uma correspondência direta, permitindo-nos ler dados através de um canal de entrada, denominado in_channel. Vamos explorar como podemos ler de um arquivo. O primeiro passo é entender como abrir o canal de entrada:

val open_in : string -> in_channel = <fun>
let open_in name =
  open_in_gen [Open_rdonly; Open_text] 0 name

Olhando a assinatura da função open_in, vemos que ela recebe uma string e retorna um in_channel. A função é implementada em termos de open_in_gen, que é uma função mais genérica que permite passar uma lista de open_flag. A flag Open_rdonly indica que o arquivo será aberto apenas para leitura, e Open_text indica que o arquivo será aberto em modo texto. O segundo argumento é um inteiro que representa a permissão do arquivo, que é ignorado quando o arquivo é aberto em modo leitura.

Depois de abrir o canal, o próximo passo é ler dele. Para ler um arquivo, temos a função input_value:

val input_value: in_channel -> a'

Como vemos na assinatura, ela recebe um in_channel e retorna uma a'. Combinando ambos, podemos ler arquivos:

let read_greeting_file () =
  let dir = get_dir "greetings" in
  let file = dir ^ "/greeting.txt" in
  let ic = open_in file in
  let content : string = input_value ic in
  close_in ic;
  content

Assim como fechávamos o out_channel com close_out, fechamos o in_channel com close_in.

Arquivos Gigantes

O exemplo anterior carrega todo o arquivo na memória, mas e se o arquivo for muito grande? Provavelmente não teremos memória suficiente para carregar todo o arquivo. Para lidar com arquivos grandes, podemos ler o arquivo em pedaços, uma das maneiras de fazer isso é usando input_line:

val input_line: in_channel -> string

Como vemos na assinatura, ele recebe um in_channel e retorna uma string. A função lê uma linha do canal de entrada e retorna a linha lida sem o caractere de nova linha. Se o fim do arquivo for atingido, a função retorna exception End_of_file. Assim, podemos ler um arquivo linha por linha:

let read_file_line_by_line ic =
  let rec read_lines acc =
    try
      let line = input_line ic in
      read_lines (line :: acc)
    with End_of_file -> List.rev acc
  in
  let lines = read_lines [] in
  close_in ic;
  lines

Outra maneira de ler arquivos em pedaços é usando input:

val input : in_channel -> bytes -> int -> int -> int

Na assinatura, temos que ele recebe um in_channel, um bytes que é o buffer onde os dados lidos serão armazenados, um inteiro que é o deslocamento no buffer onde os dados lidos serão armazenados, um inteiro que é o número de bytes a serem lidos e retorna um inteiro que é o número de bytes lidos. A função lê até n bytes do canal de entrada e os armazena no buffer a partir do deslocamento. Se o fim do arquivo for atingido, a função retorna 0. Assim, podemos ler um arquivo em chunks:

let read_file_in_chunks filename chunk_size =
  let ic = open_in filename in
  try
    while true do
      (* Cria um buffer para o chunk *)
      let buffer = Bytes.create chunk_size in
      (* Tenta ler um chunk do arquivo *)
      let bytes_read = input ic buffer 0 chunk_size in
      if bytes_read = 0 then
        raise End_of_file; (* Fim do arquivo alcançado *)
      (* Processa o chunk lido aqui *)
      (* Por exemplo, podemos imprimir o chunk lido *)
      print_endline (Bytes.sub_string buffer 0 bytes_read);
    done
  with End_of_file ->
    close_in ic; (* Certifique-se de fechar o arquivo quando terminar *)

OCaml suporta a leitura de grandes arquivos. Normalmente, o tamanho máximo de arquivo é o tamanho do max_int, que na maioria dos sistemas é inferior a 2 GB. O módulo LargeFile oferece funções que retornam tamanho e posição, e também podem buscar nesses arquivos maiores. A utilização desse módulo fica como exercício para o leitor (😝).