【问题标题】:how to replace the nth character in a string with another [ELIXIR/ ERLANG]如何用另一个 [ELIXIR/ERLANG] 替换字符串中的第 n 个字符
【发布时间】:2018-06-12 18:53:27
【问题描述】:

如何在 Elixir 或 Erlang 中执行 String replace_at

例如给定这个固定宽度的文件:

EmployeeFundMappingID EmployeeID  FundID      IsActive EntryDate               ExitDate                ExitTypeID  DateCreated             CreatedByID DateModified            ModifiedByID ConfirmedBy DateConfirmed           GUID                                     IsPooled DatePooled
1                     1118544     1           1        2009-04-20 00:00:00.000 NULL                    NULL        2014-05-17 08:46:48.020 1           2014-10-30 13:34:47.177 NULL         1           2009-04-20 17:48:12.067 NULL                                     NULL     NULL
2                     1027350     1           1        2008-03-03 00:00:00.000 NULL                    NULL        2014-05-17 08:46:48.020 1           2014-10-30 13:34:47.177 NULL         1           2008-05-04 15:13:30.303 NULL                                     NULL     NULL
3                     1024795     1           1        2008-02-29 00:00:00.000 NULL                    NULL        2014-05-17 08:46:48.020 1           2014-10-30 13:34:47.177 NULL         1           2008-05-04 15:13:30.303 NULL                                     NULL     NULL
4                     1116497     1           1        2009-03-24 00:00:00.000 NULL                    NULL        2014-05-17 08:46:48.020 1           2014-10-30 13:34:47.177 NULL         1           2009-03-24 13:00:15.277 NULL                                     NULL     NULL
5                     1116569     1           1        2009-03-24 00:00:00.000 NULL                    NULL        2014-05-17 08:46:48.020 1           2014-10-30 13:34:47.177 NULL         1           2009-03-24 14:43:08.280 NULL                                     NULL     NULL
6                     1116920     1           1        2009-03-27 00:00:00.000 NULL                    NULL        2014-05-17 08:46:48.020 1           2014-10-30 13:34:47.177 NULL         1           2009-03-27 17:16:35.073 NULL                                     NULL     NULL

col 位置为:

[0, 22, 34, 46, 55, 79, 103, 115, 139, 151, 175, 188, 200, 224, 265, 274]

我们如何在每个列位置将\s 替换为\t

我正在有效地尝试将Fixed-Width 文件转换为csv

【问题讨论】:

  • 你试过String.replace/4吗?它可以使用正则表达式将空格替换为逗号。
  • @AbhyuditJain,这些空间位于特定位置。这将如何运作?
  • String.replace(data, ~r{ +}, ",")
  • @AbhyuditJain 谢谢。这有效,但它在日期时间分裂
  • 我没有注意到那里的空间。这将是一个棘手的正则表达式。

标签: string erlang elixir


【解决方案1】:

我会用一组改变字符串中相应位置的函数来减少原始行。

funs =
  [22, 34, 46, 55, 79, 103, 115, 139, 151, 175, 188, 200, 224, 265, 274]
  |> Enum.map(& &1 - 1)
  |> Enum.map(fn len ->
       fn <<s :: binary-size(len), " ", rest :: binary>> ->
         s <> "\t" <> rest
       end
     end)

input
|> String.trim
|> String.split("\n")
|> Enum.map(fn line ->
     Enum.reduce(funs, line, fn fun, acc -> fun.(acc) end)
   end)

这可能会以更优雅的方式使用生成的宏、每个位置一次和递归调用来完成,但在这里减少函数列表对我来说看起来更直接。


这种方法的优点是它会立即在任何不一致的数据上失败,确保(或多或少)如果它通过了,则转换正确完成,这与所有其他较短的解决方案不同。

而且它比任何Regex 解决方案都快得多。


由于这将应用于 16M 行,因此这可能是性能最高的版本,它一次匹配整行:

