본문 바로가기

C#/이것이 C#이다

[ 이것이 C#이다 ] Chapter 10 . 배열과 컬렉션 그리고 인덱서

[ 학습 흐름 ]

[ 학습 흐름 ]

  1. 배열의 초기화
  2. System.Array
  3. 2차원 배열
  4. 다차원 배열
  5. 가변 배열
  6. 컬렉션
  7. 인덱서

[ All For one,one for all ]

[ 배열 ]

//데이터 형식 [] 배열이름 = new 데이터형식[용량];
int[] scores = new int[5];

- 배열은 데이터를 담는 상자이다 . 필요한 용량을 가진 배열을 만든 다음 데이터를 넣을 수 있다.

int[]scores = new int[5];
scores[0]=80;

- 배열의 각 요소에 데이터를 저장할때는 배열이름 뒤에 대괄호를 붙이고 인덱스를 적어준다 .

- 인덱스는 0 부터 시작한다.

using System;

namespace ArraySample;


class MainApp
{
    static void Main(string[] args)
    {
        int[] scores = new int[5];
        scores[0] = 80;
        scores[1] = 74;
        scores[2] = 81;
        scores[3] = 90;
        scores[4] = 34;

        foreach(int score in scores) 
        {
            Console.WriteLine(score);
        }
        int sum = 0;
        foreach(int score in scores)
        {
            sum += score;
        }
        //Length는 배열의 용량을 나타낸다
        int average=sum/scores.Length;
        Console.WriteLine($"Average Score : {average}");
    }
}

[ System.Index와 ^연산 ]

System.Index last = ^1
scores[last] = 34 //scores[scores.Length-1]=34와 동일

scores[^1] = 34//더 간소화된 버전

- 배열의 마지막 요소에 접근하려면 배열길이 -1 을 통해 확인해야 하는 번거로움이 있다 .

- ^ 연산자는 컬렉션의 마지막부터 역순으로 인덱스를 지정하는 기능이다 . ex) ^1 =배열.Length-1

- ^연산자의 연산결과는 System.Index 형식의 인스턴스로 나타난다 . 


[ 배열을 초기화 하는 세가지 방법 ]

[ 배열 용량을 명시 ]

string[] array1 = new string[3]{"안녕","Hello","Halo"};

- 배열의 원소개수를 명시하고 , 중괄호로 둘어싸인 블록을 붙인 뒤, 블록 사이 각 배열의 원소에 입력될 데이터 입력

- 배열 객체를 초기화하는 {} 블록을 일컬어 컬렉션 초기자라고 부른다 .


[ 배열 용량을 생략 ]

string[] array2 = new string[]{"안녕","Hello","Halo"};

- 용량을 생략하여 초기화해도 컴파일러는 첫번째 방식과 동일한 실행파일을 만든다 .


[ new연산자/형식/대괄호/용량 생략 ]

string[] array3 = {"안녕","Hello","Halo"};

- new 연산자 , 형식 ,대괄호,용량 모두 생략한다 . 첫번째 ,두번째 방식과 동일한 실행파일을 만든다 .

- 세번째 방식이 편하지만 , 코드를 공유해야할때는 상대방이 읽기 편하게 첫번째 방식을 쓰는게 좋다 .

using System;

namespace InitializingArray;


class MainApp
{
    static void Main(string[] args)
    {
        string[] array1 = new string[3] { "안녕", "Hello", "Halo" };
        Console.WriteLine("array1..");
        foreach (string s in array1) Console.WriteLine($" {s}");

        string[] array2 = new string[] { "안녕", "Hello", "Halo" };
        Console.WriteLine("array2..");
        foreach (string s in array2) Console.WriteLine($" {s}");

        string[] array3 = { "안녕", "Hello", "Halo" };
        Console.WriteLine("array3..");
        foreach (string s in array3) Console.WriteLine($" {s}");
    }
}

[ System.Array ]

[ System.Array ]

- .Net의 CTS에서 배열은 System.Array 클래스에 대응된다 .

class MainApp
{
    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}");

    }
}

 

- 위 예시와 같이 배열이 System.Array 형식에서 파생됐음을 보여준다 .

- System.Array의 특성과 메소드를 파악하면 배열의 특성과 메소드를 알 수 있다 .


[ System.Array의 주요 메소드와 프로퍼티 ]

