Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@momeemt

Nimの配列を華麗に処理する sequtils

はじめに

こんにちは。高校2年の樅山です。
今回は、Nimの配列(array型、seq型、string型など)を処理する標準ライブラリである、sequtilsをご紹介いたします。

この記事は、

の22日目に参加しています。

他の標準ライブラリを調べる👇

sequtils

sequtilsは、openArray1の配列を操作するための機能を提供する標準ライブラリの一つです。

動的配列に変換する

toSeqテンプレートに、iterable2な要素を渡すと、動的配列 seq型に変換し、返します。

toSeq
template toSeq(iter: untyped): untyped

実行例

動的配列に変換する
import sequtils

echo (1..5).toSeq
echo {1, 2, 3, 4, 5}.toSeq
echo [1, 2, 3, 4, 5].toSeq
stdout
(stdout)
@[1, 2, 3, 4, 5]
@[1, 2, 3, 4, 5]
@[1, 2, 3, 4, 5]

多次元動的配列を作る

多次元動的配列を作る場合は、newSeqWithテンプレートが便利です。

newSeqWith
template newSeqWith(len: int; init: untyped): untyped

実行例

多次元動的配列を作る
import sequtils

echo newSeqWith(3, newSeq[int](3))
stdout
(stdout)
@[@[0, 0, 0], @[0, 0, 0], @[0, 0, 0]]

要素数を数える

配列の要素数を数える

countプロシージャは、ある要素がいくつ含まれているかを数え、その個数を返します。

count
proc count[T](s: openArray[T]; x: T): int

実行例

count
import sequtils

echo [1, 2, 3, 4, 5].count(3)
echo @[3, 1, 4, 1, 5, 9, 2, 6].count(1)
echo "momeemt".count('m')
stdout
(stdout)
1
2
3

条件を満たす要素数を数える

countItテンプレートは、配列と条件を受け取り、配列の要素を取り出して条件に当てはまる要素の個数を返します。

ローカル変数 it を用いて直接処理を記述して渡します。

countIt
template countIt(s, pred: untyped): int

実行例

条件を満たす要素数を数える
import sequtils

echo [1, 2, 3, 4, 5].countIt(it >= 3)
echo @[3, 1, 4, 1, 5, 9, 2, 6].countIt(it mod 2 == 0)
echo "momeemt".countIt(it != 'm')
stdout
(stdout)
3
3
4

要素のインデックスを得る

最小の要素のインデックスを得る

minIndexプロシージャは、渡した配列の要素の中で最も小さい要素のインデックスを返します。
配列に包まれた型は、<演算子で比較できる 型でなければなりません。
また、最小の要素が複数ある場合は、最初の要素のインデックスが返されます。

minIndex
proc minIndex[T](s: openArray[T]): int

実行例

最小の要素のインデックスを得る
import sequtils

echo @[9, 8, 7, 6, 5, 4, 3, 2, 1].minIndex
echo @[2, -100, 9].minIndex
echo "momeemt".minIndex
echo [1, 1, 1, 1, 1].minIndex
stdout
(stdout)
8
1
3
0

最大の要素のインデックスを得る

maxIndexプロシージャは、渡した配列の要素の中で最も大きい要素のインデックスを返します。
配列に包まれた型は、<演算子で比較できる 型でなければなりません。
また、最大の要素が複数ある場合は、最初の要素のインデックスが返されます。

maxIndex
proc maxIndex[T](s: openArray[T]): int

実行例

最大の要素のインデックスを得る
import sequtils

echo @[9, 8, 7, 6, 5, 4, 3, 2, 1].maxIndex
echo @[2, -100, 9].maxIndex
echo "momeemt".maxIndex
echo [1, 1, 1, 1, 1].maxIndex
stdout
(stdout)
0
2
6
0

動的配列を結合する

concatプロシージャは、任意の動的配列 seq[T]型 の可変長引数を受け取り、それらを結合した新しい動的配列を返します。

