将字符串拆分成多个chunk,每个chunk不超过100个字符,拆分不能在数字中间

split the string in multiple chunks, with each chunk not exceeding 100 characters and the split cannot be in the middle of a number

给定一个整数枚举(来自外部源),我需要将它们转换为字符串,然后加入它们(由 space 分隔)。

第二部分是将结果字符串拆分成多个chunk,每个chunk不超过100个字符,拆分不能在数字中间(所以拆分只能发生在插入数字之间的 space 上。

第一部分很简单。实际上将生成的字符串的大小保持在限制之下是一个挑战。

查看了 Enum。chunk_while/4 但不确定这是正确的方法。我还探索了在列表为整数时拆分列表并尝试加入结果但没有运气。

这是一个函数,join_len/2,一次完成。它的工作原理是构建每个字符串,直到达到 100 个字符,然后移动到下一个字符串。

defmodule Example do
  def join_len(enum, len), do: join_len(enum, "", [], len)

  defp join_len([], curr, done, _len), do: Enum.reverse([curr | done])

  defp join_len([num | rest], curr, done, len) do
    str = Integer.to_string(num)
    size = byte_size(str)
    curr_size = byte_size(curr)

    cond do
      curr_size == 0 -> join_len(rest, str, done, len)
      curr_size + size + 1 <= len -> join_len(rest, "#{curr} #{str}", done, len)
      curr_size + size + 1 > len -> join_len(rest, str, [curr | done], len)
    end
  end

  def test do
    nums = for _i <- 1..20, do: 1_234_567_890
    join_len(nums, 100)
  end
end

运行 test 产生:

[
    "1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890",
    "1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890",
    "1234567890 1234567890"
]

这是为了完整性。 Adam 发布并且我接受的答案更加简洁明了

def max_size_str(enumin, spacing) do
chunk_fun = fn element, acc ->
  acc_sum = acc |> Enum.map(fn x -> List.last(x) end) |> Enum.sum()

  if acc_sum + List.last(element) <= spacing do
    if acc == [[0, 0]] do
      {:cont, [element]}
    else
      {:cont, [element | acc]}
    end
  else
    {:cont, acc, [element]}
  end
end

after_fun = fn
  element ->
    {:cont, element, []}
end

enumin
|> Enum.map(fn x -> [x, String.length(Integer.to_string(x)) + 1] end)
|> Enum.chunk_while([[0, 0]], chunk_fun, after_fun)
|> Enum.map(&Enum.reverse(&1))
|> Enum.map(fn x -> x |> Enum.map(&List.first(&1)) end)
|> Enum.map(&Enum.join(&1, " "))

end

如果生成的字符串的总长度不是很大,并且您可以先连接所有数字(如您在原始问题中所述),那么涉及 Regex.scan/2 的解决方案可能会更加简洁。

nums = for _i <- 1..20, do: 1_234_567_890
Regex.scan(~r/\d.{1,99}(?=\s|\Z)/, Enum.join(nums, " "))

#⇒ [
#    ["1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"],
#    ["1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890 1234567890"],
#    ["1234567890 1234567890"]
#  ]

这里的正则表达式贪婪地寻找一个数字后跟最多 99 个符号,然后是 space 或输入结束(后者不包括在匹配中。)

出于好奇,这里有一个稍微修改过的 Adam 解决方案,它在函数子句中使用模式匹配来实现相同的结果。

defmodule Example do
  def join_len(enum, len, acc \ [])

  def join_len([], _len, acc), do: Enum.reverse(acc)

  def join_len([num | rest], len, acc),
    do: join_len(rest, len, join_num("#{num}", len, acc))
    
  defp join_num(num, _, []), do: [num]
  defp join_num(num, len, [head|tail])
      when byte_size(num) + byte_size(head) < len,
    do: [head <> " " <> num | tail]
  defp join_num(num, _, acc), do: [num | acc]

  def test do
    nums = for _i <- 1..20, do: 1_234_567_890
    join_len(nums, 100)
  end
end