분류 이름 설명
정적메소드 Sort() 배열을 정렬한다 .
BinarySearch<T>() 이진 탐색을 수행한다 .
IndexOf() 배열에서 찾고자 하는 특정 데이터의 인덱스를 반환
TrueForAll<T>() 배열의 모든 요소가 지정한 조건에 부합하는지 여부 반환
FindIndex<T>() 배열에서 지정한 조건에 부합하는 첫번째 요소의 인덱스 반환
Resize<T>() 배열의 크기를 재조정한다
Clear() 배열의 모든 요소를 초기화한다 . 숫자형식이면 0 ,
논리기반은 false,참조형식 기반은 null로 초기화한다 ,
Foreach<T>() 배열의 모든 요소에 동일한 작업을 진행한다 .
Copy<T>() 배열의 일부를 다른 배열에 복사한다 .
인스턴스
메소드
GetLength() 배열에서 지정한 차원의 길이를 반환한다,
프로퍼티 Length 배열의 길이를 반환한다 
Rank 배열의 차원을 반한한다 .

- <T>를 형식 매개변수라 한다 .

- 호출시 T 대신 배열의 기반 자료형을 인수로 입력하면 컴파일러가 해당 형식에 맞춰 동작하도록 메소드를 컴파일한다 .

using System;

namespace MoreOnArray;

class MainApp
{
    static bool CheckPassed(int score)
    {
        return score >= 60;
    }
    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.WriteLine(score);
        }
        Console.WriteLine();

        //오름차순으로 정렬
        Array.Sort(scores);
        Array.ForEach<int>(scores, new Action<int>(Print));
        Console.WriteLine();

        Console.WriteLine($"Number Of Dimensions : {scores.Rank}");

        Console.WriteLine($"Binary Search : 81 is at" + $"{Array.BinarySearch<int>(scores, 81)}");
        Console.WriteLine($"Linear Search : 81 is at" + $"{Array.IndexOf(scores, 90)}");
        //배열과 함께 조건을 검사하는 메서드를 매개변수로 받는다 .
        Console.WriteLine($"Every One Passed ? " + $"{Array.TrueForAll<int>(scores, CheckPassed)}");

        //특정조건에 부합하는 메소드를 매개변수로 받는다 . 람다식으로 구현됨
        int index = Array.FindIndex<int>(scores, (score) => score < 60);
        scores[index] = 61;
        Console.WriteLine($"Every One Passed ? " + $"{Array.TrueForAll<int>(scores, CheckPassed)}");

        Console.WriteLine($"Old length of Scores : {scores.GetLength(0)}");
        Array.Resize<int>(ref scores, 10);
        Console.WriteLine($"New length of Scores : {scores.Length}");

        Array.ForEach<int>(scores, new Action<int>(Print));
        Console.WriteLine();
        //3번째 부터 7개를 0으로 초기화
        Array.Clear(scores, 3, 7);
        Array.ForEach<int>(scores, new Action<int>(Print));
        Console.WriteLine();
        //배열의 일부를 다른 곳에 복사하는 것을 분할한다고 한다 .
        int[] sliced = new int[3];
        Array.Copy(scores,0, sliced,0,3);
        Array.ForEach<int>(scores, new Action<int>(Print));
        Console.WriteLine();
    }
}

[ System.Range ]

[ 배열 분할 ]

System.Range r1=0..3//시작인덱스 .. 마지막인덱스
int[]sliced=scores[r1];//인덱스 대신 Range 객체를 입력하면 분할된 배열이 반환된다 .
int[]sliced2 = scores[1..3];// 직접 입력시 간결한 코드

- System.Range는 시작 인덱스와 마지막 인덱스를 이용해서 범위를 나타낸다 .

- System.Range 객체를 생성할떄는 .. 연산자를 이용한다 .

- 주의점은 마지막 인덱스는 배열 분할 결과에서 제외된다 .

int [] sliced3 = scores[..3]//첫번째 요소 (0)부터 세번째 요소 (2)까지

int [] sliced4 = scores[1..]//두번째 요소부터 마지막 요소까지

int[] sliced5 = scores[..]//전체

 

- .. 연산자가 받아들이는 두 연산자는 생략 가능하다 .

using System;

namespace MoreOnArray;

class MainApp
{
    static void PrintArray(System.Array array)
    {
        foreach(var e in array)
            Console.Write(e);
        Console.WriteLine();
    }
    static void Main(string[] args)
    {
        //대문자 알파벳 개수 26 개
        char[] array = new char['Z' - 'A' + 1];
        for(int i = 0; i < array.Length; i++)
            array[i] = (char)('A'+i);

        PrintArray(array[..]);
        PrintArray(array[5..]);

        Range range_5_10 = 5..10;
        PrintArray(array[range_5_10]);

        Index last = ^0;
        Range range_5_last = 5 ..last;//Range 생성시 리터럴 과 Index객체를 함께 사용 가능
        PrintArray(array[range_5_last]);
        PrintArray(array[^4..^1]);

    }
}

 


