1
3
個人開発エンジニア応援 - 個人開発の成果や知見を共有しよう!-

[Swift] 配列の宣言と初期化

Last updated at Posted at 2023-09-19

Swiftで配列を宣言するには、

Array<要素>[要素]と書きます。例えば、Int型の配列ならArray<Int>[Int]です。どちらの書き方でも意味はまったく同じ。
これだけだと、配列の宣言ですから、変数や定数とするためには、これに配列のサイズや要素を指定する必要があります。これには、大きく2通りの書き方があります。

一つ目の書き方は、サイズ0(空の)配列を定義して、その後、appendにより、要素を追加していく方法です。(Arrayappendは高速です。$O(1)$)
Int型の空の配列を定義する書き方として、下記に4つあげましたが、これまた、この4つはどれも同じ意味です。(a3, a4は型推論)

一つ目
var a1: Array<Int> = []
var a2: [Int] = []
var a3 = Array<Int>()
var a4 = [Int]()

こちらは、後からappendするために、必ずvarで宣言するため、厳密には定数になり得ません。

二つ目の書き方は、あるサイズの配列を定義して、どの要素も同じ値で初期化する方法です。
Int型の要素10個の配列を定義して、どれも値を-1にする書き方として、下記に4つあげますが、ご多分に漏れず、この4つもどれも同じ意味です。

二つ目
let size = 10, value = -1
var a1: Array<Int> = Array(repeating: value, count: size)
var a2: [Int] = Array(repeating: value, count: size)
var a3 = Array<Int>(repeating: value, count: size)
var a4 = [Int](repeating: value, count: size)

こちらは、letで宣言することもできるため定数になり得ますが、用途はあまりないかも。

詳しい説明は省略しますが、ここで注意しなければいけないこととして、class型の配列をArray(repeating:count:)で定義すると、各要素のインスタンスはどれも同じになるということです。

では、それぞれの要素の値が異なる配列の定義はどうすればよいでしょうか。
要素をその個数分並べて書くことができます。しかし、要素の数が10、20はよいですが、何百、何千となると、書くのが大変でしょう。

let a5 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

また、Array(_:)を使えば、例えば、Int型の0999の千個の配列は、次のように書くことができます。

let a6 = Array(0 ..< 1000)
//[0, 1, 2, 3, 4, ・・・ , 996, 997, 998, 999]

これに、mapを組み合わせると、使い方の幅が広がりますね。

let a7 = Array(0 ..< 1000).map { $0 * 3 } //3の倍数
//[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, ・・・]

ですが、もっと一般化した配列の定義が欲しいと思いませんか?
そこで、Arrayextensionを定義してみました。

配列の定義の一般化

よく使う配列の型

(これは本題ではありませんが、)よく使う配列の型として、別名をつけました。

typealias IntArray = Array<Int>
typealias DoubleArray = Array<Double>
typealias BoolArray = Array<Bool>
typealias StringArray = Array<String>

簡単に配列を宣言できるextension

以下のextensionを使うと、配列の宣言が簡略化できます。

extension Array where Element: FixedWidthInteger {
    init(_ size: Int) { self.init(size, .zero) }
}
extension Array where Element: BinaryFloatingPoint {
    init(_ size: Int) { self.init(size, .zero) }
}
extension Array where Element: ExpressibleByBooleanLiteral {
    init(_ size: Int) { self.init(size, false) }
}
extension Array where Element: StringProtocol {
    init(_ size: Int) { self.init(size, "") }
}
extension Array {
    init(_ size: Int, _ value: Element) {
        self = [Element](repeating: value, count: size)
    }
}

整数型、浮動小数点型、ブール型、文字列型を要素とする配列を簡単に書けます。
要素の初期値を省略すると、整数型は0、浮動小数点型は0.0、ブール型はfalse、文字列型は""(空文字列)で初期化します。

使用例
let N = 5
var a8 = [UInt32](N) //UInt32型のN個の配列、初期値は0
var a9 = [CGFloat](5, -1) //CGFloat型の5個の配列、初期値は-1.0
var a10 = BoolArray(10, true) //Bool型の10個の配列、初期値は`true`
var a11 = StringArray(3) //String型の10個の配列、初期値は""(空文字列)

//初期値を指定すれば、任意の型の配列を宣言できる
var a12 = [CGPoint](10, CGPoint(x: -1, y: -1)) //CGPoint型の10個の配列、初期値は`(x: -1, y: -1)`
var a13 = [Int?](N, nil) //Optional<Int>型のN個の配列、初期値は`nil`

任意の初期値を指定できますが、初期値がリテラルで無い場合は、次に説明するextensionを使います。

一般化した配列の宣言

以下のextensionを使うと、配列の宣言をさらに柔軟に書くことができます。

