From 3d90896472391c05cd5f60c497891f88f4668d3a Mon Sep 17 00:00:00 2001 From: sabiwara Date: Fri, 9 Aug 2024 10:17:34 +0900 Subject: [PATCH 1/2] Add List.ends_with?/2 --- CHANGELOG.md | 1 + lib/elixir/lib/list.ex | 38 +++++++++++++++++++++++++++ lib/elixir/test/elixir/list_test.exs | 39 ++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 235f36f6fdc..3ca40239939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This release no longer supports WERL (a graphical user interface for the Erlang * [Exception] Add `MissingApplicationsError` exception to denote missing applications * [Kernel] Update source code parsing to match [UTS #55](https://www.unicode.org/reports/tr55/) latest recommendations. In particular, mixed script is allowed in identifiers as long as they are separate by underscores (`_`), such as `http_сервер`. Previously allowed highly restrictive identifiers, which mixed Latin and other scripts, such as the japanese word for t-shirt, `Tシャツ`, now require the underscore as well * [Kernel] Warn on bidirectional confusability in identifiers + * [List] Add `List.ends_with?/2` * [Macro] Improve `dbg` handling of `if/2`, `unless/2`, and code blocks * [Process] Handle arbitrarily high integer values in `Process.sleep/1` * [String] Inspect special whitespace and zero-width characters using their Unicode representation diff --git a/lib/elixir/lib/list.ex b/lib/elixir/lib/list.ex index cc396213ef5..4bb6f621164 100644 --- a/lib/elixir/lib/list.ex +++ b/lib/elixir/lib/list.ex @@ -908,6 +908,44 @@ defmodule List do def starts_with?(list, []) when is_list(list), do: true def starts_with?(list, [_ | _]) when is_list(list), do: false + @doc """ + Returns `true` if `list` ends with the given `suffix` list, otherwise returns `false`. + + If `suffix` is an empty list, it returns `true`. + + ### Examples + + iex> List.ends_with?([1, 2, 3], [2, 3]) + true + + iex> List.ends_with?([1, 2], [1, 2, 3]) + false + + iex> List.ends_with?([:alpha], []) + true + + iex> List.ends_with?([], [:alpha]) + false + + """ + @doc since: "1.18.0" + @spec ends_with?(nonempty_list, nonempty_list) :: boolean + @spec ends_with?(list, []) :: true + @spec ends_with?([], nonempty_list) :: false + def ends_with?(list, suffix) do + case ends_with_offset(list, suffix) do + nil -> false + n -> :lists.nthtail(n, list) === suffix + end + end + + defp ends_with_offset([], [_ | _]), do: nil + defp ends_with_offset(rest, []), do: length(rest) + + defp ends_with_offset([_ | tail], [_ | suffix_tail]) do + ends_with_offset(tail, suffix_tail) + end + @doc """ Converts a charlist to an atom. diff --git a/lib/elixir/test/elixir/list_test.exs b/lib/elixir/test/elixir/list_test.exs index 368e0d6b9ea..20b237c98ce 100644 --- a/lib/elixir/test/elixir/list_test.exs +++ b/lib/elixir/test/elixir/list_test.exs @@ -285,6 +285,45 @@ defmodule ListTest do end end + describe "ends_with?/2" do + test "list and prefix are equal" do + assert List.ends_with?([], []) + assert List.ends_with?([1], [1]) + assert List.ends_with?([1, 2, 3], [1, 2, 3]) + end + + test "proper lists" do + refute List.ends_with?([2], [1, 2]) + assert List.ends_with?([1, 2, 3], [2, 3]) + refute List.ends_with?([2, 3, 4], [1, 2, 3, 4]) + end + + test "list is empty" do + refute List.ends_with?([], [1]) + refute List.ends_with?([], [1, 2]) + end + + test "prefix is empty" do + assert List.ends_with?([1], []) + assert List.ends_with?([1, 2], []) + assert List.ends_with?([1, 2, 3], []) + end + + test "only accepts proper lists" do + message = "no function clause matching in List.ends_with_offset/2" + + assert_raise FunctionClauseError, message, fn -> + List.ends_with?([1 | 2], [1 | 2]) + end + + message = "no function clause matching in List.ends_with_offset/2" + + assert_raise FunctionClauseError, message, fn -> + List.ends_with?([1, 2], 1) + end + end + end + test "to_string/1" do assert List.to_string([?æ, ?ß]) == "æß" assert List.to_string([?a, ?b, ?c]) == "abc" From 906a69a9e4dd60edf88cc2165586d7fdbf046ae3 Mon Sep 17 00:00:00 2001 From: sabiwara Date: Fri, 9 Aug 2024 17:02:07 +0900 Subject: [PATCH 2/2] Delegate to :lists.suffix/2 --- lib/elixir/lib/list.ex | 12 +----------- lib/elixir/test/elixir/list_test.exs | 8 ++------ 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/lib/elixir/lib/list.ex b/lib/elixir/lib/list.ex index 4bb6f621164..f8fe874bbeb 100644 --- a/lib/elixir/lib/list.ex +++ b/lib/elixir/lib/list.ex @@ -933,17 +933,7 @@ defmodule List do @spec ends_with?(list, []) :: true @spec ends_with?([], nonempty_list) :: false def ends_with?(list, suffix) do - case ends_with_offset(list, suffix) do - nil -> false - n -> :lists.nthtail(n, list) === suffix - end - end - - defp ends_with_offset([], [_ | _]), do: nil - defp ends_with_offset(rest, []), do: length(rest) - - defp ends_with_offset([_ | tail], [_ | suffix_tail]) do - ends_with_offset(tail, suffix_tail) + :lists.suffix(suffix, list) end @doc """ diff --git a/lib/elixir/test/elixir/list_test.exs b/lib/elixir/test/elixir/list_test.exs index 20b237c98ce..12676c126f1 100644 --- a/lib/elixir/test/elixir/list_test.exs +++ b/lib/elixir/test/elixir/list_test.exs @@ -310,15 +310,11 @@ defmodule ListTest do end test "only accepts proper lists" do - message = "no function clause matching in List.ends_with_offset/2" - - assert_raise FunctionClauseError, message, fn -> + assert_raise ArgumentError, ~r/not a list/, fn -> List.ends_with?([1 | 2], [1 | 2]) end - message = "no function clause matching in List.ends_with_offset/2" - - assert_raise FunctionClauseError, message, fn -> + assert_raise ArgumentError, ~r/not a list/, fn -> List.ends_with?([1, 2], 1) end end