12

Is there a way to perform a simultaneous find and replace?

e.g.: I want to replace all 1 for 0 and simultaneously all 0 to 1

[
    [1, 1, 1, 1, 1, 0],
    [1, 1, 1, 1, 0, 1],
    [1, 1, 1, 0, 1, 1],
    [1, 1, 0, 1, 1, 1],
    [1, 0, 1, 1, 1, 1],
    [0, 1, 1, 1, 1, 1],
    [1, 1, 1, 1, 1, 0],
]

10 Answers 10

18

Yes, you can do that by using an expression. For this particular case, you can use Vim arithmetic and calculate 1-x, which will turn 0 into 1 and 1 into 0.

:s/\d/\=1-submatch(0)/g

For a more general replacement case, you can use a Dict to store the mapping of match to replacement string.

For example, to turn the digits into written numbers:

:let repl = {"0": "zero", "1": "one"}
:s/\d/\=repl[submatch(0)]/g

See:

1
  • 1
    I love the elegance of the first solution. And the dict mapping in your second solution is a handy technique to know! Thanks!
    – Aman
    Jul 25, 2022 at 2:03
17

There's a bunch of excellent answers here already, but for the sake of completeness I feel like I should point out that, in most practical respects, if you concatenate multiple ex commands with the bar character |, they act like a single operation.

In particular, they will lead to a single item in each of the undo tree and changelist.

As such, the following acts like a simultaneous change of 1 to 0 and 0 to 1:

:%s/1/TEMP/g | %s/0/1/g | %s/TEMP/0/g
1
  • 6
    I like these kind of solutions; the other ones are more clever and arguably better, but this one is easier to remember and has lower cognitive load. Sometimes I spend more time getting the "clever" method to work than I save by just using the "dumb" method. Maybe I'm just not smart enough heh Apr 30, 2020 at 18:15
8

In your specific use case, i think what you can do is this handy oneline:

%s/0\|1/\=!submatch(0)/g

That basically inverts the match.

7

Sure:

%s/[01]/\="10"[submatch(0)]/g

Matches either a 0 or 1 and replaces it with either

"10"[0] = 1

or

"10"[1] = 0

7

This answer is longer than others, it didn't take the specific data value advantage, it's a general solution to replace n different values with n different replacements:

let d={0:1,1:0} | %s/\v(0|1)/\=d[submatch(0)]/g | unlet d

So if you want to change Hello vim 8 to Oh god 9 in a single replacement:

let d={'Hello':'Oh','vim':'god',8:9} | %s/\v(Hello|vim|8)/\=d[submatch(0)]/g | unlet d
6

I'm actually surprised there is no native tr in vi/vim, but if your platform is UNIXy enough, there's an external command for just this:

:%!tr 01 10

This is what I would personally use, but it may not be portable enough for you.

2
5

I'm quite certain I've already gave an answer to the general case either here on vi.SE, or on stackoverflow, but impossible to find the Q/A...

In that answer I provide the command CycleSubstitute that could be used in your case in the following way

:[range]CycleSubstitute/0/1

I've packaged it here. And it's implemented in the following way:

:command! -bang -nargs=1 -range CycleSubstitute <line1>,<line2>call s:CycleSubstitute("<bang>", <f-args>)

function! s:CycleSubstitute(bang, repl_arg) range
  let do_loop = a:bang != "!"
  let sep = a:repl_arg[0]
  let fields = split(a:repl_arg, sep)
  let cleansed_fields = map(copy(fields), 'substitute(v:val, "\\\\[<>]", "", "g")')
  " build the action to execute
  let action = '\=s:DoCycleSubst('.do_loop.',' . string(cleansed_fields) . ', "^".submatch(0)."$")'
  " prepare the :substitute command
  let args = [join(fields, '\|'), action ]
  let cmd = a:firstline . ',' . a:lastline . 's'
        \. sep . join(fields, '\|')
        \. sep . action
        \. sep . 'g'
  " echom cmd
  " and run it
  exe cmd
endfunction

function! s:DoCycleSubst(do_loop, fields, what)
  let idx = (match(a:fields, a:what) + 1) % len(a:fields)
  return a:fields[idx]
endfunction
5

Similar to @LucHermitte's answer, the SwapStrings plugin lets you do

:%SwapStrings 0 1

My own PatternsOnText plugin provides (among many others) a :SubstituteMultiple command that can do swaps as a corner case of doing multiple substitutions atomically (that is, without introducing a temp replacement):

:%SubstituteMultiple /0/1/ /1/0/ g
2

To add to the list of plugins allowing this, vim-abolish features the Subvert function which can be leveraged to achieve multiple replaces:

%Subvert/{1,0}/{0,1}/g
1
:%s/\([01]\)/\=1-submatch(1)/g

The following is taking place in the substitute command

  • Search for 1 or 0, remember it as submatch 1 (between the first / and second /)
  • Replace with the value by subtracting from 1 (between the second / and third /)
  • 1 becomes 0 and 0 becomes 1

\= is interpreted as the beginning of a mathematical expression in the substituted section

1
  • 2
    Welcome to Vi and Vim! Good answers usually provide an explanation alongside code in order to be instructive
    – D. Ben Knoble
    May 1, 2020 at 1:29

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.