Getting adjacent values from a list

Hello

What would be the best way to get adjacent values for given value inside a list. Something like:

list = [7, 3, 8, 1, 5, 9]
val = 1
[head | val | tail] = list

And now in the head I can check the last element (that would be 8), and in the tail I can check the first element (that would be 5). But that doesn’t work :wink: Any other “clever” method to get that values?

With no real check, get first matching result, or last invalid…

val = 1
Enum.reduce_while(list, {nil, nil, nil}, fn x, acc -> 
  new_acc = {elem(acc, 1), elem(acc, 2), x}
  if elem(new_acc, 1) != val, do: {:cont, new_acc}, else: {:halt, new_acc} 
end)
{8, 1, 5}

Try

  def adj([], value), do: nil
  def adj([h, adj | _], value) when h == value,  do: adj

  def adj([h | t], value) do
    adj(t, value)
  end

Another solution may be Enum.split_while/2

list = [7, 3, 8, 1, 5, 9]
val = 1
# at 1 because enumerables are 0-based
list |> Enum.split_while(fn elem -> elem != val end) |> elem(1) |> Enum.at(1)

# It's not as fast for previous element, but still easy to get:
list |> Enum.split_while(fn elem -> elem != val end) |> elem(0) |> Enum.at(-1)
list |> Enum.split_while(fn elem -> elem != val end) |> elem(0) |> List.last()
1 Like

Thank you for all the answers! But I think in the end I will go with:

list = [7, 3, 8, 1, 5, 9]
val = 9

index = Enum.find_index(list, fn e -> e == val end)
case index do
  0 ->
    {nil, Enum.at(list, index + 1)}
  _ ->
    {Enum.at(list, index - 1), Enum.at(list, index + 1)}
end

Mainly because it is more readable to me.

Sorry I misunderstood your request.
The recursive function above finds the right adjacent value.

This one below finds the 2 adjacent:

  def adjs([], _), do: []
  def adjs([val, right | t], value) when val == value, do: right
  def adjs([left, val, right | t], value) when val == value, do: {left, right}

  def adjs([_h | t], value) do
    adjs(t, value)
  end
3 Likes

This seems the best solution here if you count time, but in my case, there is db query anyway, so I don’t care that much. But this is the solution that I will go when I would consider time!

Okay.
Just to be stricter and uniform the results,
def adjs([], _), do: [] can be changed to def adjs([], _), do: nil

so the spec would be
@spec adjs(list, integer) :: nil | integer | tuple

These two clauses can be simplified (no need for guards)

  def adjs([value, right | t], value), do: right
  def adjs([left, value, right | t], value), do: {left, right}
1 Like

Also, keep in mind to return in the format of {:ok, value}, {:ok, value1, value2}, :error
otherwise if you just get {value1, value2} or [] you will never be sure if you are getting two values, or the one value you got is a two-element tuple.

Here there is no possible error. Max, it returns nil.
Also, the return value spec being either nil, or an integer or a tuple allows, via pattern matching, to distinguish the different cases.
One could do

adjs(list)
|> case  do
    nil -> :foo
    val when is_integer(val) -> :bar
    {left, right} -> :baz
end

Well, if the list only contians integers, yes. But it is never mentioned that. It talks about value, so I expect a tuple, nil, or and empty list could be a value as well.