input
|> String.trim
|> String.split("\n")
|> Enum.map(
     # [22, 34, 46, 55, 79, 103,
     #  115, 139, 151, 175, 188,
     #  200, 224, 265, 274]
     # note: this assumes the listed positions above are 1-based
     fn <<
        c1 :: binary-size(21),
        " ",
        c2 :: binary-size(11),
        " ",
        c3 :: binary-size(11),
        " ",
        c4 :: binary-size(8),
        " ",
        c5 :: binary-size(23),
        " ",
        c6 :: binary-size(23),
        " ",
        c7 :: binary-size(11),
        " ",
        c8 :: binary-size(23),
        " ",
        c9 :: binary-size(11),
        " ",
        c10 :: binary-size(23),
        " ",
        c11 :: binary-size(12),
        " ",
        c12 :: binary-size(11),
        " ",
        c13 :: binary-size(23),
        " ",
        c14 :: binary-size(40),
        " ",
        c15 :: binary-size(8),
        " ",
        c16 :: binary
        >> ->
     c1 <> "\t" <> 
       c2 <> "\t" <> 
       c3 <> "\t" <> 
       c4 <> "\t" <> 
       c5 <> "\t" <> 
       c6 <> "\t" <> 
       c7 <> "\t" <> 
       c8 <> "\t" <> 
       c9 <> "\t" <> 
       c10 <> "\t" <> 
       c11 <> "\t" <> 
       c12 <> "\t" <> 
       c13 <> "\t" <> 
       c14 <> "\t" <> 
       c15 <> "\t" <> 
       c16
   end)

【讨论】:

  • 问题在于它没有将其转换为 csv
  • @AbhyuditJain 对不起? CSV 完全接受 "\t" 作为列分隔符。如果您想要逗号分隔,请将s &lt;&gt; "\t" &lt;&gt; rest 中的"\t" 更改为您想要的任何内容。
  • @mudasobwa 嗨。请我将其应用于超过 1600 万行的文件...这会表现良好吗?
  • 我已经用可能是性能最高的版本更新了答案;如果您需要确切地知道什么更好,请做一个基准测试,但我很确定这两个版本都应该可以很好地处理 16M 行。
  • @mudasobwa 我最初的想法是我需要在某些时候使用 File.stream 来逐行读取文件,所以大小将是一个问题......然后应用地图转换,然后逐行写回文件。我猜input 可能是一个 File.stream ...对吗?
【解决方案2】:

您可以先加入日期时间,然后用逗号替换所有空格,然后将日期时间恢复为原始格式:

data
|> String.replace(~r/(-\d+)([\s]{1})(\d+)/, "\\1T\\3")
|> String.replace(~r/ +/, ",")
|> String.replace(~r/(\d)(T)(\d)/, "\\1 \\3")

【讨论】:

    【解决方案3】:

    在超过 1600 万行的数据集上比较两种实现:

      def flat2csv1(src, dst) do
        Logger.info("START")
    
        t = System.system_time(:millisecond)
    
        funs =
          [12, 52, 76]
          |> Enum.map(&(&1 - 1))
          |> Enum.map(fn len ->
            fn <<s::binary-size(len), " ", rest::binary>> ->
              s <> "\t" <> rest
            end
          end)
    
        File.stream!(src)
        |> Enum.map(fn line ->
          Enum.reduce(funs, line, fn fun, acc -> fun.(acc) end)
        end)
        |> write(dst)
    
        log_elapsed("DONE", t)
      end
    
      def flat2csv0(src, dst) do
        Logger.info("START")
    
        t = System.system_time(:millisecond)
    
        File.stream!(src)
        |> Enum.map(fn <<
                         c1::binary-size(11),
                         " ",
                         c2::binary-size(39),
                         " ",
                         c3::binary-size(23),
                         " ",
                         ce::binary
                       >> ->
          c1 <> "\t" <> c2 <> "\t" <> c3 <> "\t" <> ce
        end)
        |> write(dst)
    
        log_elapsed("DONE", t)
      end
    
      defp log_elapsed(s, t) do
        t = System.system_time(:millisecond) - t
        Logger.debug("#{s}: #{t} ms")
      end
    
      defp write(s, dst) do
        File.write!(dst, s, [:append])
      end
    

    结果

    # flat2csv0
    11:40:25.055 [info] START
    11:42:26.028 [info] DONE: 120969 ms
    
    # flat2csv1
    11:45:17.521 [info] START
    11:48:25.433 [info] DONE: 187906 ms
    

    【讨论】:

    • 哇。我一直尊重 Erlang VM 二进制模式匹配优化,但我无法想象它们as 非常棒。——谢谢!
    猜你喜欢
    • 1970-01-01
    • 2018-06-04
    • 1970-01-01
    • 1970-01-01
    • 2021-06-15
    • 2015-10-24
    • 2017-12-01
    • 2013-12-03
    • 1970-01-01
    相关资源
    最近更新 更多