Распаковать несколько переменных из последовательности

Я ожидаю, что приведенный ниже код напечатает chr7.

import strutils

var splitLine = "chr7    127471196  127472363  Pos1  0  +".split()
var chrom, startPos, endPos = splitLine[0..2]
echo chrom

Вместо этого он печатает @[chr7, 127471196, 127472363].

Есть ли способ распаковать несколько значений из последовательностей одновременно?

И каким был бы самый лаконичный способ сделать это, если бы элементы не были смежными? Например:

var chrom, startPos, strand = splitLine[0..1, 5]

Выдает ошибку:

read_bed.nim(8, 40) Error: type mismatch: got (seq[string], Slice[system.int], int literal(5))
but expected one of:
system.[](a: array[Idx, T], x: Slice[system.int])
system.[](s: string, x: Slice[system.int])
system.[](a: array[Idx, T], x: Slice[[].Idx])
system.[](s: seq[T], x: Slice[system.int])

  var chrom, startPos, strand = splitLine[0..1, 5]
                                         ^

person The Unfun Cat    schedule 11.08.2015    source источник


Ответы (4)


Это можно сделать с помощью макросов.

import macros

macro `..=`*(lhs: untyped, rhs: tuple|seq|array): auto =
  # Check that the lhs is a tuple of identifiers.
  expectKind(lhs, nnkPar)
  for i in 0..len(lhs)-1:
    expectKind(lhs[i], nnkIdent)
  # Result is a statement list starting with an
  # assignment to a tmp variable of rhs.
  let t = genSym()
  result = newStmtList(quote do:
    let `t` = `rhs`)
  # assign each component to the corresponding
  # variable.
  for i in 0..len(lhs)-1:
    let v = lhs[i]
    # skip assignments to _.
    if $v.toStrLit != "_":
      result.add(quote do:
        `v` = `t`[`i`])

macro headAux(count: int, rhs: seq|array|tuple): auto =
  let t = genSym()
  result = quote do:
    let `t` = `rhs`
    ()
  for i in 0..count.intVal-1:
    result[1].add(quote do:
      `t`[`i`])

template head*(count: static[int], rhs: untyped): auto =
  # We need to redirect this through a template because
  # of a bug in the current Nim compiler when using
  # static[int] with macros.
  headAux(count, rhs)

var x, y: int
(x, y) ..= (1, 2)
echo x, y
(x, _) ..= (3, 4)
echo x, y
(x, y) ..= @[4, 5, 6]
echo x, y
let z = head(2, @[4, 5, 6])
echo z
(x, y) ..= head(2, @[7, 8, 9])
echo x, y

Макрос ..= распаковывает назначения кортежа или последовательности. Вы можете сделать то же самое с var (x, y) = (1, 2), например, но ..= также работает для последовательностей и массивов и позволяет повторно использовать переменные.

Шаблон/макрос head извлекает первые count элементы из кортежа, массива или последовательностей и возвращает их в виде кортежа (который затем можно использовать как любой другой кортеж, например, для деструктурирования с помощью let или var).

person Reimer Behrends    schedule 12.08.2015
comment
Очень красивое решение. Именно из-за этой проблемы с static[int] и макросами я не смог найти других решений. Приятно видеть обходной путь для этого. - person bluenote10; 13.08.2015

Для тех, кто ищет быстрое решение, вот шустрый пакет, который я написал под названием unpack.

Вы можете выполнить деструктуризацию/распаковку последовательности и объекта с помощью следующего синтаксиса:

someSeqOrTupleOrArray.lunpack(a, b, c)
[a2, b2, c2] <- someSeqOrTupleOrArray

{name, job} <- tim

tom.lunpack(job, otherName = name)
{job, name: yetAnotherName} <- john
person T.agd    schedule 05.12.2018

В настоящее время сопоставление с образцом в Nim работает только с tuples. Это также имеет смысл, потому что сопоставление с образцом требует статически известной арности. Например, что должно произойти в вашем примере, если длина seq не равна трем? Обратите внимание, что в вашем примере длина последовательности может быть определена только во время выполнения, поэтому компилятор не знает, действительно ли возможно извлечь три переменные.

