Using guards with Lists

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
1 Like

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
2 Likes

Ah you’re right - the order of arguments passed to MapSet.subset?/2 matters but the OP wasn’t trying to check both ways.

1 Like

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.

Your solution looks like it will have problems with duplicates, e.g. [:a, :a], [:a, :a, :b]

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.

True. I just made a few assumptions. I believe the coding samples provided are a really good start for OP to craft their final solution.

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. :smiley:

1 Like

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.

I must be missing something but couldn’t you just do

def list_is_in_list(a, b) do
  Enum.all?(a, fn x -> Enum.member?(b, x) end)
end

It mightn’t be the fastest but it is simple and easy to understand.

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.

This should work if duplicates are expected

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