【问题标题】:Elixir - How to improve the code and styleElixir - 如何改进代码和风格
【发布时间】:2021-04-21 20:33:05
【问题描述】:

目标是一个脚本,它逐行读取文件,包含文件路径(Windows 和 Linux)。它剥离路径,只留下带有扩展名的文件名。然后用“_”替换文件名中的任何特殊字符 - 下划线,最后将连续的下划线减少一个。 就像 st__a___ck 变成 st_a_ck。 我让它工作了,但我相信可能有更好/更好看的方式来做到这一点。 我是一个非常初学者,仍在学习以 Elixir/功能方式思考。 我想要的是看到不同的方法来做这件事,改进和提炼一点的方法。

测试样本:

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")

【问题讨论】:

标签: erlang elixir


【解决方案1】:

我会首先指出代码的一般问题。

  • File.stream!/3 生成一个明确设计为延迟处理的Stream(因此我们不会将文件的全部内容保存在内存中)。将其传递给Enum.map/2 是零意义的。使用Stream.map/2 继续懒惰地处理文件,或使用Flow.map/2 并行化映射操作并使用所有可用内核(你也保持懒惰!)。
  • 格式很重要。我们使用 2 个空格作为缩进。使用Elixir Formatter(或混合任务formatter)来格式化您的代码。
  • 尽可能直接在函数头中分解(而不是defp print_name_and_suffix(str), do: [h|t] = str ... 直接执行defp print_name_and_suffix([h|t])
  • 尽量减少字符串中的替换调用次数,因为每个调用都需要单独的字符串传递来替换字符。
  • 使用带有模式匹配的不同函数子句来简化生活。
  • 尽可能使用二进制模式匹配和递归。

也就是说,最 [固执] Elixirish 的方法是:

defmodule Paths do
  def read_file(filename) do
    filename
    |> File.stream!()
    # Uncomment next line and replace all Steam calls with Flow 
    # to embrace multi core parallelism
    # |> Flow.from_enumerable()  
    |> 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/2String 模块中的任何内部函数,但我明确地用最实用的方法实现了它。

【讨论】:

  • 这是很棒的东西。一定会牢记这些要点。非常感谢。
猜你喜欢
  • 2019-03-02
  • 1970-01-01
  • 2011-06-17
  • 2022-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多