6

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

a
a
a
b
b
b
c
c
c

将被转化为:

a
b
c
a
b
c
a
b
c

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

4

5 回答 5

6

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

现在来解决你的问题

:1,/c/-1Interleave
:Interleave 2 1
  • 1,/c/-1范围从第一行开始,到匹配字母的第一行上方 1 行结束c
  • :1,/c/-1Interleave基本上交错a's 和b's的组
  • :Interleave 2 1这次的范围是整个文件。
  • :Interleave 2 1a将混合's 和b's 组与s组交错c。混合比例为 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
于 2013-02-11T00:37:41.590 回答
1

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

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

:set nowrapscan

然后映射例如这些(应该是递归的,而不是 nnoremap):
<C-R>并且<CR>应该按字面意思输入。
\vin patterns 表示“非常神奇”,@!负前瞻。\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.
于 2013-02-11T14:55:56.403 回答
1

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

  • A)它假设任何行中都不存在一些唯一字符(或任意字符串) - 我假设@如下。
  • 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 部分所在的行。
于 2013-05-26T05:07:35.383 回答
1

如果你有xclip,你可以剪断线条并paste用来交错:

  1. 视觉选择一组线
  2. 键入"+d以将它们剪切到剪贴板
  3. 视觉选择另一组线
  4. 类型!paste -d '\n' /dev/stdin <(xclip -o -selection clipboard)
于 2016-10-04T21:32:51.817 回答
0

将以下内容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在可视模式下突出显示行,然后调用:'<,'>!interleave.awk 3,或将 3 替换为要交错的多个组(或留空 2)。

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

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

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

于 2021-07-21T20:10:03.827 回答