10.3 알아두면 삶이 윤택해지는 System.Array
C#에서는 모든 것이 객체입니다. 배열도 객체이며, 당연히 기반이 되는 형식이 있습니다. .NET의 CTS에서 배열은 System.Array 클래스에 대응됩니다. 다음 예제 프로그램은 int 기반의 배열이 System.Array 형식에서 파생됐음을 보여줍니다.
using System;
namespace DerivedFromArray
{
class Program
{
static void Main(string[] args)
{
int[] array = new int[] { 10, 30, 20, 7, 1 };
Console.WriteLine($"Type Of array : {array.GetType()}");
Console.WriteLine($"Base type Of array : {array.GetType().BaseType}");
}
}
}Type Of array : System.Int32[]
Base type Of array : System.Array따라서 System.Array의 특성과 메소드를 파악하면 배열의 특성과 메소드를 알게 되는 셈이며, 보너스로 배열을 이용하며 재미있는 일들도 할 수 있습니다. 예를 들어 내부 데이터를 원하는 순서대로 정렬한다는가, 특정 데이터를 배열 속에서 찾아내는 작업들 말입니다. System.Array 클래스에는 수십 가지 메소드와 프로퍼티가 있지만, 지면을 절약하기 위해 우리는 그중에서 자주 사용하게 될 몇가지만 살펴보겠습니다.
정적메소드
| 정적 메소드 | Sort() | 배열을 정렬합니다. | |||||||
| BinarySearch<T>() | 이진 탐색을 수행합니다. | ||||||||
| IndexOf() | 배열에서 찾고자 하는 특정 데이터의 인덱스를 반환합니다. | ||||||||
| TrueForAll<T>() | 배열의 모든 요소가 지정한 조건에 부합하는지의 여부를 반환합니다. | ||||||||
| FindIndex<T>() | 배열에서 지정한 조건에 부합하는 첫 번째 요소의 인덱스를 반환합니다. IndexOf() 메소드가 특정 값을 찾는데 비해, FindIndex<T>() 메소드는 지정한 조건에 바탕하여 값을 찾습니다. | ||||||||
| Resize<T>() | 배열의 크기를 재조정합니다 | ||||||||
| Clear() | 배열의 모든 요소를 초기화합니다. 배열이 숫자 형식 기반이면 0으로, 논리형식 기반으로 false로, 참조 형식 기반이면 null로 초기화합니다. | ||||||||
| ForEach<T>() | 배열의 모든 요소에 대해 도일한 작업을 수행하게 합니다. | ||||||||
| Copy<T>() | 배열의 일부를 다른 배열에 복사합니다 | ||||||||
인스턴스 메소드 | GetLength() | 배열에서 지정한 차원의 길이를 반환합니다. 이 메소드는 나중에 설명할 다차원 배열에서 유용하게 사용됩니다. | |||||||
| 프로퍼티 | Length | 배열의 길이를 반환합니다. | |||||||
| Rank | 배열의 차원을 반환합니다. | ||||||||
BinarySearch, TrueForAll, FindIndex, Resize, ForEach처럼 뒤에 <T>를 붙이고 다니는 메소드는 매개변수(Type Parameter)라고 합니다. 이들 메소드를 호출할 때 T대신 배열의 기반 자료형을 인수로 입력하면 컴파일러가 해당 형식에 맞춰 동작하도록 메소드를 컴파일 합니다. 자세한 내용은 나중에 제대로 배워보도록하고, Array 클래스의 메소드와 프로퍼티를 활용하는 예제를 준비했습니다.
using System;
using System.Net.Sockets;
namespace MoreOnArray
{
class Program
{
private static bool CheckPassed(int score)
{
return score >= 60;
}
private static void Print(int value)
{
Console.Write($"{value} ");
}
static void Main(string[] args)
{
int[] scores = new int[] { 80, 74, 81, 90, 34 };
foreach (int score in scores)
Console.Write($"{scores}");
Console.WriteLine();
Array.Sort(scores);
Array.ForEach<int>(scores, new Action<int>(Print));
Console.WriteLine();
Console.WriteLine($"Number of dimesions : {scores.Rank}");
Console.WriteLine($"Binary Search : 81 is at " + $"{ Array.BinarySearch<int>(scores, 81) }");
Console.WriteLine($"Linear Search : 90 is at " + $"{ Array.IndexOf(scores, 90) }");
Console.WriteLine("Everyone passed ? : " + $"{ Array.TrueForAll<int>(scores, CheckPassed)}");
//TrueForAll 메소드는 배열과 함께 조건을 검사하는 메소드를 매개변수로 받습니다
int index = Array.FindIndex<int>(scores, (score) => score < 60);
//FindIndex 메소드는 특정 조건에 부합하는 메소드를 매개변수로 받습니다.
//여기선 람다식을 이용했습니다.
scores[index] = 61;
Console.WriteLine($"Everyone passed ? : " + $"{Array.TrueForAll<int>(scores, CheckPassed)}");
Console.WriteLine("Old length of scores : " + $"{scores.GetLength(0)}");
Array.Resize<int>(ref scores, 10); //5였던 배열의 용량을 10으로 재조정
Console.WriteLine($"New length of scores : { scores.Length }");
Array.ForEach<int>(scores, new Action<int>(Print)); //Action 대리자
Console.WriteLine();
Array.Clear(scores, 3, 7);
Array.ForEach<int>(scores, new Action<int>(Print));
int[] sliced = new int[3];
Array.Copy(scores, 0, sliced, 0, 3);
//scores 배열의 0번째부터 3개 요소를 sliced 배열의 0번째 ~ 2번째 요소에 요소에 차례대로 복사
Array.ForEach<int>(sliced, new Action<int>(Print));
Console.WriteLine();
}
}
}80 74 81 90 34
34 74 80 81 90
Number of dimesions : 1
Binary Search : 81 is at 3
Linear Search : 90 is at 4
Everyone passed ? : False
Everyone passed ? : True
Old length of scores : 5
New length of scores : 10
61 74 80 81 90 0 0 0 0 0
61 74 80 0 0 0 0 0 0 0
61 74 80마지막 문단 int[] sliced ~ Array.Copy구간(59,60번째줄)에서 scores 배열의 일부를 sliced라는 배열을 복사했습니다.
이렇게 배열의 일부를 다른 곳에 복사하는 것을 분할(slice)이라고도 표현합니다.
10.4 배열 분할하기
Array.Copy() 메소드도 나쁘지 않았지만, 지금 배우는 배열 분할 방법을 알게되면 더는 사용하지 않게 될 것입니다. 새로운 배열 분할 방법을 이해하기 전에 C# 8.0에 System.Index 형식과 함께 도입된 System.Range와 낯을 익힐 필요가 있습니다. System.Range는 이름이 나타내는 것처럼, 시작 인덱스와 마지막 인덱스를 이용한 범위를 나타냅니다. System.Range 객체를 생성할 때는 다음과 같이 .. 연산자를 이용합니다. .. 연산자는 아래 코드 예시처럼 왼쪽엔 시작 인덱스, 오른쪽엔 마지막 인덱스가 옵니다.
System.Range r1 = 0..3; //시작 인덱스 0, 마지막 인덱스 3
int[] sliced = scores[r1]; // [와 ]사이에 인덱스 대신 System.Rang 객체를 입력하면 분할된 배열이 반환됩니다.
int[] slice2 = scores[0..3];
//[와 ] 사이에 직접 .. 을 넣는게 코드가 더 간결해집니다.
아래 코드가 위에 두줄보다 간단하고 쉽습니다.조심해야 할 사항으로는 연산자는 두번째 피연산자, 즉 마지막 인덱스는 배열 분할 결과에서 제외된다는 것입니다. 또, '..' 연산자가 받아들이는 두 연산자는 생략이 가능합니다. [..] 처럼 시작과 마지막 인덱스 모두 생략시엔 배열 전체를 나타내는 System.Range 객체를 반환합니다. 또한 System.Range 객체를 생성할 때 System.Index 객체를 이용할 수도 있습니다. 마지막은 예제 프로그램입니다.
//첫번째 요소(0)부터 세 번째 요소(2) 요소 까지
int[] sliced3 = scores[..3];
//1부터 마지막 요소까지
int[] sliced4 = scores[1..];
//전체
int[] sliced5 = scores[..];
=============================
//
System.Index idx = ^1;
int[] sliced5 = scores[..idx];
//System.Index객체를 생성하지 않고 ^연산자를 직접 인력하면 코드가 더 간결해집니다.
int[] sliced6 = scores[..^1];
using System;
using System.Net.Sockets;
namespace Slice
{
class Program
{
static void PrintArray(System.Array array)
{
foreach (var e in array)
{
Console.Write(e);
}
Console.WriteLine();
}
static void Main(string[] args)
{
char[] array = new char['Z'-'A' + 1]; //'Z'는 ASCII 코드로 90, 'A'는 65입니다
//대문자 알파벳 개수는 모두 90(Z) - 65(A) + 1, 즉 26입니다.
for (int i = 0; i < array.Length; i++) //array 'A' to 'Z'
{
array[i] = (char)('A' + i);
}
PrintArray(array[..]); //0부터 마지막까지
PrintArray(array[5..]); //5부터 끝까지
Range range_5_10 = 5..10;
PrintArray(array[range_5_10]); //5번째부터 (10 - 1)까지
Index last = ^0;
Range range_5_last = 5..last; //Range를 생성할 대 리터럴과 Index 객체도 함께 사용할 수 있습니다.
PrintArray(array[range_5_last]); //꿈에 벌써 5번쨰 끝(^)까지
PrintArray(array[^4..^1]); //끝에서 4번째부터 끝에서 2번째까지
}
}
}ABCDEFGHIJKLMNOPQRSTUVWXYZ
FGHIJKLMNOPQRSTUVWXYZ
FGHIJ
FGHIJKLMNOPQRSTUVWXYZ
WXY10.5 2차원 배열
우리가 앞서 다뤘던 배열은 원소들이 늘어서 있는 방향이 하나뿐인 1차원이었지만, 이번엔 세로 + 가로로 원소를 배치하는 2차원 배열입니다. 다른말로 1차원 배열을 원소로 갖는 배열입니다. 다음은 1차원의 길이가 3이고 2차원의 길이가 2인 2차원 배열입니다.
| 1 [0,0] | 2 [0,1] | 3 [0,2] |
| 4 [1,0] | 5 [1,1] | 6 [1,2] |
2차원 배열을 선언하는 방법은 다음과 같습니다. 1차원과 기본적으로 같지만 차원의 용량 또는 길이를 콤마로 구분해준다는 점이 다르네요. 또한 2차원 배열의 원소에 접근할 때는 첫 번째 차원과 두 번째 차원의인덱스를 대괄호 [와 ] 사이에 같이 입력해줘야합니다. 마지막으로 2차원 배열을 선언과 동시에 초기화 하고 싶다면 앞서 설명한 1차원 배열의 세 가지 초기화 방법을 다음과 같은 형태로 사용하면 되겠습니다.
[2차원 배열 선언 방식]
데이터_형식[,] 배열이름 = new 데이터_형식[2차원_길이, 1차원_길이];
[2x3 크기의 int 형식 2차원 배열]
int[,] array = new int[2, 3];
array[0, 0] = 1;
array[0, 1] = 2;
array[0, 2] = 3;
array[1, 0] = 4;
array[1, 1] = 5;
array[1, 2] = 6;
[배열 원소 접근 예제]
Console.WriteLine(array[0, 2]);
Console.WriteLine(array[1, 1]);
[2차원 배열을 선언과 동시에 초기화]
int[,] arr = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } }; //배열의 형식과 길이 명시
int[,] arr2 = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } }; //배열의 길이 생략
int[,] arr3 = { { 1, 2, 3 }, { 4, 5, 6 } }; //형식과 길이 모두 생략배열을 초기화하는 코드를 보니 배열안에 배열이 들어가있습니다. 이것은 앞에 설명했듯이 2차원 배열이 1차원 배열을 원소로 갖는 배열이기 때문입니다. 이제 2차원 배열 예제 프로그램을 만들어봅시다!
using System;
using System.Net.Sockets;
namespace _2DArray
{
class Program
{
static void Main(string[] args)
{
int[,] arr = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
Console.WriteLine($"[{i}, {j}] : {arr[i,j]} ");
}
Console.WriteLine();
}
Console.WriteLine();
//
int[,] arr2 = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } };
for (int i = 0; i< arr2.GetLength(0); i++)
{
for (int j = 0; j< arr2.GetLength(1); j++)
{
Console.WriteLine($"[{i}, {j}] : {arr2[i, j]} ");
}
Console.WriteLine();
}
Console.WriteLine();
//
int[,] arr3 = { { 1, 2, 3 }, { 4, 5, 6 } };
for (int i = 0; i< arr3.GetLength(0); i++)
{
for (int j = 0; j< arr3.GetLength(1); j++)
{
Console.WriteLine($"[{i}, {j}] : {arr3[i, j]} ");
}
Console.WriteLine();
}
Console.WriteLine();
}
}
}[0, 0] : 1
[0, 1] : 2
[0, 2] : 3
[1, 0] : 4
[1, 1] : 5
[1, 2] : 6
[0, 0] : 1
[0, 1] : 2
[0, 2] : 3
[1, 0] : 4
[1, 1] : 5
[1, 2] : 6
[0, 0] : 1
[0, 1] : 2
[0, 2] : 3
[1, 0] : 4
[1, 1] : 5
[1, 2] : 6
'C#.' 카테고리의 다른 글
| 인프런 c# 배열 연습 (0) | 2023.11.12 |
|---|---|
| Unity Disacards - C# (0) | 2023.11.05 |
| 이것이 C# 이다 10장) 배열 Array [1] (0) | 2023.08.31 |
| 이것이 C#이다 9장 ) 인터페이스와 추상클래스의 프로퍼티 (feat:무명형식) (0) | 2023.08.31 |
| 이것이 C#이다 9장) 불변 객체 record (0) | 2023.08.30 |