Hello everyone, I’ve been wondering if it is possible to use a guard that can reenforce a clause in order to verify if a list is inside another list (order does’t matter). Something like:
def my_function(list_a, list_b) when list_a in list_b, do: "something..."
Obviously, this will not work because it will check if the first list is a single element from the second list. The expected output could be something similar to Enum.all?/2. Thanks for any suggestions
Simply put, you cannot. When I found myself in a similar situation, I did something very clumsy like this (this assumes that the first list a is the smaller one whose elements must all be in b):
def my_function(a, b) when is_list(a) and is_list(b) do
case list_is_in_list?(a, b) do
true -> proceed_with_computation()
false -> raise(ArgumentError, "first list must have all its element in the second list")
end
end
def list_is_in_list?(a, b) when is_list(a) and is_list(b) do
common_elements = MapSet.intersection(MapSet.new(b), MapSet.new(a))
Enum.sort(common_elements) == Enum.sort(a)
end
I’d be tempted to check length(common_elements) == length(a) for the final condition. Am I missing some corner case that makes the sort and explicit element by element equality check necessary?
If order doesn’t matter would this be just as good?
def my_function(a, b) when is_list(a) and is_list(b) do
case one_is_a_subset?(MapSet.new(a), MapSet.new(b)) do
true -> proceed_with_computation()
false -> raise(ArgumentError, "first list must have all its element in the second list")
end
end
def one_is_a_subset?(%MapSet{} = a, %MapSet{} = b) do
MapSet.subset?(a,b) || MapSet.subset?(b,a)
end
I don’t think the “||” is even necessary in that case:
def my_function(a, b) when is_list(a) and is_list(b) do
case Map.subset?(MapSet.new(a), MapSet.new(b)) do
true -> proceed_with_computation()
false -> raise(ArgumentError, "first list must have all its element in the second list")
end
end
No you are not, I simply didn’t do the length part since I assumed the OP wants to ignore order of elements but wants to check for a sub-list inside the bigger list.
Thanks for the answer, I was definitely expecting a “no” as an answer when it comes to guards or some sort of macro. Thanks to everyone that checked my question, I appreciate it.
Certainly, just figured that it was worth keeping in mind since we don’t know op’s needs. I remember searching for something like that back in the days myself, I would definitely have been happy with the suggestions.
Even if your scenario is achievable by macros I’d strongly advise against it – if the code you end up writing is derived from mine, and it is a bit verbose I admit, you’ll still have much better code readability and the intent of your function will be laid out plainly for everyone to comprehend.
It is tempting to make your own DSLs (Domain-Specific Languages) but do have in mind that it takes effort and hopping through files to decipher what do they end up doing. If your need is as simple as your question here, always go for inline code.
I might catch flak for this opinion but IMO this is one of the top reasons why LISP languages aren’t ruling the world: namely that many project authors end up crafting a perfect DSL for the needs of the project but then nobody except for them can maintain or extend the project.
Indeed, however I was wondering if I could implement some sort of guard or macro for this cause of checking if a list was inside another list (I’m still not very fluent with metaprogramming in Elixir). Perhaps a silly problem with a very direct solution, however I guess I wanted to make sure I wasn’t missing something related to guards. Thanks for the comment, I appreciate it.
defmodule L do
def list_in_list?([] = _sublist, _superlist), do: true
def list_in_list?([_|_] = _sublist, [] = _superlist), do: false
def list_in_list?([h|t] = _sublist, superlist) do
case List.delete(superlist, h) do
^superlist -> false
new_superlist -> list_in_list?(t, new_superlist)
end
end
end