Nim:如何动态定义可以向前或向后的 Slice?
Nim: How to dynamically define a Slice that can be either forwards of backwards?
我想动态定义一个 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
我的用例比那个更复杂,但它相当于一个命令行程序,它将输入文本、“条形码”和条形码起始位置作为参数,并告诉它条形码是否存在于指定位置的输入文本。如果position给出的是负int,表示我们指定的是从末尾开始的位置。
我有一些东西按预期工作:
$ 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 (also Sum types, Algebraic Data types in Nim; terminology Union Type 在 Nim 中不常使用)通常是在 Nim 中实现动态类型最直接的方式(经典示例是 JsonNode)。
编辑:根据需要 API
由于重点是“切片”的可重用性和性能改进,因此可能会使用以下内容(也在这里: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)。我怀疑这比开始索引的预计算有更大的性能改进。
- 根据前两点,在多次应用 match 之前要“编译”的对象并不是真正的 Slice(因此得名 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"
我想动态定义一个 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
我的用例比那个更复杂,但它相当于一个命令行程序,它将输入文本、“条形码”和条形码起始位置作为参数,并告诉它条形码是否存在于指定位置的输入文本。如果position给出的是负int,表示我们指定的是从末尾开始的位置。
我有一些东西按预期工作:
$ 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 (also Sum types, Algebraic Data types in Nim; terminology Union Type 在 Nim 中不常使用)通常是在 Nim 中实现动态类型最直接的方式(经典示例是 JsonNode)。
编辑:根据需要 API
由于重点是“切片”的可重用性和性能改进,因此可能会使用以下内容(也在这里: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)。我怀疑这比开始索引的预计算有更大的性能改进。
- 根据前两点,在多次应用 match 之前要“编译”的对象并不是真正的 Slice(因此得名 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"