extension Array {
    init(_ size: Int, _ initValue: ((Int) -> Element)) {
        self = [Element]()
        for index in 0 ..< size {
            let value = initValue(index)
            self.append(value)
        }
    }
    init(_ size: Int, _ initValue: (() -> Element)) {
        self = [Element]()
        for _ in 0 ..< size {
            let value = initValue()
            self.append(value)
        }
    }
}

あらゆる型の配列の初期値を要素ごとに書くことができます。

使用例1
//let a7 = Array(0 ..< 1000).map { $0 * 3 } //と等価の書き方
let a14 = IntArray(1000) { $0 * 3 }

//Int型タプルのN個の配列、初期値は`(インデクス, 偶数)`
let N = 10 
let a15 = [(Int, Int)](N) { n in (n, n * 2) }
//[(0, 0), (1, 2), (2, 4), (3, 6), (4, 8), (5, 10), (6, 12), (7, 14), (8, 16), (9, 18)]
使用例2
//String型の10個の配列、初期値は標準入力(改行区切り)から取得
let a16 = StringArray(10) { readLine()! }

//Int型のN行、M列の(二次元)配列、初期値は標準入力(スペース区切り)から取得
let N = 3, M = 4
let a17 = [[Int]](N) { readLine()!.split(separator: " ").map { Int($0)! } }

//Int型のN行、X列の二次元配列、列方向の初期値は`空配列`。要素は後から追加していく。
var a18 = [[Int]](N) { [] }
//a18[row].append(col)

//CGPoint型の10個の配列、初期値は標準入力(スペース区切り)から取得
var a19 = [CGPoint](10) {
    let xy = readLine()!.split(separator: " ").map { CGFloat(Double($0)!) }
    return CGPoint(x: xy[0], y: xy[1])
}

汎用性があり過ぎて、例を出すのが難しいほどです。
class型の配列も、要素ごとにインスタンスを生成するため、安心して使うことができます。

おまけ

以下のextensionを使うと、Arrayへのappend+=で書けて便利です。

Array += Element
extension Array {
    @inlinable static func += (lhs: inout Self, rhs: Self.Element) { lhs.append(rhs) }
}
使用例
var a20 = [Int]()
//(0 ..< 10).forEach { a20.append($0) } //と等価
(0 ..< 10).forEach { a20 += $0 }
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

まあ、このextensionが無くても、もともと次のようには書けますが、扱いは、配列の結合ですから、上のextensionの方が性能はよいはずです。

var a20 = [Int]()
(0 ..< 10).forEach { a20 += [$0] }
//[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

おまけ2

2D Array(任意の型のN行、M列の二次元配列)をアクセスする場合は、array[row][column] と書きますが、これを array[row, column] と書ける extension です。
array>型の extension 定義に苦労したので、備忘録を兼ねて残しておきます。同じ手法で x, y, z の三次元配列のアクセサを定義することも可能です。

2D array accessor
extension MutableCollection where Self.Element: MutableCollection {
    @inlinable subscript(_ row: Self.Index, _ column: Self.Element.Index) -> Self.Element.Element {
        get {
            //precondition(self.startIndex ..< self.endIndex ~= row)
            //precondition(self[row].startIndex ..< self[row].endIndex ~= column)
            return self[row][column]
        }
        set {
            //precondition(self.startIndex ..< self.endIndex ~= row)
            //precondition(self[row].startIndex ..< self[row].endIndex ~= column)
            self[row][column] = newValue
        }
    }
}
使い方
let keywords = "associatedtype,borrowing,class,consuming,deinit,enum,extension,fileprivate,func,import,init,inout,internal,let,open,operator,private,precedencegroup,protocol,public,rethrows,static,struct,subscript,typealias,var,break,case,catch,continue,default,defer,do,else,fallthrough,for,guard,if,in,repeat,return,throw,switch,where,while,Any,as,await,catch,false,is,nil,rethrows,self,Self,super,throw,throws,true,try,#available,#colorLiteral,#else,#elseif,#endif,#fileLiteral,#if,#imageLiteral,#keyPath,#selector,#sourceLocation,#unavailable".split(separator: ",").map { Array($0) }
print(keywords[1]) //["b", "o", "r", "r", "o", "w", "i", "n", "g"]
print(keywords[1, 3]) //r
print(keywords[3]) //["c", "o", "n", "s", "u", "m", "i", "n", "g"]
print(keywords[3, 3]) //s

おわりに

[任意型](個数) { n in n番目の初期値 }の書き方は、大変便利です。
一律の初期値の場合は、[任意型](個数) { 初期値 }の書き方。
初期値のところに、リテラルの他に、式や関数が書けるところがポイントです。

ご参考まで。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3