【问题标题】:How to efficiently interlace multiple groups of lines in Vim?如何在 Vim 中高效地交错多组行?
【发布时间】:2021-07-22 04:24:32
【问题描述】:

我正在尝试交错三组文本。例如以下文本:

a
a
a
b
b
b
c
c
c

要转化为:

a
b
c
a
b
c
a
b
c

有没有一种有效的方法来做到这一点?

【问题讨论】:

  • 这是真实世界的情况还是简化的测试用例?
  • 减少了测试用例。有三组需要交错,但每组超过 3 个。我可以纠正一个宏,但我认为可能有更好的方法 :g 和 move
  • 嗯,如果所有as 相同并且bs 相同并且cs 相同我只需手动订购第一个abc (:/b/m. , :/c/m.) 并将生成的块复制 3 次(这相当快),但您的实际需求可能更复杂。
  • 一个关闭它的宏qa:.m +38 | j<return>38k 唯一的问题是它加入了行
  • 嘿,这让我想起了this one 在这种情况下,请不要犹豫,深入研究 VimScript.. ;)

标签: vim


【解决方案1】:

在我的~/.vim 文件深处的某个地方,我有一个:Interleave 命令(附加在下面)。没有任何参数:Interleave 将像往常一样交错。使用 2 个参数,它将指定要组合在一起的数量。例如:Interleave 2 1 将从顶部取 2 行,然后从底部与 1 行交错。

现在解决你的问题

:1,/c/-1Interleave
:Interleave 2 1
  • 1,/c/-1 范围从第一行开始,到第一行上方 1 行结束,匹配字母 c。
  • :1,/c/-1Interleave 基本上将a 和b 的组交错
  • :Interleave 2 1 这次的范围是整个文件。
  • :Interleave 2 1 将混合的 a 和 b 的组与 cs 的组交错。混合比为 2 比 1。

:Interleave 代码如下。

command! -bar -nargs=* -range=% Interleave :<line1>,<line2>call Interleave(<f-args>)
fun! Interleave(...) range
  if a:0 == 0
    let x = 1
    let y = 1
  elseif a:0 == 1
    let x = a:1
    let y = a:1
  elseif a:0 == 2
    let x = a:1
    let y = a:2
  elseif a:0 > 2
    echohl WarningMsg
    echo "Argument Error: can have at most 2 arguments"
    echohl None
    return
  endif
  let i = a:firstline + x - 1
  let total = a:lastline - a:firstline + 1
  let j = total / (x + y) * x + a:firstline
  while j < a:lastline
    let range = y > 1 ? j . ',' . (j+y) : j
    silent exe range . 'move ' . i
    let i += y + x
    let j += y
  endwhile
endfun