[ 2차원 배열 ]

[ 2차원배열 ]

데이터 형식[,]배열이름 = new 데이터 형식[2차원길이,1차원길이];

int [,]array=new int[2,3]

int[,] arr1 = 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}};//형식과 길이를 모두 생략

- 1차원 배열을 원소로 갖는 배열

- int[2,3]은 기반 형식이 int이며 길이는 3인 1차원 배열을 원소로 2개 갖고 있는 2차원 배열 .

using System;

namespace _2DArray;

class MainApp
{
    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.Write($"[{i},{j}] : {arr[i, j]} ");
            }
            Console.WriteLine();
        }

    }
}

 


[ 다차원 배열 ]

[ 다차원배열 ]

int[,,]array=new int[4,3,2]
	{
    	{{1,2},{3,4},{5,6}},
    	{{1,4},{2,5},{3,6}},
    	{{6,5},{4,3},{2,1}},
    	{{6,3},{5,2},{4,1}},
    }

- 차원이 둘 이상인 배열

- 3차원 이상의 배열은 추천하지 않는다 .

- 복잡한 구조의 배열을 선언시 만들려는 배열의 각 크기를 지정해주는 것이 좋다 .

using System;

namespace _3DArray;

class MainApp
{
    static void Main(string[] args)
    {
        int[,,] arr = new int[4,3,2] 
        { 
            {{1,2},{3,4},{5,6}},
            {{1,2},{3,4},{5,6}},
            {{1,2},{3,4},{5,6}},
            {{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.Write("{ ");
                for(int k = 0; k < arr.GetLength(2); k++)
                {
                Console.Write($"{arr[i, j,k]}");

                }
                Console.Write(" }");

            }
            Console.WriteLine();
        }

    }
}

 


[ 가변 배열 ]

[ 가변 배열 ]

- 다양한 길이의 배열을 요소로 갖는 다차원 배열로 이용 될 수 있다  .

- 가변 배열은 다차원 배열과 다르게 배열을 요소로 사용해 접근할 수 있다 .

- 영어로는 Jagged Array (들쭉날쭉한)라 칭한다 .

데이터형식[][] 배열이름 = new 데이터 형식[가변배열의 용량][];
//가변 배열의 요소로 입력되는 배열은 그 길이가 모두 같을 필요가 없다.

int[][]jagged = new int[3][];
jagged[0]=new int[5]{1,2,3,4,5};
jagged[1]=new int[]{10,20,30};
jagged[2]=new int[]{100,200};

- 가변 배열의 요소로 입력되는 배열은 그 길이가 같을 필요가 없다 .

int[][] jagged2 = new int[2][]{new int[]{1000,2000},new int[4]{6,7,8,9}};

- 가변배열도 선언과 동시에 초기화가 가능하다 .

- 가변 배열의 요소는 "배열이다"

using System;

namespace _3DArray;

class MainApp
{
    static void Main(string[] args)
    {
        int[][] jagged = new int[3][];
        jagged[0] = new int[5] { 1, 2, 3, 4, 5 };
        jagged[1] = new int[] { 10, 20, 30 };
        jagged[2] = new int[] { 100, 200 };

        foreach (int[]arr in jagged)
        {
            Console.Write($"Length : {arr.Length}, ");
            foreach(var e in arr)
                Console.Write($"{e} ");
            Console.WriteLine("");
        }
    }
}

 


[ 컬렉션 ]

[ 컬렉션 ]

- 같은 성격을 띈 데이터의 모음을 담는 자료구조

- 컬렉션 자료구조는 Iclollection 인터페이스를 상속한다 .


[ Array List ]

- 컬렉션의 요소에 접근할때 [] 연산자 이용

- 특정 위치에 있는 요소에 데이터 임의 할당이 가능

- 배열과 다르게 용량을미리 지정할 필요 없이 자동으로 늘어나거나 줄어든다 .

using System;
using System.Collections;

namespace UsingList;

class MainApp
{
    static void Main(string[] args)
    {
        ArrayList list = new ArrayList();
        for(int i=0;i<5;i++)
            list.Add(i);

        foreach (object obj in list)
            Console.Write($"{obj} ");
        Console.WriteLine();

        list.RemoveAt(2);
        foreach (object obj in list)
            Console.Write($"{obj} ");
        Console.WriteLine();

        list.Insert(2, 2);
        foreach (object obj in list)
            Console.Write($"{obj} ");
        Console.WriteLine();

        list.Add("abc");
        list.Add("def");

        for (int i = 0; i < list.Count; i++)
            Console.Write($"{list[i]}");
        Console.WriteLine();

    }
}

- Add 메서드는 컬렉션의 마지막에 있는 요소 뒤에 새 요소를 추가한다 

- RemoveAt 메서드는 특정 인덱스에 있는 요소를 제거한다 

- Insert 메서드는 원하는 위치에 새 요소를 삽입한다 .

- Add / Insert 메서드는 매개변수로 obj 형식을 받기에 다양한 형식 객체를 담지만 박싱,언박싱이 이루어진다 .

- 박싱/언박싱은 적지 않은 오버헤드가 발생하기에 데이터가 많을수록 성능저하가 일어난다 .


[ Queue ]

- 데이터를 차례대로 입력했다 입력된 순서대로 하나씩 꺼내 처리하기 위해 사용한다 .

- 입력은 오직 뒤에서 , 출력은 앞에서만 이루어진다 .

using System;
using System.Collections;

namespace UsingQueue;

class MainApp
{
    static void Main(string[] args)
    {
        Queue que = new Queue();
        que.Enqueue(1);
        que.Enqueue(2);
        que.Enqueue(3);
        que.Enqueue(4);
        que.Enqueue(5);

        while(que.Count > 0);
        Console.WriteLine(que.Dequeue());   
    }
}

[ Stack ]

- 먼저 들어온 데이터가 나중에 나가는 선입후출의 구조이다 .

using System;
using System.Collections;

namespace UsingStack;

class MainApp
{
    static void Main(string[] args)
    {
        Stack stack = new Stack();
        stack.Push(1);
        stack.Push(2);
        stack.Push(3);
        stack.Push(4);
        stack.Push(5);

        while(stack.Count > 0);
        Console.WriteLine(stack.Pop());   
    }
}

[ HashTable ]

- 키와 값 쌍으로 이루어진 데이터를 다룰때 사용한다 

- 어떠한 형식이든 키로 사용 가능하다

- 배열만큼 빠른 탐색속도를 가지고 있다 .(해싱)

using System;
using System.Collections;

namespace UsingHashTable;

class MainApp
{
    static void Main(string[] args)
    {
       Hashtable ht = new Hashtable();
        ht["하나"] = "One";
        ht["둘"] = "Two";
        ht["셋"] = "Three";
        ht["넷"] = "Four";
        ht["다섯"] = "Five";

        Console.WriteLine(ht["하나"]);
        Console.WriteLine(ht["둘"]);
        Console.WriteLine(ht["셋"]);
        Console.WriteLine(ht["넷"]);
        Console.WriteLine(ht["다섯"]);
    }
}

[ 컬렉션 초기화 하는 방법 ]

[ 배열의 도움 ]

int arr={123,456,789};

ArrayList list = new ArrayList(arr);
Queue queue = new Queue(arr);
Stack stack = new Stack(arr);

ArrayList list2 = new ArrayList(){11,22,33}

- ArrayList / Queue / Stack은 배열의 도움을 받아 간단하게 초기화를 수행할수있다.

- ArrayList는 배열의 도움 없이 직접 컬렉션 초기자를 이용하여 초기화 할 수 있다 .

- 컬렉션 초기자는 IEnumerable 인터페이스의 Add 메서드를 구현하는 컬렉션만 지원하기에 ArrayList만 사용 가능하다 .


[ HashTable 초기화 ]

HashTable ht = new HashTable()
{
    ["하나"]=1,
    ["둘"]=2
};

- 딕셔너리 초기자를 이용한다 .

HashTable ht = new HashTable()
{
    {"하나",1},
    {"둘",2}
};

- 컬렉션 초기자 역시 이용 가능하다 .


[ 인덱서 ]

[ 인덱서 ]

class 클래스 이름
{
	한정자 인덱서형식 this[형식 index]
    {
    	get
        {
        	//index를 이용하여 내부 데이터 변환
        }
        set
        {
        	//index를 이용하여 내부 데이터 저장
        }
    }
}

- 인덱스를 이용해서 객체 내의 데이터에 접근하게 해주는 프로퍼티 (객체를 배열처럼 사용하게 해준다)

- 인덱서는 인덱스를 통해 객체 내 데이터에 접근하기에 식별자를 따로 가지지 않는다 .

using System;
using System.Collections;

namespace Indexer;

class MyList
{
    private int[] array;