concat
proc concat[T](seqs: varargs[seq[T]]): seq[T]

実行例

動的配列を結合する
import sequtils

const
  arr1 = @['a', 'b', 'c', 'd']
  arr2 = @['e', 'f', 'g']
  arr3 = @['h']

echo concat(arr1, arr2, arr3)
stdout
(stdout)
@['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

配列の要素を繰り返す

cycleプロシージャは、配列に含まれる要素を繰り返してできた、新しい動的配列を返します。
繰り返す回数は Natural型、つまり自然数(1以上の整数)でなければなりません。

cycle
proc cycle[T](s: openArray[T]; n: Natural): seq[T]

実行例

配列の要素を繰り返す
import sequtils

echo [1, 2].cycle(2)
echo @[true, false].cycle(4)
echo "Wow".cycle(3)
stdout
(stdout)
@[1, 2, 1, 2]
@[true, false, true, false, true, false, true, false]
@['W', 'o', 'w', 'W', 'o', 'w', 'W', 'o', 'w']

ある要素を繰り返して配列を作る

repeatプロシージャは、ある要素を繰り返して動的配列を作り、返します。
繰り返す回数は、Natural型、つまり自然数(1以上の整数)でなければなりません。

repeat
proc repeat[T](x: T; n: Natural): seq[T]

実行例

ある要素を繰り返して配列を作る
import sequtils

echo 'a'.repeat(10)
echo true.repeat(2)
echo 6.repeat(3) # 悪魔的
stdout
(stdout)
@['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']
@[true, true]
@[6, 6, 6]

重複を取り除く

deduplicateプロシージャは、渡した配列から重複した要素を取り除いた新しい動的配列を返します。
また、ソート済みの配列を渡す場合は、第2引数 isSortedtrue にするとより高速に重複を除去できます。

deduplicate
proc deduplicate[T](s: openArray[T]; isSorted: bool = false): seq[T]

実行例

重複を取り除く
import sequtils, algorithm

echo "momeemt".deduplicate
echo @[3, 1, 4, 1, 5, 9, 2, 6].deduplicate
echo @[1, 6, 7, 2, 9, 2, 1, 3, 3, 6, 8, 4, 5, 2].sorted.deduplicate true
stdout
(stdout)
@['m', 'o', 'e', 't']
@[3, 1, 4, 5, 9, 2, 6]
@[1, 2, 3, 4, 5, 6, 7, 8, 9]

2つの配列をまとめる

zipプロシージャは、2つの任意の配列をまとめることができます。この2つの配列は、型が異なっても構いません

zip
proc zip[S, T](s1: openArray[S]; s2: openArray[T]): seq[(S, T)]

配列の要素数が同じ場合

要素数が同じ場合、配列Aのindex 0と、配列Bのindex 0が1つのtupleとしてまとめられ、tupleの配列が返されます。

実行例

同じ要素数の配列をまとめる
import sequtils

const
  arr1 = [1, 2, 3, 4, 5]
  arr2 = ["momeemt", "abap", "shina", "tosyun", "fof"]
  arr3 = arr1.zip(arr2)

echo arr3
echo arr3[0][1]
stdout
(stdout)
@[(1, "momeemt"), (2, "abap"), (3, "shina"), (4, "tosyun"), (5, "fof")]
momeemt

配列の要素数が異なる場合

要素数が異なる場合、要素数が少ない配列に合わせられ、あぶれた要素は切り捨てられます。

実行例

配列の要素数が異なる場合
import sequtils

const
  arr1 = [1, 2, 3,]
  arr2 = ["Tokyo", "Kanagawa", "Osaka", "Aichi", "Saitama"]
  arr3 = arr1.zip(arr2)

echo arr3
echo arr3[0][1]
stdout
@[(1, "Tokyo"), (2, "Kanagawa"), (3, "Osaka")]
Tokyo

タプル配列を分解する

unzipプロシージャは、(S, T)タプルの配列を (seq[S], seq[T)に分解します。
zipプロシージャの逆演算です。

unzip
proc unzip[S, T](s: openArray[(S, T)]): (seq[S], seq[T])

実行例

タプル配列を分解する
import sequtils

const
  tupleArr = @[(1, "momeemt"), (2, "abap"), (3, "shina"), (4, "tosyun"), (5, "fof")]
  (arr1, arr2) = tupleArr.unzip

echo arr1
echo arr2
stdout
(stdout)
@[1, 2, 3, 4, 5]
@["momeemt", "abap", "shina", "tosyun", "fof"]

配列を分割する

distributeプロシージャは、配列を、与えた分割数で分割します。
また、要素数以上の分割数で分割すると、空配列に分割します。

spreadオプションはデフォルトではtrueで、全ての要素を均等に分割します。
falseの場合、最大要素数 1 + len(s) div num をできるだけ確保しながら分割し、確保できなくなった場合は確保できる最大数か、空配列に分割します。

distribute
proc distribute[T](s: seq[T]; num: Positive; spread = true): seq[seq[T]]

実行例

配列を分割する
import sequtils

const arr = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

echo arr.distribute(6)
echo arr.distribute(6, false)
echo arr.distribute(12)
stdout
(stdout)
@[@[1, 2], @[3, 4], @[5, 6], @[7, 8], @[9], @[10]]
@[@[1, 2], @[3, 4], @[5, 6], @[7, 8], @[9, 10], @[]]
@[@[1], @[2], @[3], @[4], @[5], @[6], @[7], @[8], @[9], @[10], @[], @[]]

要素を削除する

deleteプロシージャは、可変変数に代入された動的配列と、削除する要素の範囲インデックスを受け取り、破壊的に削除します。
範囲外のインデックスを指定しても、削除可能なインデックスの要素のみを削除し、例外が投げられません。

delete
proc delete[T](s: var seq[T]; first, last: Natural)

実行例

要素を削除する
import sequtils

var
  arr1 = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
  arr2 = @[1, 2, 3, 4, 5, 6, 7, 8, 9]

arr1.delete(3, 8)
arr2.delete(0, 9)

echo arr1
echo arr2
stdout
(stdout)
@[1, 2, 3]
@[]

配列を挿入する

insertプロシージャは、可変変数に代入された動的配列と、挿入したい配列、挿入したいインデックスを受け取り、動的配列に配列を挿入します。
この時、挿入する配列もされる配列も同じ型を包んだ配列でなければなりません。

insert
proc insert[T](dest: var seq[T]; src: openArray[T]; pos = 0)

実行例

配列を挿入する
import sequtils

var
  arr1 = @[1, 2, 3, 7, 8 ,9]
  arr2 = @[4, 5, 6]

arr1.insert(arr2, 3)

echo arr1
stdout
(stdout)
@[1, 2, 3, 4, 5, 6, 7, 8, 9]

全ての要素に操作を行う

配列と操作を渡し、全ての要素に破壊的な操作を行います。
applyプロシージャには、操作をラムダ式で、applyItテンプレートには、操作をローカル変数 it を用いて渡します。
applyItテンプレートの方が、より簡潔ですが、可読性が落ちます。

ラムダ式を渡す

apply
# case1
proc apply[T](s: var openArray[T]; op: proc (x: var T) {.closure.}) {.inline.}

# case2
proc apply[T](s: var openArray[T]; op: proc (x: T): T {.closure.}) {.inline.}

# case3
proc apply[T](s: openArray[T]; op: proc (x: T) {.closure.}) {.inline.}

applyには呼び出し方が3つあります。

  1. 渡す配列は可変変数に代入されている必要があります。渡すラムダ式は、可変変数を受け取り、取り出した要素に破壊的変更を行います。
  2. 渡す配列は可変変数に代入されている必要があります。渡すラムダ式は、取り出した要素を受け取って、新しい要素を返却し、その要素が元の配列に破壊的代入されます。
  3. 渡した配列は、要素が取り出され、渡したラムダ式に適用されます。

特に、3番目の apply は、渡した配列に一切の変更を加えませんし、新しい配列も生成しません。

実行例

全ての要素に操作を行う
import sequtils, strformat

var
  arr1 = @[1, 2, 3, 4, 5]
  arr2 = @[1, 2, 3, 4, 5]
  arr3 = @[1, 2, 3, 4, 5]
  lmd1 = proc (n: var int) =
    n = n * n
  lmd2 = proc (n: int): int =
    result = n * n
  lmd3 = proc (n: int) =
    echo &"lmd3: {n} * {n} = {n * n}" 

arr1.apply(lmd1)
arr2.apply(lmd2)
arr3.apply(lmd3)

echo arr1
echo arr2
echo arr3
stdout
(stdout)
lmd3: 1 * 1 = 1
lmd3: 2 * 2 = 4
lmd3: 3 * 3 = 9
lmd3: 4 * 4 = 16
lmd3: 5 * 5 = 25
@[1, 4, 9, 16, 25]
@[1, 4, 9, 16, 25]
@[1, 2, 3, 4, 5]

ローカル変数 it を用いる

applyIt
template applyIt(varSeq, op: untyped)

実行例

全ての要素に操作を行う
import sequtils, strformat

var arr = @[1, 2, 3, 4, 5]
arr.applyIt(it * it)
echo arr
stdout
(stdout)
@[1, 4, 9, 16, 25]

全ての要素に操作を行った配列を得る

配列と操作を渡し、全ての要素に操作を適用した新しい配列を返します。
mapプロシージャには、操作をラムダ式で、mapItテンプレートには、操作をローカル変数 it を用いて渡します。
mapItテンプレートの方が、より簡潔ですが、可読性が落ちます。

ラムダ式を渡す

map
proc map[T, S](s: openArray[T]; op: proc (x: T): S {.closure.}): seq[S] {.inline.}

実行例

全ての要素に操作を行った配列を得る
import sequtils

const
  arr = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  square = proc (x: int): int =
    result = x * x

echo arr.map(square)
stdout
(stdout)
@[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

ローカル変数 it を用いる

mapIt
template mapIt(s: typed; op: untyped): untyped

実行例

全ての要素に操作を行った配列を得る
import sequtils

const arr = @[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
echo arr.mapIt(it * it)
stdout
(stdout)
@[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

条件を満たす要素のみの配列を得る

配列と条件を渡し、条件を満たす要素だけを抜き出した新しい配列を返します。
filterプロシージャには、条件をラムダ式で、filterItテンプレートには、条件をローカル変数 it を用いて渡します。
また、filterイテレータは、ラムダ式で条件を満たす要素を渡し、for文で走査できます。
filterItテンプレートの方が、より簡潔ですが、可読性が落ちます。

ラムダ式を渡す

filter
proc filter[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): seq[T] {.inline.}

実行例

条件を満たす要素のみの配列を得る
import sequtils, math

const
  arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  arr3 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  lmd1 = proc (n: int): bool =
    result = n mod 2 == 0
  lmd2 = proc (n: int): bool =
    result = pow(sqrt(n.float), 2) == n.float
  lmd3 = proc (n: int): bool =
    result = n mod 7 < n mod 5

echo arr1.filter(lmd1)
echo arr2.filter(lmd2)
echo arr3.filter(lmd3)
stdout
(stdout)
@[2, 4, 6, 8]
@[1, 4, 9]
@[7, 8, 9]

ローカル変数 it を用いる

filterIt
template filterIt(s, pred: untyped): untyped

実行例

条件を満たす要素のみの配列を得る
import sequtils, math

const
  arr1 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  arr3 = [1, 2, 3, 4, 5, 6, 7, 8, 9]

echo arr1.filterIt(it mod 2 == 0)
echo arr2.filterIt(pow(sqrt(it.float), 2) == it.float)
echo arr3.filterIt(it mod 7 < it mod 5)
stdout
(stdout)
@[2, 4, 6, 8]
@[1, 4, 9]
@[7, 8, 9]

for文で走査する

filter
iterator filter[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): T

実行例

フィルターをかけた配列をfor文で操作する
import sequtils

const
  arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
  lmd = proc (n: int): bool =
    result = n mod 3 == 0

for elem in arr.filter(lmd):
  echo elem
stdout
(stdout)
3
6
9

条件を満たさない要素を削除する

可変変数に代入された配列と条件を渡し、条件を満たさない要素を削除します。
keepIfプロシージャには、条件をラムダ式で、keepItIfテンプレートには、条件をローカル変数 it を用いて渡します。
filterプロシージャ、filterItテンプレートと異なり、要素には破壊的変更が加えられます。
keepItIfテンプレートの方が、より簡潔ですが、可読性が落ちます。

ラムダ式を渡す

keepIf
proc keepIf[T](s: var seq[T]; pred: proc (x: T): bool {.closure.}) {.inline.}

実行例

条件を満たさない要素を削除する
import sequtils

var
  arr = @[168.9, 172.4, 158.9, 181.2, 166.4, 170.9, 169.2, 174.4]
  lmd = proc (n: float): bool =
    result = n >= 170.0

arr.keepIf(lmd)

echo arr
stdout
(stdout)
@[172.4, 181.2, 170.9, 174.4]

ローカル変数 it を用いる

keepItIf
template keepItIf(varSeq: seq; pred: untyped)

実行例

条件を満たさない要素を削除する
import sequtils

var arr = @[168.9, 172.4, 158.9, 181.2, 166.4, 170.9, 169.2, 174.4]
arr.keepItIf(it >= 170.0)
echo arr
stdout
(stdout)
@[172.4, 181.2, 170.9, 174.4]

全ての要素が条件を満たすかどうかを得る

配列と条件を渡し、全ての要素が条件を満たす場合は true を、満たさない場合は false を返します。
allプロシージャには、条件をラムダ式で、allItテンプレートには、条件をローカル変数 it を用いて渡します。
allItテンプレートの方が、より簡潔ですが、可読性が落ちます。

ラムダ式を渡す

all
proc all[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): bool

実行例

全ての要素が条件を満たすかどうかを得る
import sequtils

var
  arr = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  lmd1 = proc (n: int): bool =
    result = n > 0
  lmd2 = proc (n: int): bool =
    result = n >= 0

if arr.all(lmd1):
  echo "全ての要素が自然数です"
elif arr.all(lmd2):
  echo "全ての要素が0以上です"
else:
  echo "負数が含まれています"
stdout
(stdout)
全ての要素が0以上です

ローカル変数 it を用いる

allIt
template allIt(s, pred: untyped): bool

実行例

全ての要素が条件を満たすかどうかを得る
import sequtils

var arr = @[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

if arr.allIt(it > 0):
  echo "全ての要素が自然数です"
elif arr.allIt(it >= 0):
  echo "全ての要素が0以上です"
else:
  echo "負数が含まれています"
stdout
(stdout)
全ての要素が0以上です

少なくとも1つの要素が条件を満たすかどうかを得る

配列と条件を私、少なくとも1つの要素が条件を満たす場合は true を、満たさない場合は false を返します。
anyプロシージャには、条件をラムダ式で、anyItテンプレートには、条件をローカル変数 it を用いて渡します。
anyItテンプレートの方が、より簡潔ですが、可読性が落ちます。

ラムダ式を渡す

any
proc any[T](s: openArray[T]; pred: proc (x: T): bool {.closure.}): bool

実行例

少なくとも1つの要素が条件を満たすかどうかを得る
import sequtils

var
  arr = @[2001, 2003, 2004, 2005, 2007, 2009]
  lmd = proc (n: int): bool =
    result = 2002 <= n and n <= 2004

if arr.any(lmd):
  echo "少なくとも一人は高校生がいます"
else:
  echo "高校生はいません"
stdout
(stdout)
少なくとも一人は高校生がいます

ローカル変数 it を用いる

anyIt
template anyIt(s, pred: untyped): bool

実行例

少なくとも1つの要素が条件を満たすかどうかを得る
import sequtils

var arr = @[2001, 2003, 2004, 2005, 2007, 2009]

if arr.anyIt(2002 <= it and it <= 2004):
  echo "少なくとも一人は高校生がいます"
else:
  echo "高校生はいません"
stdout
(stdout)
少なくとも一人は高校生がいます

リテラルに直接操作を適用する

mapLiteralsマクロは、リテラルと操作を渡すことで、リテラルに直接操作を適用することができます。
nestedオプションは、falseを与えると、ネストされたリテラルには操作を行わないようになります。

mapLiterals
macro mapLiterals(constructor, op: untyped; nested = true): untyped

実行例

リテラルに直接操作を適用する
import sequtils

echo mapLiterals((1, 2, (3, 4, 5), (6, 7), 8, 9, 10), `$`, true)
echo mapLiterals((1, 2, (3, 4, 5), (6, 7), 8, 9, 10), `$`, false)
stdout
(stdout)
("1", "2", (3, 4, 5), (6, 7), "8", "9", "10")

畳み込み

foldlテンプレートで左畳み込みが、foldrテンプレートで右畳み込みが動的配列と、操作を渡すことで行えます。
ローカル変数abが存在し、既存の計算結果がaに、次に畳み込まれる値がbに格納されます。

通常の畳み込み

foldl,foldr
template foldl(sequence, operation: untyped): untyped
template foldr(sequence, operation: untyped): untyped

実行例

通常の畳み込み
import sequtils

const
  arr1 = @[1, 2, 3, 4, 5]
  arr2 = @["Hello,", " ", "Nim", "!"]

echo arr1.foldl(a + b)
echo arr1.foldl(a - b)
echo arr1.foldl(a * b)
echo arr2.foldl(a & b)

echo arr1.foldr(a + b)
echo arr1.foldr(a - b)
echo arr1.foldr(a * b)
echo arr2.foldr(a & b)
stdout
(stdout)
15
-13
120
Hello, Nim!
15
3
120
Hello, Nim!

初期値を設定した左畳み込み

foldlテンプレートは、配列、操作、初期値を渡すことで、初期値を設定できます。

foldl
template foldl(sequence, operation, first): untyped

実行例

初期値を設定した左畳み込み
import sequtils

const
  arr1 = @[1, 2, 3, 4, 5]
  arr2 = @["Hello,", " ", "Nim", "!"]

echo arr1.foldl(a + b, 10)
echo arr2.foldl(a & b, "yay! ")
stdout
(stdout)
25
yay! Hello, Nim!

終わりに

配列の処理は、かなり頻繁に求められます。
sequtilsと、前回紹介した algorithmで提供されるプロシージャなどで、わざわざ自分で書かなくても多くの機能が提供されているため、なんとなくでも把握しておくのも良いかもしれません。
特に、...It系のテンプレートは、疑似変数itを用いて渡すことができるので、簡潔に処理がかけて大変強力です。ぜひ、その恩恵に預かってみてください。


  1. openArray型とは、引数で利用できる特別な型です。静的配列 array型、動的配列 seq型など、配列に関する型を全て受け入れます。また、string型は seq[char]型と解釈され、処理できます。 

  2. iterableであるとは、イテレータのようにfor文で要素を取り出せるような要素であるということを指します。 

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
momeemt
こんにちは。高校3年生の樅山です。 Nimの記事を書いています。 標準ライブラリの解説をするのが好きです。
nim-in-japan
Nim言語の日本コミュニティです。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?