我想确定源文件中使用的标签宽度用空格缩进。 这对于具有特别规则缩进的文件并不难,其中前导空格仅用于缩进,始终是制表符宽度的倍数,并且缩进在时间上增加一个级别。 但是许多文件会偏离这种常规缩进,通常用于某种形式的垂直对齐。因此,我正在寻找一种良好的启发式算法,以估计使用的标签宽度,允许一些不规则压痕的可能性。
这样做的动机是为SubEthaEdit编辑器编写扩展。不幸的是,SubEthaEdit没有使标签宽度可用于编写脚本,所以我将根据文本猜测它。
合适的启发式应该:
一些简化因素:
你会采取什么方法,你认为它的优点和缺点是什么?
如果要在答案中提供工作代码,最好的方法可能是使用从stdin
读取源文件的shell脚本,并将选项卡宽度写入stdout
。伪代码或单词中的清晰描述也会很好。
某些结果
为了测试不同的策略,我们可以对语言分发的标准库中的文件应用不同的策略,因为它们可能遵循语言的标准缩进。我将考虑Python 2.7和Ruby 1.8库(系统框架安装在Mac OS X 10.7上),它们的预期宽度分别为4和2。排除的是具有以制表符开头的行或者没有以至少两个空格开头的行的那些文件。
的Python:
Right None Wrong
Mode: 2523 1 102
First: 2169 1 456
No-long (12): 2529 9 88
No-long (8): 2535 16 75
LR (changes): 2509 1 116
LR (indent): 1533 1 1092
Doublecheck (10): 2480 15 130
Doublecheck (20): 2509 15 101
红宝石:
Right None Wrong
Mode: 594 29 51
First: 578 0 54
No-long (12): 595 29 50
No-long (8): 597 29 48
LR (changes): 585 0 47
LR (indent): 496 0 136
Doublecheck (10): 610 0 22
Doublecheck (20): 609 0 23
在这些表中,“Right”应作为语言标准制表符宽度的确定,“错误”作为非零标签宽度不等于语言标准宽度,“无”作为零标签 - 宽度还是没有答案。 “模式”是选择最常出现的缩进变化的策略; “第一个”是第一个缩进线的缩进; “No-long”是FastA的排除大缩进线并采用该模式的策略,其中数字表示允许的最大缩进变化; “LR”是Patrick87基于线性回归的策略,其变体基于线之间的压痕变化和线的绝对压痕; “Doublecheck”(无法抗拒双关语!)是Mark对FastAl策略的修改,限制了可能的标签宽度,并检查模态值的一半是否也经常发生,有两个不同的阈值来选择较小的宽度。
答案 0 :(得分:3)
我打开VB.Net(不是吗?:-)这就是我的意思:
Sub Main()
Dim lines = IO.File.ReadAllLines("ProveGodExists.c")
Dim previndent As Integer = 0
Dim indent As Integer
Dim diff As Integer
Dim Diffs As New Dictionary(Of Integer, Integer)
For Each line In lines
previndent = indent
indent = Len(line) - Len(LTrim(line))
diff = indent - previndent
If diff > 0 And diff < 13 Then
If Diffs.ContainsKey(diff) Then
Diffs(diff) += 1
Else
Diffs.Add(diff, 1)
End If
End If
Next
Dim freqtbl = From p In Diffs Order By p.Value Descending
Console.WriteLine("Dump of frequency table:")
For Each item In freqtbl
Console.WriteLine(item.Key.ToString & " " & item.Value.ToString)
Next
Console.WriteLine("My wild guess at tab setting: " & freqtbl(0).Key.ToString)
Console.ReadLine()
End Sub
结果:
频率表转储:
4 748
8 22
12 12
2 2
9 2
3 1
6 1
我对标签设置的狂热猜测:4
希望有所帮助。
答案 1 :(得分:3)
好的,因为你想要一个与语言无关的解决方案,我们将无法使用任何语法提示。虽然你说,你不想要一个完美的解决方案,但这里有一个,这对大多数语言都很有效。
我实际上必须解决密码学中的类似问题,才能在polyalphabetic cipher中获得正确的代码字长。这种加密是基本的Caesar-chiffre(字母表的每个字母都由 n 字母移动),其中密码用于以不同方式移动字母( nth 明文的字母由 mod(密码的第n,长度(密码))字母移动。选择的武器是autocorrelation。
算法如下:
自相关是一个非常好的函数,可用于您想要检测数据流中的重复值的每种情况。它大量用于信号处理并且速度非常快(取决于估计的信号重复的最大距离)。
是的,那时我用自相关破解了多字母密文。 ;)
答案 2 :(得分:1)
对于您想要支持的每个语言,您需要进行一些解析:
1)排除评论(按行或按块,也可以嵌套?)
2)找到子块的开头({
在C语言中,begin
在pascal中,do
在shell等中。)
然后看看在打开子块之后空间的数量增加了多少。做一些简单的统计 - 找出最常见的值,最大值和最小值,平均值。通过这种方式,您还可以查看缩进是否正常以及缩进程度。
答案 3 :(得分:1)
也许做点什么......
示例:
答案 4 :(得分:1)
您的选择(实际上)是2,3,4,5,6,7,8。
我会使用@FastAl建议的东西扫描前50-100行左右。我可能只是盲目地从文本的任何行的前面盲目地拉空间数并计算空白字符串的长度。如果你有正则表达式,左边的修剪线和两次运行长度似乎是浪费。另外,我会System.Math.abs(indent - previndent)
,所以你得到去缩进数据。正则表达式是这样的:
row.matches('^( +)[^ ]') # grab all the spaces from line start to non-space.
一旦你得到了7个选项中哪个选项具有最高计数的统计数据,请将其作为第一个猜测运行。对于8,6和4你应该检查4和2,3或2是否也有重要的计数(第二位或超过10%或其他一些廉价启发式)。如果有很多12(或9s)可能暗示4(或3)也是比8(或6)更好的选择。一次删除或添加超过2个级别(通常是折叠的结尾括号)是非常罕见的。
不相关的笨拙
我看到的一个问题是,旧的.c代码特别具有这种令人讨厌的模式:
code level 0
/* Fancy comments get weird spacing because there
* is an extra space beyond the *
* looks like one space!
*/
code indent (2 spaces)
/* Fancy comments get weird spacing because there
* is an extra space beyond the *
* looks like three spaces!
*/
code level 0
code indent (2 spaces)
/* comment at indent level 1
With no stars you wind up with 2 spaces + 3 spaces.
*/
呸。我不知道你是如何处理这样的评论标准的。对于“c”代码,您可能需要处理2.0版中的特殊注释...但我暂时忽略它。
您的最后一个问题是处理与您的假设不符的行。我的建议是将它们“标记”到深度,然后留出额外的空间。如果你必须纠正我会这样做:rowtabdepth = ceiling((rowspacecount - (tabwidth/2)) / tabwidth)
答案 5 :(得分:0)
作为基线,可以简单地计算所有缩进增量,并将最频繁的增加作为标签宽度。作为一个shell脚本,编写为每个管道阶段都有小动作,它可能如下所示:
#!/bin/sh
grep -v -E '^[[:space:]]*$' |
sed 's/^\([[:space:]]*\).*/\1/' |
awk '{ print length($0) }' |
awk '$1 > prev { print $1 - prev } { prev = $1 }' |
sort |
uniq -c |
sort -k1nr |
awk '{ print $2 }' |
head -n 1
此实现为O(n log(n))
,其中n
是文件中的行数,但可以在O(n)
中轻松完成。
答案 6 :(得分:0)
启发式:
Python脚本,获取文件名或stdin并打印最佳缩进号:
#!/usr/bin/env python
import fileinput, collections
def leadingSpaceLen(line):
return len(line) - len(line.lstrip())
def indentChange(line1, line2):
return leadingSpaceLen(line2) - leadingSpaceLen(line1)
def indentChanges(lines):
return [indentChange(line1, line2)
for line1, line2 in zip(lines[:-1], lines[1:])]
def bestIndent(lines):
f = collections.defaultdict(lambda: 0)
for change in indentChanges(lines):
if change > 0:
f[change] += 1
return max(f.items(), key=lambda x: x[1])[0]
if __name__ == '__main__':
print bestIndent(tuple(fileinput.input()))