首先,如果你根本不关心性能,您可以将所有工作转发给正则表达式:
~r/\A(.{0,30})(?:-|\Z)/
我认为这将是最短的解决方案,但效率不高:
iex(28)> string
"another-long-string-to-be-truncated-and-much-text-here"
iex(29)> string2
"another-long-string-to-be-cool-about-that"
iex(30)> Regex.run(~r/\A(.{0,30})(?:-|\Z)/, string) |> List.last()
"another-long-string-to-be"
iex(31)> Regex.run(~r/\A(.{0,30})(?:-|\Z)/, string2) |> List.last()
"another-long-string-to-be-cool"
高效的解决方案
但如果您确实关心性能和内存,那么我建议:
defmodule CoolSlugHelper do
def slug(input, length \\ 30) do
length_minus_1 = length - 1
case input do
# if the substring ends with "-"
# i. e. "abc-def-ghi", 8 or "abc-def-", 8 -> "abc-def"
<<result::binary-size(length_minus_1), "-", _::binary>> -> result
# if the next char after the substring is "-"
# i. e. "abc-def-ghi", 7 or "abc-def-", 7 -> "abc-def"
<<result::binary-size(length), "-", _::binary>> -> result
# if it is the exact string. i. e. "abc-def", 7 -> "abc-def"
<<_::binary-size(length)>> -> input
# return an empty string if we reached the beginnig of the string
_ when length <= 1 -> ""
# otherwise look into shorter substring
_ -> slug(input, length_minus_1)
end
end
end
它不会逐个字符地收集结果字符串。相反,它会从所需长度开始一直到 1 查找正确的子字符串。这就是它在内存和速度方面变得高效的方式。
我们需要这个length_minus_1
变量,因为我们不能在binary-size
二进制模式匹配。
以下是截至 2018 年 12 月 22 日所有提议解决方案的基准:
(简单的正则表达式是~r/\A(.{0,30})(?:-|\Z)/
上面的正则表达式)
Name ips average deviation median 99th %
CoolSlugHelper 352.14 K 2.84 μs ±1184.93% 2 μs 8 μs
SlugHelper 70.98 K 14.09 μs ±170.20% 10 μs 87 μs
Simple Regex 33.14 K 30.17 μs ±942.90% 21 μs 126 μs
Truncation 11.56 K 86.51 μs ±84.81% 62 μs 299 μs
Comparison:
CoolSlugHelper 352.14 K
SlugHelper 70.98 K - 4.96x slower
Simple Regex 33.14 K - 10.63x slower
Truncation 11.56 K - 30.46x slower
Memory usage statistics:
Name Memory usage
CoolSlugHelper 2.30 KB
SlugHelper 12.94 KB - 5.61x memory usage
Simple Regex 20.16 KB - 8.75x memory usage
Truncation 35.36 KB - 15.34x memory usage