From 31c1153ba9aaea063a9c3762e494939f58bc07cf Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Wed, 23 Apr 2025 13:40:14 +0200 Subject: [PATCH 1/8] add String.count/2 Discussed on https://groups.google.com/g/elixir-lang-core/c/JtWvn9aghgQ --- lib/elixir/lib/string.ex | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index f3f78f91a8a..f07a856398e 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -709,6 +709,34 @@ defmodule String do {left, right} end + @doc ~S""" + Counts the number of occurrences of a pattern in a string. + + ## Examples + + iex> String.count("hello world", "o") + 2 + + iex> String.count("hello world", "l") + 3 + + iex> String.count("hello world", "x") + 0 + + iex> String.count("hello world", ~r/o/) + 2 + + """ + @spec count(t, pattern | Regex.t()) :: non_neg_integer + @doc since: "1.19.0" + def count(string, pattern) when is_struct(pattern, Regex) do + Enum.count(Regex.scan(pattern, string)) + end + + def count(string, pattern) do + Enum.count(:binary.matches(string, pattern)) + end + @doc ~S""" Returns `true` if `string1` is canonically equivalent to `string2`. From 76931abbaf718e375410a910991f695de4f73414 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Wed, 23 Apr 2025 14:05:57 +0200 Subject: [PATCH 2/8] Update lib/elixir/lib/string.ex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Valim --- lib/elixir/lib/string.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index f07a856398e..99559d877e2 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -730,11 +730,11 @@ defmodule String do @spec count(t, pattern | Regex.t()) :: non_neg_integer @doc since: "1.19.0" def count(string, pattern) when is_struct(pattern, Regex) do - Enum.count(Regex.scan(pattern, string)) + length(Regex.scan(pattern, string, return: :index)) end def count(string, pattern) do - Enum.count(:binary.matches(string, pattern)) + length(:binary.matches(string, pattern)) end @doc ~S""" From ced5f3d1f51724271bd0b45e8cf1071b065c8b6d Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Wed, 23 Apr 2025 14:09:48 +0200 Subject: [PATCH 3/8] add doctest for compiled pattern, use Kernel.length --- lib/elixir/lib/string.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 99559d877e2..afa07cf2f49 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -726,15 +726,19 @@ defmodule String do iex> String.count("hello world", ~r/o/) 2 + iex> pattern = :binary.compile_pattern([" ", "!"]) + iex> String.count("foo bar baz!!", pattern) + 4 + """ @spec count(t, pattern | Regex.t()) :: non_neg_integer @doc since: "1.19.0" def count(string, pattern) when is_struct(pattern, Regex) do - length(Regex.scan(pattern, string, return: :index)) + Kernel.length(Regex.scan(pattern, string, return: :index)) end def count(string, pattern) do - length(:binary.matches(string, pattern)) + Kernel.length(:binary.matches(string, pattern)) end @doc ~S""" From a863d059079d1ebd27094555fd5230b34834076e Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Wed, 23 Apr 2025 14:10:06 +0200 Subject: [PATCH 4/8] Update lib/elixir/lib/string.ex Co-authored-by: Jean Klingler --- lib/elixir/lib/string.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index afa07cf2f49..795af5d9414 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -710,7 +710,7 @@ defmodule String do end @doc ~S""" - Counts the number of occurrences of a pattern in a string. + Counts the number of occurrences of a `pattern` in a `string`. ## Examples From 685c6cb25093bd1f16566330b24fc6180e1a61c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Apr 2025 14:30:57 +0200 Subject: [PATCH 5/8] Update lib/elixir/lib/string.ex --- lib/elixir/lib/string.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 795af5d9414..84242955e5b 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -726,6 +726,8 @@ defmodule String do iex> String.count("hello world", ~r/o/) 2 + The `pattern` can also be a compiled pattern: + iex> pattern = :binary.compile_pattern([" ", "!"]) iex> String.count("foo bar baz!!", pattern) 4 From 1f33ad78554dc0b4ed30481101ddeecebcf95ad4 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Thu, 24 Apr 2025 13:16:32 +0200 Subject: [PATCH 6/8] count -> count_occurrences --- lib/elixir/lib/string.ex | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 84242955e5b..9c59a90dfeb 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -710,36 +710,39 @@ defmodule String do end @doc ~S""" - Counts the number of occurrences of a `pattern` in a `string`. + Counts the number of non-overlapping occurrences of a `pattern` in a `string`. ## Examples - iex> String.count("hello world", "o") + iex> String.count_occurrences("hello world", "o") 2 - iex> String.count("hello world", "l") + iex> String.count_occurrences("hello world", "l") 3 - iex> String.count("hello world", "x") + iex> String.count_occurrences("hello world", "x") 0 - iex> String.count("hello world", ~r/o/) + iex> String.count_occurrences("hello world", ~r/o/) 2 + iex> String.count_occurrences("Hellooo", "oo") + 1 + The `pattern` can also be a compiled pattern: iex> pattern = :binary.compile_pattern([" ", "!"]) - iex> String.count("foo bar baz!!", pattern) + iex> String.count_occurrences("foo bar baz!!", pattern) 4 """ - @spec count(t, pattern | Regex.t()) :: non_neg_integer + @spec count_occurrences(t, pattern | Regex.t()) :: non_neg_integer @doc since: "1.19.0" - def count(string, pattern) when is_struct(pattern, Regex) do + def count_occurrences(string, pattern) when is_struct(pattern, Regex) do Kernel.length(Regex.scan(pattern, string, return: :index)) end - def count(string, pattern) do + def count_occurrences(string, pattern) do Kernel.length(:binary.matches(string, pattern)) end From ce86edbfb7081bb313f9ad56d0f4391de510a4bb Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Thu, 24 Apr 2025 14:03:05 +0200 Subject: [PATCH 7/8] count_matches, handle empty case --- lib/elixir/lib/string.ex | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 9c59a90dfeb..8cd15dd0bf3 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -712,37 +712,45 @@ defmodule String do @doc ~S""" Counts the number of non-overlapping occurrences of a `pattern` in a `string`. + In case the pattern is an empty string, the function returns 1 + the number of graphemes + in the string. + ## Examples - iex> String.count_occurrences("hello world", "o") + iex> String.count_matches("hello world", "o") 2 - iex> String.count_occurrences("hello world", "l") + iex> String.count_matches("hello world", "l") 3 - iex> String.count_occurrences("hello world", "x") + iex> String.count_matches("hello world", "x") 0 - iex> String.count_occurrences("hello world", ~r/o/) + iex> String.count_matches("hello world", ~r/o/) 2 - iex> String.count_occurrences("Hellooo", "oo") + iex> String.count_matches("Hellooo", "oo") 1 + iex> String.count_matches("hello world", "") + 12 + The `pattern` can also be a compiled pattern: iex> pattern = :binary.compile_pattern([" ", "!"]) - iex> String.count_occurrences("foo bar baz!!", pattern) + iex> String.count_matches("foo bar baz!!", pattern) 4 """ - @spec count_occurrences(t, pattern | Regex.t()) :: non_neg_integer + @spec count_matches(t, pattern | Regex.t()) :: non_neg_integer @doc since: "1.19.0" - def count_occurrences(string, pattern) when is_struct(pattern, Regex) do + def count_matches(string, <<>>), do: length(string) + 1 + + def count_matches(string, pattern) when is_struct(pattern, Regex) do Kernel.length(Regex.scan(pattern, string, return: :index)) end - def count_occurrences(string, pattern) do + def count_matches(string, pattern) do Kernel.length(:binary.matches(string, pattern)) end From e471b4cd04661d8f0ea3d637f777cfa9885754c6 Mon Sep 17 00:00:00 2001 From: Steffen Deusch Date: Fri, 25 Apr 2025 15:25:51 +0200 Subject: [PATCH 8/8] count_matches -> count --- lib/elixir/lib/string.ex | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex index 8cd15dd0bf3..4b42cce9aad 100644 --- a/lib/elixir/lib/string.ex +++ b/lib/elixir/lib/string.ex @@ -717,40 +717,40 @@ defmodule String do ## Examples - iex> String.count_matches("hello world", "o") + iex> String.count("hello world", "o") 2 - iex> String.count_matches("hello world", "l") + iex> String.count("hello world", "l") 3 - iex> String.count_matches("hello world", "x") + iex> String.count("hello world", "x") 0 - iex> String.count_matches("hello world", ~r/o/) + iex> String.count("hello world", ~r/o/) 2 - iex> String.count_matches("Hellooo", "oo") + iex> String.count("Hellooo", "oo") 1 - iex> String.count_matches("hello world", "") + iex> String.count("hello world", "") 12 The `pattern` can also be a compiled pattern: iex> pattern = :binary.compile_pattern([" ", "!"]) - iex> String.count_matches("foo bar baz!!", pattern) + iex> String.count("foo bar baz!!", pattern) 4 """ - @spec count_matches(t, pattern | Regex.t()) :: non_neg_integer + @spec count(t, pattern | Regex.t()) :: non_neg_integer @doc since: "1.19.0" - def count_matches(string, <<>>), do: length(string) + 1 + def count(string, <<>>), do: length(string) + 1 - def count_matches(string, pattern) when is_struct(pattern, Regex) do + def count(string, pattern) when is_struct(pattern, Regex) do Kernel.length(Regex.scan(pattern, string, return: :index)) end - def count_matches(string, pattern) do + def count(string, pattern) do Kernel.length(:binary.matches(string, pattern)) end