About

Python の数値計算ライブラリ numpy の多次元配列 ndarray の作成方法についてまとめた.
numpy,ndarray

以下のコードを実行するには,まず numpy を import する.

import numpy as np

numpy.array(object, dtype=None)

numpy.array を使うと,値と型を指定して多次元配列を作成できる.

>>> x = np.array([[0,1,2],
                  [3,4,5],
                  [6,7,8]],
                  np.int32)

empty() で作成する.

numpy.empty を使うと,形状を指定して値を初期化しない多次元配列を作成できる.

>>> x = np.empty((2,2))
>>> x # デタラメな値が入っている.
array([[  4.24399158e-314,   8.48798317e-314],
       [  1.27319747e-313,   1.69759663e-313]])

zeros() で作成する.

numpy.zeros を使うと,形状を指定して値を0で初期化した多次元配列を作成できる.

>>> x = np.zeros((2,2))
>>> x
array([[ 0.,  0.],
       [ 0.,  0.]])

ones() で作成する.

numpy.ones を使うと,形状を指定して値を1で初期化した多次元配列を作成できる.

>>> x = np.ones((2,2))
>>> x
array([[ 1.,  1.],
       [ 1.,  1.]])

empty_like(), zeros_like(), ones_like() で作成する.

numpy.zeros_like: 形状を与えられた配列と同じで値を初期化しない多次元配列を作成できる.
numpy.empty_like: 形状を与えられた配列と同じで値を0で初期化した多次元配列を作成できる.
numpy.ones_like: 形状を与えられた配列と同じで値を1で初期化した多次元配列を作成できる.

>>> x = [[1, 2], [3, 4]]
>>> y = np.empty_like(x)
>>> y
array([[0, 0],
       [0, 0]])
>>> y = np.zeros_like(x)
>>> y
array([[0, 0],
       [0, 0]])
>>> y = np.ones_like(x)
>>> y
array([[1, 1],
       [1, 1]])

About

Python の数値計算ライブラリ numpy を使う上で必須であるインデックス操作についてまとめた.

以下のコードを実行するには,まず numpy を import する.

import numpy as np

Shallow Copy と Deep Copy

ndarray の一部をインデックス操作 (indexing)で切り出したものをスライス (slice)という.

スライスは元の配列の参照なので,スライスを変更すると元の配列も変わってしまう.

>>> x = np.array([1,2,3,4,5,6,7,8,9])
>>> y = x[:3]
>>> y
array([1, 2, 3])
>>> y[0] = 100
>>> y
array([100,   2,   3])
>>> x
array([100,   2,   3,   4,   5,   6,   7,   8,   9])

copy()を使えば,参照ではなく新たな ndarray が作成されるので,切り出した配列を変更しても,元の配列が変わってしまうことはない.

>>> x = np.array([1,2,3,4,5,6,7,8,9])
>>> y = x[:3].copy()
>>> y
array([1, 2, 3])
>>> y[0] = 100
>>> y
array([100,   2,   3])
>>> x
array([1, 2, 3, 4, 5, 6, 7, 8, 9])

インデックス操作 (Indexing)

1次元配列

次の1次元配列に対する操作を見ていく.

x = np.array([0,1,2,3,4,5,6,7,8,9])

array[index]

インデックスを1つ指定する.

>>> x[0]
0

array[start:]

start を指定すると,[start, 9] のスライスが作成される.

>>> x[6:]
array([6, 7, 8, 9])

array[:end]

end を指定すると,[0,end) のスライスが作成される.
Python の list 同様,end の要素はスライスに含まれないことに注意する.

>>> x[:4]
array([0, 1, 2, 3])

array[start:end]

start 及び end を指定すると,[start,end) のスライスが作成される.

>>> x[2:6]
array([2, 3, 4, 5])

array[start:end:stride]

stride を指定すると,[start,end) まで stride ずつ値を取り出したスライスが作成される.

>>> x[1:10:2]
array([1, 3, 5, 7, 9])

2次元配列

次の2次元配列に対する操作を見ていく.

x = np.array([[0,1,2],
              [3,4,5],
              [6,7,8]])

この場合,”要素数3の配列”の各要素が”要素数3の配列”になっていると考える.
カンマで区切って軸ごとの start:end:stride を記述する.
array[軸0に対する操作,軸1に対する操作]

軸0に対する操作

軸1に対する操作を省略した場合は軸0に対する操作のみ行われる.

>>> x[1]
array([0, 1, 2])
>>> x[1:3]
array([[3, 4, 5],
       [6, 7, 8]])
>>> x[::2]
array([[0, 1, 2],
       [6, 7, 8]])

軸0及び軸1に対する操作

>>> x[1:,1:]
array([[4, 5],
       [7, 8]])

>>> x[1][1]
4

3次元配列

3次元配列にでも考え方は同様である.

x = np.array([[[0,1],[2,3]],
              [[4,5],[6,7]]])

この場合,”要素数2の配列”の各要素が”要素数2の配列”の各要素が”要素数2の配列”になっていると考える.
カンマで区切って軸ごとの start:end:stride を記述する.
array[軸0に対する操作,軸1に対する操作,軸2に対する操作]