Поэтому я думаю, что решение, на которое ссылается @def-, идет в правильном направлении. В этом примере используются массивы, которые действительно имеют статически известный размер. В этом случае компилятор знает арность кортежа, т. е. извлечение определено корректно.

Если вам нужен альтернативный (возможно, удобный, но небезопасный) подход, вы можете сделать что-то вроде этого:

import macros

macro extract(args: varargs[untyped]): typed =
  ## assumes that the first expression is an expression
  ## which can take a bracket expression. Let's call it
  ## `arr`. The generated AST will then correspond to:
  ##
  ## let <second_arg> = arr[0]
  ## let <third_arg>  = arr[1]
  ## ...
  result = newStmtList()
  # the first vararg is the "array"
  let arr = args[0]
  var i = 0
  # all other varargs are now used as "injected" let bindings
  for arg in args.children:
    if i > 0:
      var rhs = newNimNode(nnkBracketExpr)
      rhs.add(arr)
      rhs.add(newIntLitNode(i-1))

      let assign = newLetStmt(arg, rhs) # could be replaced by newVarStmt
      result.add(assign)
    i += 1
  #echo result.treerepr


let s = @["X", "Y", "Z"]

s.extract(a, b, c)
# this essentially produces:
# let a = s[0]
# let b = s[1]
# let c = s[2]

# check if it works:
echo a, b, c

Я еще не включил проверку длины seq, поэтому вы просто получите ошибку выхода за пределы, если последовательность не имеет требуемой длины. Еще одно предупреждение: если первое выражение не является литералом, выражение будет оцениваться/вычисляться несколько раз.

Обратите внимание, что литерал _ разрешен в привязках let в качестве заполнителя, что означает, что вы можете делать такие вещи:

s.extract(a, b, _, _, _, x)

Это будет относиться к вашему примеру splitLine[0..1, 5], который, кстати, просто не является допустимым синтаксисом индексации.

person bluenote10    schedule 12.08.2015
comment
Хорошо спасибо. Написание извлечения для последовательностей длиной в миллиард звучит не очень весело ;) Интересно, возможно ли что-то вроде замечательного Python itemgetter в nim... - person The Unfun Cat; 12.08.2015
comment
В этом, по сути, и дело: если у вас есть миллиард полей, классическое сопоставление с образцом не имеет особого смысла, потому что вы все равно не хотите иметь миллиард переменных в своем коде (для чего и предназначено сопоставление с образцом). В этих случаях вы, скорее всего, каким-либо образом выберете определенные поля, скажем, индексы 3, 17 и 12382. Для этого и существует индексация с произвольным доступом. Обратите внимание, что вы можете расширить идею, возможно, написав s.extract(a, 3, b, 17, c, 12382). Но вы хотите получить гораздо больше по сравнению с прямым индексированием, например. let a, b, c = (s[3], s[17], c[12382]). - person bluenote10; 12.08.2015
comment
Ах. Возможно, пример a, b, c = (s[3]... ) должен представить ваш ответ? Если это действительно идиоматический nim... Возможно, st о том, будет ли работать также a, b, c = (s[3..4], s[17]).... - person The Unfun Cat; 12.08.2015

еще один вариант — package definesugar:

import strutils, definesugar

# need to use splitWhitespace instead of split to prevent empty string elements in sequence
var splitLine = "chr7    127471196  127472363  Pos1  0  +".splitWhitespace()
echo splitLine

block:
  (chrom, startPos, endPos) := splitLine[0..2]
  echo chrom # chr7
  echo startPos # 127471196
  echo endPos # 127472363

block:
  (chrom, startPos, strand) := splitLine[0..1] & splitLine[5] # splitLine[0..1, 5] not supported
  echo chrom
  echo startPos
  echo strand # +

# alternative syntax
block:
  (chrom, startPos, *_, strand) := splitLine
  echo chrom
  echo startPos
  echo strand

см. https://forum.nim-lang.org/t/7072 для последних обсуждение

person pietroppeter    schedule 12.11.2020