How to change an element in a list in erlang
When working with lists a all elements are often of similar datatype or meaning. You seldom see lists like ["John Doe","1970-01-01","London"]
but rather #person{name="John Doe",...}
or even {"John Doe",...}. To change a value in a record and tuple:
-record(person,{name,born,city}).
f(#person{}=P) -> P#person{city="New City"}. % record
f({_,_,_,}=Tuple) -> erlang:setelement(3,Tuple,"New City"). % tuple
This might not solve anything for your particular problem. To take your own example in comment:
f1([H1,H2,_H3,H4,H5],E) -> [H1,H2,E,H4,H5].
If you give a more specific description of the environment and problem it's easier to under which solution might work the best.
Edit: One (rather bad) solution 1.
replacenth(L,Index,NewValue) ->
{L1,[_|L2]} = lists:split(Index-1,L),
L1++[NewValue|L2].
1> replacenth([1,2,3,4,5],3,foo).
[1,2,foo,4,5]
Or slightly more efficient depending on the length of your lists.
replacenth(Index,Value,List) ->
replacenth(Index-1,Value,List,[],0).
replacenth(ReplaceIndex,Value,[_|List],Acc,ReplaceIndex) ->
lists:reverse(Acc)++[Value|List];
replacenth(ReplaceIndex,Value,[V|List],Acc,Index) ->
replacenth(ReplaceIndex,Value,List,[V|Acc],Index+1).
Even better is my function f1 above but maybe, just maybe the problem is still located as discussed above or here.
A list is immutable, so you can't "change" an item in a list. If you really want to replace an item at a given position, you should append the list before the element with the (changed) element and the remaining list:
1> L=[1,2,3,4,5].
[1,2,3,4,5]
2> lists:sublist(L,2) ++ [lists:nth(3,L)*100] ++ lists:nthtail(3,L).
[1,2,300,4,5]
EDIT: The scenario is a bit unusual, though... Do you have a specific problem at hand? Perhaps it can be expressed better with e.g. a lists:map?
While using functions from lists
may result in code that seems clearer it is less efficient as the list of elements before the element you want to change will be copied twice. It is more efficient to write the function yourself, and as you will probably wrap the code using lists
in a function I don't feel it will be less clear.
Instead of the code by @D.Nibon I would write the function as:
%% setnth(Index, List, NewElement) -> List.
setnth(1, [_|Rest], New) -> [New|Rest];
setnth(I, [E|Rest], New) -> [E|setnth(I-1, Rest, New)].
%% Can add following caluse if you want to be kind and allow invalid indexes.
%% I wouldn't!
%% setnth(_, [], New) -> New.
The argument order can be discussed; unfortunately the lists
module is no help here as it is inconsistent within the module. While this is not a tail-recursive function I feel that it is clearer. Also the difference in efficiency is small or non-existent so I would go with clarity. For more information about this issue see:
http://www.erlang.org/doc/efficiency_guide/myths.html#tail_recursive
http://www.erlang.org/doc/efficiency_guide/listHandling.html#id64759
For a more versatile function instead of just the new value you could pass a fun
which would be called with the old value and return the new value. In a library I would probably have both.
L = [H,H,H,H,H].
And I want to access a specified element say for example the third one and change it to E. Then if I was to use the list L again it would be
[H,H,E,H,H]
To be a real nitpicker. In Erlang, data are persistent and immutable. Once you define the L = ...
part, the L
is set in stone. You can't change it from there on out. What you can do is to create a new value and bind it to another variable, L1
say and then stop using L
. The garbage collector will then quickly make short work of L
and recycle the memory it used.
Hence, it is somewhat wrong to say that using L
again changes its contents, since that is impossible.
Another point worth mentioning is that if you use the list as if it were an array, then you can use an array (from the array
module) or use a dict (from the dict
module). It greatly enhances the speed of lookups and updates in your case. If what you do most is traversal of the list to process over all elements, however, the list is probably going to be the winner.