2

I have a list of strings which is interesting in its own right, but I also want a filtered version of it for other purposes. But I discovered to my horror that filter, in addition to returning a filtered version of the list passed in, also changes the list passed in. Even copying it over apparently doesn't work. Try these directly in command-mode:

let l=[2,3]
let m=l
let o=filter(m,'v:val!=2')
echo l

All three lists are modified by this operation. Can we write a fixed filter version that does what we want? E.g.

function realfilter(list, predicate) abort
  return filter(list, predicate) " this still has the same problem
endfunction
1
  • 1
    m=l doesn't make a copy of the list it only copies the reference to it. Copy with copy() or deepcopy() depending on whether you want shallow or deep copy.
    – B Layer
    Commented Mar 12, 2021 at 11:29

3 Answers 3

4

You just need to copy your List properly.

Think of a List variable like l as a reference to the List object that has been assigned to it. (IOW, it contains the the memory address of the assigned List not the List itself.)

When you do...

let m=l

All you've done is assign the reference to another variable. So m now points to the exact same list. Modify m[1] and you modify l[1]...they're the same thing.

To make an actual copy of a List use one of the two functions copy() or deepcopy(). You can read the docs for details on how they differ but essentially you'd use the latter if your List contains other Lists (or Dictionaries) and you want to make copies of them, too.

Depending on which you need, replace let m=l with let m=copy(l) or let m=deepcopy(l).

Here's an example to illustrate the difference between shallow and deep copying:

let l = [1, ['a', 'b'], 2]
let sc = copy(l)
let dc = deepcopy(l)
let l[2] = 3
let l[1][0] = 'z'

Then...

echo sc[2] dc[2]
echo sc[1][0] dc[1][0]

...will display...

2 2
z a
4

The help is always your friend: :h filter() says (scroll down a few lines):

The operation is done in-place.  If you want a List or
Dictionary to remain unmodified make a copy first:  
    :let l = filter(copy(mylist), 'v:val =~ "KEEP"')

So as B Layer said in comments, have a look at :h copy() and :h deepcopy().

And if you are not familiar with the documentation system, you should read How do I navigate to topics in Vim's documentation?

2
  • I tried capital K on my call to copy, but that was not helpful at all; it told me about the copy command. Same for filter. Thanks for this. Commented Mar 12, 2021 at 11:54
  • 1
    @TamaMcGlinn Add () when you search the help for functions, : for commands, and so on. Commented Mar 12, 2021 at 12:56
3

For the sake of completeness (as copy() and deppcopy() work very well), as in Python we can use [:]. e.g.

let li = [1, 2, 3]
echo filter(li[:], 'v:val % 2')
echo map(li[:], 'v:val * 2')
echo li
2
  • TIL! That's very good to know! :h sublist
    – statox
    Commented Mar 12, 2021 at 13:23
  • 1
    @statox Actually, I've realized on the spot as I was thinking back about a map(list[1:],... I've wrote some time back, and that went into my code-under-maintenance-window yesterday :D Commented Mar 12, 2021 at 13:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.