軸0,軸1及び軸2に対する操作

>>> x[1]
array([[4, 5],
       [6, 7]])
>>> x[1][1]
array([6, 7])
>>> x[1][1][0]
6

n次元配列

例は省略するが,n次元配列でも同様である.
array[軸0に対する操作, 軸1に対する操作, …, 軸nに対する操作] とかけばよい.
また \(k \le n\) としたとき, \(k\) 以降の軸の操作は省略できることも同様である.
array[軸0に対する操作, 軸1に対する操作, …, 軸kに対する操作]

文字リテラルと内部表現

C++ では二重引用符で囲まれた文字列は文字列リテラルと解釈される.

const char *str = "Hello"

このコードは次の意味を持つ.
1. 文字列を const char 型の配列としてメモリ上に保存する.
2. const char 型配列から const char* 型への暗黙的な型変換が行われる.(そのポインタは配列の先頭アドレスを指す.)
3. 配列の先頭アドレスを指すポインタで,str が初期化される.

ここで興味があるのが,「Hello」という文字列がメモリ上でどのようなビット列で格納させるのかということである.
これは次のコードで確認できる.

#include <iostream>
#include <iomanip>

char* str = "Hello";
int size = strlen(str);

for (int i = 0; i < size; i++) {
    std::cout << std::hex << static_cast<unsigned>(static_cast<unsigned char>(str[i])) << " ";
}

char型配列の要素の値を1つずつ16進数で表示するものである.
その結果,文字列の内部表現はAsciiコードであることがわかる.

H e l l o \0
48 65 6c 6c 6f 0

このようにAscii文字であれば,char 型配列の1要素が1文字に対応するので,文字列の長さを知りたければ以下のようにすればいい.

// Null文字は含まれない.
std::cout << "length of str: " << strlen(str) << std::endl;
> length of str: 5

次に日本語の場合はどうだろうか.

char* str = "こんにちは";

同様に配列の中身を16進数で確認すると,

\0
82 b1 82 f1 82 c9 82 bf 82 cd 0

今度は,文字列の内部表現はShiftコードであることがわかる.

1文字が char 配列の2要素に対応しているので,先ほどのように strlen() で文字列の長さを取得しようと思っても期待通りの結果にはならない.
なぜなら,strlen はあくまで null文字を除いた char型配列 の長さを返す関数だからである.

// Null文字は含まれない.
std::cout << "length of str: " << strlen(str) << std::endl;
> length of str: 10

ワイド文字

C++にはもう1つワイド文字というものがあった.
文字リテラルの二重引用符の前にLをつけると,これは const w_char 型の配列となる.

const w_char *str = L"こんにちは"

w_char 型 は,型の大きさを増やして日本語のようなマルチバイト文字やAscii文字もすべて1つの型で表現できるようにしたものである.

std::cout << "char: " << sizeof(char) << std::endl;
std::cout << "wchar_t: " << sizeof(wchar_t) << std::endl;
> char: 1
> wchar_t: 2

先ほどと同様に,配列の中身を表示してみる.

wchar_t* str = L"こんにちは";
int size = sizeof(wchar_t) * 5;
unsigned char *p = reinterpret_cast<unsigned char*>(str);
for (int i = 0; i < size; i++) {
    std::cout << std::hex << static_cast<unsigned>(p[i]) << " ";
}

今回は文字列の内部表現はUTF8である

\0
53 30 93 30 6b 30 61 30 6f 30 0

wchar_t 型配列は1文字1要素となっているため,wcslen() で文字数を取得できる.

std::cout << "length of str: " << wcslen(str) << std::endl;
> length of str: 5

注意したいのは,w_char 型で保証されるのはマルチバイト文字も含め1つの型で表現されることだけであり,上で見てきたような w_char 型が2バイトであるとか,内部表現はUTF8であるとかはコンパイラ依存で,環境によっては違ってくる可能性もある.
1文字が1つの型に対応しているというのは,直感的で扱いやすいが,環境依存の部分が多いため,あまり使われていないようである.

文字コードを指定するには

二重引用符で囲んだだけの文字リテラルは,内部表現がどうなるかはコンパイラ依存である.
しかし,それを明示的に指定したい場合も出てくるかもしれない.
C++11には,UTF8リテラル,UTF16リテラル,UTF32リテラルというものがある.
それぞれ二重引用符の前にu8,u,Uをつけるだけでいい.

const char* str1 = u8"こんにちは"; // UTF8でエンコードされる.
const char* str2 = u"こんにちは"; // UTF16でエンコードされる.
const char* str3 = U"こんにちは"; // UTF32でエンコードされる.

まとめ

  • 文字リテラルがどんな文字コードで表わされるかはコンパイラ依存である.
  • マルチバイト文字を std::string や char* 配列に入れて使用する場合は,文字数を取得したりする関数は期待通りの動作はしないので注意する.
  • ワイド文字はコンパイラ依存の部分が多いので,あまり使われない.
  • C++11では,u8,u,U を先頭につけると,エンコード形式を指定できる.