How to truncate a line in an elixir?

I work with slugs for an elixir, the idea is this: I have a line with [a-zA-Z0-9] words separated by a hyphen. How:

 string = "another-long-string-to-be-truncated-and-much-text-here" 

I want to be sure that the maximum line length is 30, but I also want to be sure that the words will not be cut in half when they reach the maximum length. So the first 30 characters of the string are equal to another-long-string-to-be-trun , but I want another-long-string-to-be with the word truncated deleted completely. How can i do this?

+7
source share
5 answers

UPD 12/2018 I posted the best solution here , I would suggest using it instead of below.


The simplest approach would be:

 "another-long-string-to-be-truncated-and-much-text-here" |> String.slice(0..29) |> String.replace(~r{-[^-]*$}, "") #⇒ "another-long-string-to-be" 

There is one drawback: if the hyphen is exactly at position 31, the last member will be deleted. To avoid this, you can explicitly check the condition above:

 str = "another-long-string-to-be-truncated-and-much-text-here" case str |> String.at(30) do "-" -> str |> String.slice(0..29) _ -> str |> String.slice(0..29) |> String.replace(~r{-[^-]*$}, "") end #⇒ "another-long-string-to-be" 

or:

 orig = "another-long-string-to-be-cool-cated-and-much-text-here" str = orig |> String.slice(0..29) unless String.at(orig, 30) == "-", do: str = str |> String.replace(~r{-[^-]*$}, "") str #⇒ "another-long-string-to-be-cool" 
+4
source

You can do this recursively.

 defmodule Truncation do def truncate_words_to(str, max) do length = String.length(str) words? = Regex.match?(~r{-}, str) cond do length <= max -> str words? -> truncate_words_to(String.replace(str, ~r{-[^-]*$}, ""), max) true -> String.slice(str, 0..(max-1)) end end end 
+1
source

First of all, if performance is not important to you, you can pass all the work into a regular expression:

~r/\A(.{0,30})(?:-|\Z)/

I assume this will be the shortest solution, but not effective:

 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" 

Effective solution

But if you care about performance and memory, then I suggest the following:

 defmodule CoolSlugHelper do def slug(input, length \\ 30) do length_minus_1 = length - 1 case input do # if the substring ends with "-" # ie "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 "-" # ie "abc-def-ghi", 7 or "abc-def-", 7 -> "abc-def" <<result::binary-size(length), "-", _::binary>> -> result # if it is the exact string. ie "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 

It does not collect the resulting char-by-char string. Instead, it searches for the correct substring, starting from the required length to 1. Thus, it becomes efficient in terms of memory and speed.

We need this length_minus_1 variable because we cannot use the expression in binary-size mapping of binary numbers.

Here is the standard of all proposed solutions as of December 22, 2018:

(The simple regular expression is ~r/\A(.{0,30})(?:-|\Z)/ regular expression above)

 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 
+1
source

My answer is based on @mudasobwa's answer, but I decided to simplify it.

 "another-long-string-to-be-truncated-and-much-text-here" |> String.slice(0..29) |> String.split("-") |> Enum.slice(0..-2) |> Enum.join("-") 

What is it!

0
source

Since this question still receives feedback from search engines, I will post the correct, fast and elixir solution for this task.

 defmodule SlugHelper do def slug(input, length \\ 30, acc \\ {"", ""}) def slug("", _, {_, result}), do: result def slug(_, 0, {_, result}), do: result def slug(<<"-", _::binary>>, 1, {acc, result}), do: result def slug(<<"-", rest::binary>>, length, {acc, ""}), do: slug(rest, length - 1, {"", acc}) def slug(<<"-", rest::binary>>, length, {acc, result}), do: slug(rest, length - 1, {"", result <> "-" <> acc}) def slug(<<chr::binary-size(1), rest::binary>>, length, {acc, result}), do: slug(rest, length - 1, {acc <> chr, result}) end string = "another-long-string-to-be-truncated-and-much-text-here" Enum.each(20..30, & string |> SlugHelper.slug(&1) |> IO.puts()) #⇒ another-long # another-long-string # another-long-string # another-long-string # another-long-string-to # another-long-string-to # another-long-string-to # another-long-string-to-be # another-long-string-to-be # another-long-string-to-be # another-long-string-to-be 
0
source

All Articles