10

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

15

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:

| improve this answer | |
14

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
| improve this answer | |
  • 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 – Martin Tournoij Apr 30 at 18:15
7

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.

| improve this answer | |
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

| improve this answer | |
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
| improve this answer | |
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.

| improve this answer | |
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
| improve this answer | |
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
| improve this answer | |
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
| improve this answer | |
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

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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