Update the values at the index of a list based on another list

Hi,

I have a list say lt = [0,0,0,0,0]

I have another list say, lt1 = [1, 4]

Now i want to update the value of the index of lt to 1 based on the values at lt1. Can someone suggest me how to do that?

Ex: after update values of lt = [0, 1, 0, 0, 1] as lt1 = [1, 4]

@Shantanu48114860 First of all good naming would definitely help us understand your code much more, so please don’t forget about that. If I understand correctly your problem then you should simply use Enum.reduce/3 like this:

new_value = 1
values = List.duplicate(0, 5) # [0, 0, 0, 0, 0] in nicer form :-)
indexes = [1, 4]
Enum.reduce(indexes, values, &List.replace_at(&2, &1, new_value))

Following Enum.reduce/3 documentation:

@spec reduce(t(), any(), (element(), any() → any())) :: any()

Invokes fun for each element in the enumerable with the accumulator.

The initial value of the accumulator is acc. The function is invoked for each
element in the enumerable with the accumulator. The result returned by the
function is used as the accumulator for the next iteration. The function
returns the last accumulator.

In other words:

  • 1st argument is enumerable (indexes) - for each element of it we would call some function
  • 2nd argument is accumulator (values) - it’s a default value which would be changed in each function call
  • 3rd argument is function - after each call accumulator is changed and is passed again to that function until your enumerable (indexes) becomes empty.

&List.replace_at(&2, &1, new_value) is a shorthand to:
fn arg1, arg2 -> List.replace(arg2, arg1, new_value) end.

First 2 arguments are passed by Enum.reduce/3. First agument is element (here index). Second is accumulator. It’s why we are passing &2 before &1 in List.replace_at/3.

Finally here is fragment from List.replace_at/3 documentation:

Returns a list with a replaced value at the specified index.

HINT:
If you are going to delete or insert elements by indexes then please make sure you are doing it in reverse order as otherwise you would hit a conflict of indexes.

Here goes some examples:

# those variables are not going to change in all examples
new_value = 1
values = Enum.to_list(0..9)
indexes = [1, 4]

# Firstly List.delete_at/2:

# WRONG:
Enum.reduce(indexes, values, &List.delete_at(&2, &1))
# returns: [0, 2, 3, 4, 6, 7, 8, 9]

# GOOD:
indexes |> Enum.reverse() |> Enum.reduce(values, &List.delete_at(&2, &1))
# returns: [0, 2, 3, 5, 6, 7, 8, 9]

# Same goes to List.insert_at/3:

# WRONG:
Enum.reduce(indexes, values, &List.insert_at(&2, &1, new_value))
# returns: [0, 1, 1, 2, 1, 3, 4, 5, 6, 7, 8, 9]

# GOOD:
indexes |> Enum.reverse() |> Enum.reduce(values, &List.insert_at(&2, &1, new_value))
# returns: [0, 1, 1, 2, 3, 1, 4, 5, 6, 7, 8, 9]

As you can see in first example instead of 4 value we wanted to remove 5 value and in second example secondly inserted 1 value is after 2 instead of 3. It’s why order of processed indexes is important!

Helpful resources:

  1. Enum.reduce/3
  2. Enum.reverse/1
  3. List.delete_at/2
  4. List.insert_at/2
  5. List.replace_at/2
  6. Partials and function captures in Elixir
4 Likes

Starting from Eiji’s setup

new_value = 1
values = List.duplicate(0, 5) # [0, 0, 0, 0, 0] in nicer form :-)
indexes = [1, 4]

you could also do it like this:

values
|> Enum.with_index
|> Enum.map(fn {value, index} ->
     if index in indexes do
       new_value
     else
       value
     end
end)

I think it’s more explicit, albeit maybe less idiomatic? Not sure about performance, though. Looping through the values is not cheap, but neither is repeating List.insert_at/2, especially when the indexes cluster near the end of values.

2 Likes

Sorry to be a bit of a killjoy here but isn’t it easier to just write a function which steps over values and indexes and update when the index is the same. Something like

def upd(values, indexes, new) do upd(values, indexes, new, 0) end

def upd(vs, [], _new, _i) do vs end
def upd([_v|vs], [i|is], new, i) do [new|upd(vs, is, new, i+1)] end
def upd([v|vs], is, new, i) do [v|upd(vs, is, new, i+1)] end
def upd([], _is, _new, _i) do [] end

Sorry for the formatting but on my mobile. You could make it tail recursive and end with a reverse but I doubt it is worth it here.

5 Likes