Эликсир - Как улучшить код и стиль - PullRequest
0 голосов
/ 21 декабря 2018

Целью был скрипт, который построчно считывает файл, содержащий пути к файлам (Windows и Linux).Он удаляет путь, оставляя только имя файла с расширением.Затем заменяет любые специальные символы в имени файла символом подчеркивания «_» и в конце сокращает количество последовательных подчеркиваний только на один.Как st__a___ck становится st_a_ck.Я получил это работает, но я верю, что может быть лучший / более приятный способ сделать это.Я очень новичок и все еще учусь думать об эликсире / функциональности.Я хочу увидеть разные способы сделать это, способы немного улучшить и улучшить.

Тестовый образец:

c:\program files\mydir\mydir2\my&@Doc.doc 
c:\program files\mydir\mydir2\myD$oc2.doc\ 
c:\\program files\\mydir\\mydir2\\myD;'oc2.doc
c:\\program files\\mydir\mydir2\\my[Doc2.doc\\
/home/python/projects/files.py
/home/python/projects/files.py/
//home//python//projects//files.py
//home//python//projects//files.py//
c:\program files\mydir\mydir2\my!D#oc.doc 
c:\program files\mydir\mydir2\myDoc2.doc\ 
c:\\program files\\mydir\\mydir2\\my';Doc2.doc
c:\\program files\\mydir\mydir2\\myD&$%oc2.doc\\
/home/python/projects/f_)*iles.py
/home/python/projects/files.py/
//home//python//projects//fi=-les.py
//home//python//projects//fil !%es.py//
/home/python/projects/f_)* iles.py
/home/python/projects/fi les.py/
//home//python//projects//fii___kiii=- les.py 
//home//python//projects//ff###f!%#illfffl! %es.py//

Код:

defmodule Paths do

     def read_file(filename) do
         File.stream!(filename)
         |> Enum.map( &(String.replace(&1,"\\","/")) )
         |> Enum.map( &(String.trim(&1,"\n")) )
         |> Enum.map( &(String.trim(&1,"/")) )
         |> Enum.map( &(String.split(&1,"/")) )
         |> Enum.map( &(List.last(&1)) )
         |> Enum.map( &(String.split(&1,".")) )
         |> Enum.map( &(remove_special)/1 )
         |> Enum.map( &(print_name_and_suffix)/1 )

     end
     defp print_name_and_suffix(str) do
         [h|t] = str
         IO.puts "Name: #{h}\t suffix: #{t}\t: #{h}.#{t}"
     end
     defp remove_special(str) do
         [h|t] = str
         h = String.replace(h, ~r/[\W]/, "_")
         h = String.replace(h, ~r/_+/, "_")
         [h]++t
     end

end

Paths.read_file("test.txt")

Любое понимание высоко ценится.

РЕДАКТИРОВАТЬ: Я немного рефакторинг кода.Какая версия больше похожа на стиль Elixir?

defmodule Paths do

     def read_file(filename) do
         File.stream!(filename)
         |> Enum.map( &(format_path)/1 )
         |> Enum.map( &(remove_special)/1 )
         |> Enum.map( &(print_name_and_suffix)/1 )

     end

     defp format_path(path) do
             path
             |> String.replace("\\","/")
             |> String.trim("\n")
             |> String.trim("/")
             |> String.trim("\\")
     end

     defp print_name_and_suffix(str) do
         [h|t] = str
         IO.puts "Name: #{h}\t suffix: #{t}\t: #{h}#{t}"
     end

     defp remove_special(str) do
         ext = Path.extname(str)
         filename = Path.basename(str)
             |> String.trim(ext)
             |> String.replace(~r/[\W]/, "_")
             |> String.replace( ~r/_+/, "_")

         [filename]++ext
     end

end

Paths.read_file("test.txt")

1 Ответ

0 голосов
/ 21 декабря 2018

Во-первых, я бы указал на общие проблемы с кодом.

  • File.stream!/3 создает Stream, явно предназначенный для одновременной обработки.Передача в Enum.map/2 не имеет смысла.Используйте Stream.map/2 для обработки файла в стольких параллельных процессах, сколько у вас есть ядер.
  • Форматирование имеет значение.Мы используем 2 пробела для отступа.Используйте Elixir Formatter (или смешанное задание formatter) для форматирования кода.
  • Разложите непосредственно в функциональной головке, где это возможно (вместо defp print_name_and_suffix(str), do: [h|t] = str ..., делайте напрямую defp print_name_and_suffix([h|t]).
  • Минимизируйте количество обращений к замещению в строках, так как для каждого из них требуется отдельный проход строки для замены символов.
  • Использование различных функциональных предложений с сопоставлением с образцом для упрощения жизни.
  • Попробуйте использовать двоичный файлсопоставление с образцом и рекурсия, где это применимо.

Тем не менее, наиболее [самоуверенный] подход к эликсиру будет выглядеть так:

defmodule Paths do
  def read_file(filename) do
    filename
    |> File.stream!()
    |> Stream.map(&right_trim/1)
    |> Stream.map(&strip_path/1)
    |> Stream.map(&split_and_cleanup/1)
    |> Stream.map(&name_and_suffix/1)
    |> Enum.to_list()
  end

  defp right_trim(str), do: Regex.replace(~r/\W+\z/, str, "")

  defp strip_path(input, acc \\ "")
  defp strip_path("", acc), do: acc
  defp strip_path(<<"\\", rest :: binary>>, acc), do: strip_path(rest, "")
  defp strip_path(<<"/", rest :: binary>>, acc), do: strip_path(rest, "")
  defp strip_path(<<chr :: binary-size(1), rest :: binary>>, acc),
    do: strip_path(rest, acc <> chr)

  defp split_and_cleanup(str) do
    str
    |> String.split(".")
    |> Enum.map(&String.replace(&1, ~r/[_\W]+/, "_"))
  end

  defp name_and_suffix([file, ext]) do
    IO.puts "Name: #{file}\t suffix: .#{ext}\t: #{file}.#{ext}"
  end
end

Paths.read_file("/tmp/test.txt")

Обратите внимание, в основном, на strip_path/2 функцию, он рекурсивно анализирует входную строку, возвращая часть после последней косой черты, вперед или назад. Я мог бы использовать String.split/2 или любую внутреннюю функцию из модуля String, но я явно реализовал ее с наиболее функциональным подходом.

...