#types #slice #nim-lang
#типы #срез #nim-lang
Вопрос:
Я хотел бы динамически определить 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
Мой вариант использования более сложный, но он сводится к программе командной строки, которая принимает в качестве аргументов входной текст, «штрих-код» и начальную позицию штрих-кода, и сообщает, присутствует ли штрих-код во входном тексте в указанной позиции. Если позиция задана как отрицательное значение 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" amp; 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
объект, который я могу повторно использовать вместо повторного вычисления фрагмента «на месте».
Комментарии:
1. Можете ли вы привести примеры по API, которые вы пытаетесь достичь (мне не ясно из опубликованных примеров)?
2. @pietroppeter Извините за мою запоздалую реакцию. Я добавил пример API, которого я хотел бы достичь (скорее: которого я действительно достигаю, но я хотел бы «оптимизировать» внутреннюю реализацию).
3. Можете ли вы просто использовать объект HSlice? пусть mySlice = (bcStart-1)..<(bcStart bcLen — 1)
4. Я не думаю, что здесь можно использовать общий тип Slice [T] (или HSlide [T, U]), поскольку во время компиляции он не будет знать, является ли T int или BackwardsIndex . ниже я обновляю свой ответ, утверждая, что следует просто избегать мышления в терминах срезов.
Ответ №1:
Все проблемы связаны с тем фактом, что ваш тип является классом типов. Это псевдотип, который может использоваться только во время компиляции в качестве параметра для перегрузки процесса (или для is
оператора). В частности, его нельзя назначить var
(первая ошибка, о которой вы сообщаете), и его нельзя динамически использовать во время выполнения.
Остальные 2 ошибки, которые вы получаете, связаны с 1) тем фактом, что s1
не определено за пределами области if. 2) Тот факт, что компилятору требуется уникальный тип для s1
(сначала он выводит тип из предложения if, а затем применяет для предложения else).
Варианты объектов (также типы сумм, алгебраические типы данных в Nim; терминологический тип объединения не часто используется в 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
должны быть сопоставлены несколько раз с разными текстами (возможно, переменной длины) - лучше избегать вычисления «фрагмента» строки, поскольку он выделяет новую строку (см. Здесь). Я подозреваю, что это большее улучшение производительности, чем предварительное вычисление начального индекса.
- по предыдущим двум пунктам объект, который должен быть «скомпилирован» перед многократным применением сопоставления, на самом деле не является фрагментом (отсюда и название myPattern)
Комментарии:
1. Спасибо за полезные ответы. На самом деле я бы сказал, что проблема YXZ: мое упрощение не совсем точно отражает мой реальный вариант использования. Простое сопоставление было чрезмерным упрощением, о котором я подумал, чтобы иметь что-то, что могло бы «оправдать» извлечение фрагмента, но в моем реальном случае использования, после извлечения фрагмента текста с помощью фрагмента, я делаю больше, чем просто проверяю его соответствие штрих-коду. Я передаю его функции, которая, например, вычисляет расстояние до штрих-кода, поэтому мне нужен доступ к полному фрагменту текста.
2. Но, возможно, я могу реализовать все, что хочу, используя (полные) циклы для исходного текста, если это имеет хорошие шансы быть более эффективным, чем выделение новой строки.
Ответ №2:
выражение
let sl = if (bcLen >0): bcLen else: BackwardsIndex(bcLen)#Error: type mismatch!
не удается скомпилировать на статически типизированном языке, поэтому вам нужно вставить 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] # ""
Редактировать
Вы хотели иметь возможность передавать фрагмент, который можно было бы использовать в разных входных строках. Вот как это будет выглядеть с учетом вышеизложенного:
#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"
Комментарии:
1. Если конечные строки соответствуют типу API, которого хотел достичь OP, я не вижу, в чем было бы преимущество перед стандартным синтаксисом (например
echo[^5 .. 9]
, все, кроме последней строки, будут скомпилированы нормально). Хороший ответ, хотя 🙂2. О, я активно не рекомендую его использовать. но, надеюсь, это показывает, что добавление желаемого синтаксиса в Nim довольно элегантно.