    public MyList()
    {
        array=new int[3];
    }

    public int this[int index]
    {
        get { return array[index]; }
        set 
        {
            if(index>=array.Length)
            {
                Array.Resize(ref array, index+1);
                Console.WriteLine($"Array Resized : {array.Length}");
            }
            array[index] = value; 
        }
    }
    public int Length
    {
        get { return array.Length; }
    }
}

class MainApp
{
    static void Main(string[] args)
    {
       MyList list = new MyList();
        for (int i = 0; i < 5; i++)
            list[i] = i;

        for(int i=0;i<list.Length;i++)
            Console.WriteLine(list[i]);
    }
}

 


[ foreach가 가능한 객체 만들기 ]

[ foreach문의 조건 ]

- foreach 구문은 IEnumerable을 상속하는 형식만 지원한다 .

- 해당 인터페이스는 GetEnumerator()메서드 하나만 가지고 있다 .

- GetEnumerator()는 IEnumerator 인터페이스를 상속하는 클래스의 객체를 반환해야 한다 .


[ yield문의 이용 ]

- yield 문을 이용하면 컴파일러가 자동으로 IEnumerator를 구현한 클래스를 생성해준다 .

- yield return 문은 현재 메소드의 실행을 일시 정지하고 호출자에게 결과를 반환한다

- 메소드가 다시 호출되면 일시 정지된 실행을 복구 , yield return/yield break문을 만날때까지 작업을 실행한다 .

using System;
using System.Collections;

namespace Yield;

class MyEnumerator
{
    int[] numbers = { 1, 2, 3, 4 };
    public IEnumerator GetEnumerator()
    {
        yield return numbers [0];
        yield return numbers[1];
        yield return numbers[2];
        yield break;
        yield return numbers[3];

    }
}

class MainApp
{
    static void Main(string[] args)
    {
      var obj=new MyEnumerator();
        foreach(int i in obj)
            Console.WriteLine(i);
    }
}

[ IEnumerator 객체의 이용 ]

메소드 또는 프로퍼티 설명
boolean MoveNexet() 다음 요소로 이동한다.컬렉션의 끝을 지난 경우 false , 이동이 성공한 경우 true를 반환한다 
void Reset() 컬렉션의 첫번째 위치의 "앞"으로 이동한다 .첫번째 위치가 0번인 경우 Reset()을 호출하면 -1번으로 이동하는 것.
첫번째 위치로의 이동은 MoveNext()를 호출한 다음 이뤄진다 .
Object Current{get;} 컬렉션의 현재 요소를 반환한다 .

- IEnumerator 인터페이스의 메소드 및 프로퍼티 목록이다 .

-MoveNext()/Reset()/Current 프로퍼티의 구현시 IEnumerator의 조건을 충족한다 .

- 모두 충족한 클래스는 IEnumerable이 요구하는 GetIEnumerator()메소드 구현시 자기 자신을 반환하면 된다 .

using System;
using System.Collections;

namespace Enumerable;

class MyList : IEnumerable, IEnumerator
{
    int[] array;
    //컬렉션의 현재 위치를 다루는 변수.초깃값은 0이 아닌 -1.
    //foreach문이 첫번째 반복을 수행하면 MoveNext메서드를 실행하고
    //이때 Position이 1이 되어 두번째 요소를 가져오는 문제가 발생하기 때문
    int position = -1;

    public MyList()
    {
        array = new int[3];
    }
    public int this[int index]
    {
        get { return array[index]; }
        set
        {
            if (array.Length <= index)
            {
                Array.Resize(ref array, index+1);
                Console.WriteLine($"Array Resized : {array.Length}");
            }
            array[index] = value;
        }
    }
    public object Current
    {
        get { return array[position]; }
    }

    public IEnumerator GetEnumerator()
    {
        Console.WriteLine("GetEnumerator");

        return this;
    }

    public bool MoveNext()
    {
        Console.WriteLine("MoveNext");
        if(position==array.Length-1)
        {
            Reset();
            return false;
        }
        position++;
        return position < array.Length;
    }

    public void Reset()
    {
        Console.WriteLine("Reset");

        position = -1;
    }
}

class MainApp
{
    static void Main(string[] args)
    {
      MyList list=new MyList();
        for (int i = 0; i < 5; i++)
            list[i]=i;

        foreach(int e in list)
            Console.WriteLine(e);
    }
}