<commit_before>defmodule Elixlsx.Util do
  @col_alphabet to_string(Enum.to_list(?A..?Z))

  @doc ~S"""
  returns the column letter(s) associated with a column index. Col idx starts at 1.

  ## Example

      iex> encode_col(1)
      "A"

      iex> encode_col(28)
      "AB"

  """
  @spec encode_col(non_neg_integer) :: String.t
  def encode_col(0), do: ""
  def encode_col(num) when num <= 26, do: <<num + 64>>
  def encode_col(num) do
    mod = div(num, 26)
    rem = rem(num, 26)

    if rem == 0 do
      encode_col(mod - 1) <> encode_col(26)
    else
      encode_col(mod) <> encode_col(rem)
    end
  end

  @doc ~S"""
  returns the column index associated with a given letter.

  ## Example

      iex> decode_col("AB")
      28

      iex> decode_col("A")
      1

  """
  @spec decode_col(list(char()) | String.t) :: non_neg_integer
  def decode_col s do
    cond do
      is_list s -> decode_col(to_string s)
      String.valid? s -> decode_col_ s
      true -> raise %ArgumentError{message: "decode_col expects string or charlist, got "
                                   <> inspect s}
    end
  end


  @spec decode_col_(String.t) :: non_neg_integer
  defp decode_col_("") do 0 end
  defp decode_col_(s) do
    alphabet_list = String.to_charlist @col_alphabet

    if !String.match? s, ~r/^[A-Z]*$/ do
      raise %ArgumentError{message: "Invalid column string: " <> inspect s}
    end

    # translate list of strings to the base-26 value they represent
    Enum.map(String.to_charlist(s), (fn x -> :string.chr(alphabet_list, x) end)) |>
    # multiply and aggregate them
    List.foldl(0, (fn (x, acc) -> x + 26 * acc end))
  end


  @doc ~S"""
  Returns the Char/Number representation of a given row/column combination.
  Indizes start with 1.

  ## Examples

      iex> to_excel_coords(1, 1)
      "A1"

      iex> to_excel_coords(10, 27)
      "AA10"

  """
  @spec to_excel_coords(number, number) :: String.t
  def to_excel_coords(row, col) do
    encode_col(col) <> to_string(row)
  end

  @spec from_excel_coords(String.t) :: {pos_integer, pos_integer}
  @doc ~S"""
  returns a tuple {row, col} corresponding to the input.
  row and col are 1-indexed, use from_excel_coords0 for zero-indexing.

  Example:

      iex> from_excel_coords("C2")<commit_msg>remove redundant to_string & to_charlist<commit_after>defmodule Elixlsx.Util do
  @col_alphabet Enum.to_list(?A..?Z)

  @doc ~S"""
  returns the column letter(s) associated with a column index. Col idx starts at 1.

  ## Example

      iex> encode_col(1)
      "A"

      iex> encode_col(28)
      "AB"

  """
  @spec encode_col(non_neg_integer) :: String.t
  def encode_col(0), do: ""
  def encode_col(num) when num <= 26, do: <<num + 64>>
  def encode_col(num) do
    mod = div(num, 26)
    rem = rem(num, 26)

    if rem == 0 do
      encode_col(mod - 1) <> encode_col(26)
    else
      encode_col(mod) <> encode_col(rem)
    end
  end

  @doc ~S"""
  returns the column index associated with a given letter.

  ## Example

      iex> decode_col("AB")
      28

      iex> decode_col("A")
      1

  """
  @spec decode_col(list(char()) | String.t) :: non_neg_integer
  def decode_col s do
    cond do
      is_list s -> decode_col(to_string s)
      String.valid? s -> decode_col_ s
      true -> raise %ArgumentError{message: "decode_col expects string or charlist, got "
                                   <> inspect s}
    end
  end


  @spec decode_col_(String.t) :: non_neg_integer
  defp decode_col_("") do 0 end
  defp decode_col_(s) do
    if !String.match? s, ~r/^[A-Z]*$/ do
      raise %ArgumentError{message: "Invalid column string: " <> inspect s}
    end

    # translate list of strings to the base-26 value they represent
    Enum.map(String.to_charlist(s), (fn x -> :string.chr(@col_alphabet, x) end)) |>
    # multiply and aggregate them
    List.foldl(0, (fn (x, acc) -> x + 26 * acc end))
  end


  @doc ~S"""
  Returns the Char/Number representation of a given row/column combination.
  Indizes start with 1.

  ## Examples

      iex> to_excel_coords(1, 1)
      "A1"

      iex> to_excel_coords(10, 27)
      "AA10"

  """
  @spec to_excel_coords(number, number) :: String.t
  def to_excel_coords(row, col) do
    encode_col(col) <> to_string(row)
  end

  @spec from_excel_coords(String.t) :: {pos_integer, pos_integer}
  @doc ~S"""
  returns a tuple {row, col} corresponding to the input.
  row and col are 1-indexed, use from_excel_coords0 for zero-indexing.

  Example:

      iex> from_excel_coords("C2")
{'fraction_non_alphanumeric': 0.12261640798226164, 'fraction_numerical': 0.01729490022172949, 'mean_word_length': 2.7497921862011636, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6094968', 'n_tokens_mistral': 1721, 'n_tokens_neox': 1601, 'n_words': 459}