【讨论】:

    【解决方案2】:

    这是一个“oneliner”(几乎),但您必须为每个唯一的行减去 1 重做它,在您的示例中为 2 次。也许没有用,但我认为这是一个很好的练习,可以更多地了解 VIM 中的模式。只要整行是唯一的,它就可以处理所有类型的行(例如,mno 和 mnp 是两个唯一的行)。

    首先确保这一点(并且不将/ 映射到任何东西,或该行中的任何其他东西):

    :set nowrapscan
    

    然后映射例如这些(应该是递归的,不是nnoremap):
    &lt;C-R&gt;和&lt;CR&gt;应该按字面意思输入。
    \v 在模式中意味着“非常神奇”,@! 负前瞻。 \2 使用第二个括号中的内容。

    :nmap ,. "xy$/\v^<C-R>x$<CR>:/\v^(<C-R>x)@!(.*)$\n(\2)$/m-<CR>j,.
    :nmap ,, gg,.
    

    然后尽可能多地执行,,,在您的示例中为 2 次。一个适用于所有bs,一个适用于所有cs。


    编辑:映射说明。我将使用问题中的示例,就好像它已经使用此映射运行了一次一样。
    一次运行后:

    1. a
    2. b
    3. a
    4. b
    5. a
    6. b
    7. c
    8. c
    9. c
    

    然后光标在最后一个a(第5行),当输入,,时,它首先回到第一行,然后为,.运行映射,这个映射是这样做的:

    "xy$             # yanks current line (line 1) to reg. "x" ("a")  "
    /\v^<C-R>x$<CR>  # finds next line matching reg. "x" ("a" at line 3)
    :/\v^(<C-R>x)@!(.*)$\n(\2)$/m-<CR>
    # finds next line that have a copy under it ("c" in line 7) and moves that line
    # to current line (to line 3, if no "-" #after "m" it's pasted after current line)
    # Parts in the pattern:
    - ^(<C-R>x)@!(.*)$  # matches next line that don't start with what's in reg. "x"
    - \n(\2)$           # ...and followed by newline and same line again ("c\nc")
    - m-<CR>            # inserts found line at current line (line 3)
    j                # down one line (to line 4, where second "a" now is)
    ,.               # does all again (recursive), this time finding "c" in line 8
    ...
    ,.               # gives error since there are no more repeated lines,
                     # and the "looping" breaks.
    

    【讨论】:

      【解决方案3】:

      我今晚刚刚独立遇到了这个问题。我的没有一些答案那么优雅,但我认为它更容易理解。它做了很多假设,所以有点小技巧:

      • A) 它假设有一些独特的字符(或任意字符 string) 不存在于任何行中 - 我假设下面是 @。
      • B) 它 假设您不希望在任何 a、b 或 c 部分。
      • C) 它假设您可以轻松识别 最大行长度,然后将所有行填充为该长度(例如 也许使用 %!进入 awk 等,使用 printf)

        1. 用空格填充所有行到相同的最大长度。
        2. Visual 仅选择 a 和 b 部分,然后选择 %s/$/@
        3. 阻止复制并超过 b 部分以位于 c 部分之前。
        4. 阻止将 a 部分复制并粘贴到 bc 部分之前。
        5. %s/@/\r
        6. %s/^ *//g
        7. %s/ *$//g
        8. 删除 a 和 b 部分所在的左侧行。

      【讨论】:

        【解决方案4】:

        如果你有xclip,你可以剪断线条并使用paste 将它们交错:

        1. 视觉选择一组线
        2. 输入 "+d 将它们剪切到剪贴板
        3. 视觉选择另一组线
        4. 输入!paste -d '\n' /dev/stdin &lt;(xclip -o -selection clipboard)

        【讨论】:

          【解决方案5】:

          将以下内容作为interleave.awk 放入您的路径中,使其可执行。

          #!/usr/bin/awk -f
          BEGIN { C = 2; if (ARGC > 1) C = ARGV[1]; ARGV[1]="" }
          { g = (NR - 1) % C; if (!g) print $0; else O[g] = O[g] $0 "\n" }
          END { for (i = 1; i < C; i++) printf O[i] }
          

          然后从vim 以可视模式突出显示行,然后调用:'&lt;,'&gt;!interleave.awk 3,或将 3 替换为要交错的任意数量的组(或留空为 2)。

          您要求一种有效的方法。除了解释语言,这可能是交错任意行的最有效算法 - 第一组立即打印,节省了一些 RAM。如果 RAM 非常宝贵(例如,大量行或太多行),您可以改为将偏移存储到每行的开头,并且如果行具有一致的明确定义的长度(至少在组内),您不会甚至需要存储偏移量。然而,这种方式文件只被扫描一次(允许使用标准输入),并且 CPU 可以快速复制数据块,而文件指针操作可能每个都需要上下文切换,因为它们通常必须触发系统调用。

          也许最重要的是,代码简单而简短——阅读和执行的效率通常是最重要的。

          编辑:看起来其他人也采用了相同的解决方案 - 刚刚在搜索引擎中重新构建问题时发现 https://stackoverflow.com/a/16088069/118153,看看我是否遗漏了一些明显的东西。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-12-12
            • 2010-09-08
            • 2015-12-21
            • 1970-01-01
            • 1970-01-01
            • 2022-01-23
            • 1970-01-01
            相关资源
            最近更新 更多