问题描述
我想动态定义一个Slice
,它可以基于前向索引或后向索引(取决于其起始位置是正数还是负数)。
我正在尝试https://play.nim-lang.org/
我尝试了以下联合类型:
type mySlice = Slice[BackwardsIndex] | Slice[int]
var sl: mySlice
let s = "1234567890"
let bcStart = 3
let bcLen = 3
if bcLen < 0:
sl = (bcStart-1)..<(bcStart+bcLen-1)
else:
sl = ^(bcStart+bcLen-1)..^(bcStart)
echo s[sl]
此操作失败,显示为/usercode/in.nim(2,5) Error: invalid type: 'mySlice' for var
。
我尝试过
let s = "1234567890"
let bcStart = 3
let bcLen = 3
if bcLen < 0:
let sl = (bcStart-1)..<(bcStart+bcLen-1)
else:
let sl = ^(bcStart+bcLen-1)..^(bcStart)
echo s[sl]
这失败,如下所示:
/usercode/in.nim(5,7) Hint: 'sl' is declared but not used [XDeclaredButNotUsed]
/usercode/in.nim(7,7) Hint: 'sl' is declared but not used [XDeclaredButNotUsed]
/usercode/in.nim(8,8) Error: undeclared identifier: 'sl'
我还尝试了以下方法:
let s = "1234567890"
let bcStart = 3
let bcLen = 3
let sl =
if bcLen < 0:
(bcStart-1)..<(bcStart+bcLen-1)
else:
^(bcStart+bcLen-1)..^(bcStart)
echo s[sl]
还有另一种失败的方式:
/usercode/in.nim(8,23) Error: type mismatch: got <HSlice[system.BackwardsIndex,system.BackwardsIndex]> but expected 'HSlice[system.int,system.int]'
为什么会失败,我该怎么办?
编辑(09/09/2020)所需的API
我的用例比这要复杂得多,但是它相当于一个命令行程序,该程序以输入文本,“条形码”和条形码的起始位置为参数,并告诉输入中是否存在条形码。文字在指定位置。如果位置为负整数,则表示我们从末尾开始指定位置。
我的工作正常:
$ cat src/test.nim
import docopt
from strutils import parseInt
# https://github.com/docopt/docopt.nim
const doc = """
Usage:
test -t <input_text> -b <barcode> -s <barcode_start>
-h --help Show this help message and exit.
-t --input_text <input_text> Text in which to search for the barcode.
-b --barcode <barcode> Barcode to search.
-s --barcode_start <barcode_start> Position at which the barcode starts (1-based),negative if from end.
"""
proc match_text(inText: string,barcode: string,bcStart: int): bool =
var
bcSeq: string
bcLen: int = barcode.len
if bcStart < 0:
bcSeq = inText[^(bcLen - bcStart - 1)..^(-bcStart)]
else:
bcSeq = inText[(bcStart-1)..<(bcStart + bcLen - 1)]
if bcSeq == barcode:
result = true
else:
result = false
when isMainModule:
let args = docopt(doc)
var
barcode: string
inText: string
bcStart: int
for opt,val in args.pairs():
case opt
of "-t","--input_text":
inText = $args[opt]
of "-b","--barcode":
barcode = $args[opt]
of "-s","--barcode_start":
bcStart = parseInt($val)
else:
echo "UnkNown option" & opt
quit(QuitFailure)
if match_text(inText,barcode,bcStart):
echo "Matches"
else:
echo "Doesn't match"
建筑工程:
$ nimble build
# [successful build output]
测试工作
$ ./bin/test -t aacgttb -b aa -s 1
Matches
$ ./bin/test -t aacgttb -b aa -s 2
Doesn't match
$ ./bin/test -t aacgttb -b tt -s -1
Doesn't match
$ ./bin/test -t aacgttb -b tt -s -2
Matches
但是,在我的实际应用程序中,我在不同的文本段中多次使用了相同的切片,因此我想定义一个Slice
对象,我可以将其重复使用重复计算“就地”切片的过程。
解决方法
所有问题均与您的类型为Type Class的事实有关。这是伪类型,只能在编译时用作proc重载(或is
运算符)的参数。特别是,它不能分配给var
(您报告的第一个错误),并且不能在运行时动态使用。
您收到的其他2个错误是由于1)s1
没有在if范围之外定义的事实。 2)编译器希望为s1
使用唯一类型的事实(它首先从if推断类型,然后为else子句强制执行)。
Object variants(也是Sum类型,Nim中的代数数据类型;术语Union Type在Nim中不经常使用)通常是在Nim中实现动态类型的最直接方法(经典示例是JsonNode)
编辑:在所需的API上
由于重点在于“ Slice”的可重用性和性能改进,因此可以使用以下内容(也在此处:https://play.nim-lang.org/#ix=2wXp):
type myPattern = object
barcode: string
start: int
isBackwards: bool
proc initMyPattern(barcode: string,bcStart: int): myPattern =
# no need to have a new variable for barcode.len since it is already available (not computed) for a string
# also no need to precompute last index of slice because it will not be used
if bcStart < 0:
myPattern(barcode: barcode,start: barcode.len - bcStart - 1,isBackwards: true)
else:
myPattern(barcode: barcode,start: bcStart - 1,isBackwards: false)
proc startIndex(inText: string,p: myPattern): int =
if p.isBackwards:
# this cannot be precomputed if len of inText is variable
inText.len - p.start
else:
p.start
proc match(inText: string,p: myPattern): bool =
var
i = startIndex(inText,p)
j = 0
# case where inText is not long enough to match
if i + p.barcode.len - 1 >= inText.len:
return false
# instead of computing the slice of inText (which allocates a new string),we directly iterate over indices
while j < p.barcode.len:
if p.barcode[j] != inText[i]:
return false
inc i
inc j
return true
assert "aacgttb".match initMyPattern("aa",1)
assert not "aacgttb".match initMyPattern("aa",2)
assert not "aacgttb".match initMyPattern("tt",-1)
assert "aacgttb".match initMyPattern("tt",-2)
assert not "aacgttb".match initMyPattern("ttbb",-2)
echo "tests successful"
备注:
- 我认为固定的
barcode_start
和barcode
需要针对不同的文本(可能是可变长度)进行多次匹配 - 最好避免计算字符串的“切片”,因为它会分配新的字符串(请参见here)。我怀疑这比启动索引的预计算有更大的性能改进。
- 根据前两点,在多次应用匹配之前要“编译”的对象实际上不是切片(因此名称为myPattern)
表达式
let sl = if (bcLen >0): bcLen else: BackwardsIndex(bcLen)#Error: type mismatch!
无法以静态类型的语言进行编译,因此您需要使用继承或Variant框sl
,然后在生成切片时再次取消装箱。您可能会这样:
type
PosOrNegKind = enum
Pos,Neg
PosOrNeg = object
case kind:PosOrNegKind
of Pos: posVal:int
of Neg: negVal:int
mySlice = object
beg,fin:PosOrNeg
proc `[]`(str:string,sl:mySlice):string =
let beg = case sl.beg.kind
of Pos: sl.beg.posVal
of Neg: len(str) + sl.beg.negVal
let fin = case sl.fin.kind
of Pos: sl.fin.posVal
of Neg: len(str) + sl.fin.negVal
str[beg .. fin]
proc posOrNeg(x:int):PosOrNeg =
if (x >= 0): PosOrNeg(kind: Pos,posVal: x)
else: PosOrNeg(kind: Neg,negVal: x)
proc createSlice(beg,fin:int):mySlice =
result.beg = posOrNeg(beg)
result.fin = posOrNeg(fin)
let sl = createSlice(3,-3)
echo s[sl]# "34567"
但是对于这种特殊的用例,您在值本身中具有自然的区分符(无论int是正数还是负数),因此您可以这样做:
type
MySlice = object
a,b:int
proc `--`(a,b:int):MySlice = MySlice(a: a,b: b)
proc `[]`(s:string,m:MySlice):string =
var beg = if (m.a < 0): s.len + m.a else: m.a
var fin = if (m.b < 0): s.len + m.b else: m.b
#safety checks
if fin < beg: return ""
if fin >= s.len: fin = s.len - 1
if beg < 0: beg = 0
s[beg..fin]
echo s[3 -- 5] # "345"
echo s[3 -- -2] # "345678"
echo s[-5 -- 9] # "56789"
echo s[-8 -- -2] # "2345678"
echo s[-1 -- 1] # ""
修改 您希望能够传递可以在不同输入字符串上使用的Slice。这就是上面的样子:
#fixing off-by-one errors left as an exercise for the reader
proc make_slice(barcode:string,bcStart:int):mySlice=
let bcLen = barcode.len
if bcStart < 0:
(bcStart - bcLen) -- bcStart
else:
bcStart -- (bcStart + bcLen)
let sl = make_slice("abaca",-3)
for inText in @["abacus","abacadacaba","abracadabra"]:
if inText[sl] == barcode:
echo "matches"