프날 오토핫키 강좌
누르면 강좌 리스트가 나와요
프날 오토핫키 강좌

[프날 오토핫키] 배열 #5: 연관 배열

지난 강까지의 내용을 요약하고 가겠습니다. 아래 내용 중 이해 안가는 부분이 있다면, 해당 강좌로 돌아가서 다시 숙지해주세요.

1. 배열은 연속되거나 연관된 값을 한번에 관리할 수 있게 해주는 자료구조의 일종입니다.

2. 오토핫키에서 배열은 객체입니다.

3. 객체 안에는 필드와 메서드라고 불리는 '변수'와 '함수'가 있습니다. 여기서 변수는 속성이라고도 합니다. 필드와 메서드를 묶어 '멤버'라고 부릅니다 (예를 들어서, 필드는 '멤버 변수', 메서드는 '멤버 함수'입니다.)

4. 오토핫키에서 배열은 객체이기 때문에, 배열에도 필드와 메서드가 있습니다. 단순 배열 기준으로 필드는 인덱스이며, 메서드는 Push(), Pop(), InsertAt(), RemoveAt(), Length() 등이 있습니다.

5. 객체의 멤버에 접근하는 방법은 '멤버 접근 연산자'인 마침표(.)를이용하는 것입니다. [객체명.멤버]로 쓸 수 있습니다. 예를 들어서 Car 객체의 Color 필드에 접근하고자 하면 Car.Color로, Drive() 메서드에 접근하고자 하면 Car.Drive()로 접근할 수 있습니다. 따라서 배열의 Push() 메서드는 배열명.Push()로 호출할 수 있겠네요.

6. 배열의 각 인덱스엔 '배열 인덱스 연산자'혹은 '첨자 연산자'라고 불리는 대괄호쌍([])을 이용하여 접근할 수 있습니다. 예를 들어서, 배열의 첫 번째 요소에 접근하고 싶다면 대괄호 사이에 인덱스를 넣어주어 배열명[1] 처럼 적어줄 수 있습니다. 이렇게 인덱스를 이용하여 각 요소에 접근하는 배열을 '단순 배열'이라고 합니다.

7. 단순 배열의 선언은 '배열명 := []'이고, 할당 및 사용은 '배열명[인덱스] := 할당할 값'입니다. 선언과 동시에 할당할수도 있습니다. 배열명 := [요소1, 요소2, 요소3, ...]처럼 말입니다.

8. 모든 배열은 표현식입니다.

 

위 내용이 지난 강좌까지의 내용입니다. 단순 배열보다 객체에 관해 더 많이 배운 것 같죠? 그만큼 객체에 대해 개념이 어느정도는 잡혀있어야 오토핫키에서 배열을 사용하기 쉽습니다. 특히 이번에 배울 '연관 배열'은 오토핫키 배열이 객체라는 것을 느끼게 해줄 것입니다. 이때까지 내용이 이해 되었다면 연관 배열은 쉽습니다. 단순 배열과 크게 다를게 없기 때문입니다.

 


 연관 배열 

 

연관 배열(Associative array)은 종종 '키-값 배열' 혹은 '키밸류'라고도 부릅니다. 공식적인 다른 용어는 "해시 맵" 혹은 "해시 테이블"이라고 부릅니다. 지난번에 배웠던 단순 배열은 '인덱스'와 '요소'가 일대일 대응 되었는데, 연관 배열은 '키'와 '값'이 일대일 대응 되는 배열을 의미합니다. 키는 무엇인지, 연관 배열은 어떻게 쓰는지에 대해 설명드리도록 하겠습니다.

 

'값'은 아마 아실겁니다. 짐작하듯이 배열의 요소를 값이라고 합니다. 지난 강좌에서 값을 많이 할당해보았을 것입니다.

배열[인덱스] := 값

 

위와 같은 단순 배열에서 값은 인덱스와 대응됩니다. 첫번째 인덱스의 값은 "가", 두번째 인덱스의 값은 "나" 처럼요. 그런데 연관 배열은 값이 인덱스가 아닌 '키'와 대응됩니다.

배열[키] := 값

 

즉, 기존엔 인덱스로 값을 할당하거나 사용하거나 했는데, 연관 배열을 사용하면 인덱스를 몰라도 '키'로 배열의 값을 가져올 수 있다는 것입니다. '키'는 문자열이나 숫자, 혹은 다른 객체가 올 수 있습니다. 보통은 문자열을 써주는 것이 일반적입니다. 숫자를 쓸 경우엔 단순 배열 형태를 주로 쓰기 때문입니다. 예를 들어서 아래와 같습니다.

MsgBox, % car["color"]

 

단순 배열과 동일한데 인덱스 대신 키 문자열이 들어간 것을 알 수 있습니다. 이렇듯 연관 배열을 쓰면 숫자가 아닌 문자 형태의 '키'를 사용할 수 있고, 따라서 가독성이 훨씬 높아지는 결과를 얻을 수 있습니다. 아래는 단순 배열을 써서 자동차의 속성을 담은 car[] 배열과의 비교입니다.

MsgBox, % "이 자동차의 색은 " car[1] "이고, 크기는 " car[2] "입니다."
MsgBox, % "이 자동차의 색은 " car["color"] "이고, 크기는 " car["size"] "입니다."

연관 배열을 사용한 것이 더욱 알아보기가 쉽죠?


 연관 배열의 선언과 할당, 사용 

 

연관 배열을 선언과 할당 해봅시다. 단순 배열에선 []를 이용하여 선언 및 할당 해주었지요. 연관 배열도 거의 같지만, 선언시 []가 아닌 {}를 사용한다는 차이점이 있습니다.

arr := {}
arr["키"] := 값

 

선언과 동시에 할당 할때는, 혹은 키와 값을 여러개 넣을 때는 아래와 같습니다. 키 뒤에 콜론(:)으로 값을 써주고, 각 요소 사이는 콤마로 구분해주었음을 알 수 있습니다. 그러니까 단순 배열에서 '키'를 콜론으로 값과 이어준 것 뿐이네요. 크게 어렵지 않은 형태입니다.

arr := {키1:값1, 키2:값2, 키3:값3, ...}

 

예를 들어서, car라는 배열에 color, size, form을 키로 하여 값을 넣으면 아래와 같습니다. 

car := {"color": "빨간색", "size": 5300, "form": "세단"}

 

값을 불러와볼까요? 키를 첨자 연산자[]에 넣어주시면 됩니다. (표현식에서 문자열은 따옴표처리! 이젠 잘 아시죠?)

MsgBox, % "이 자동차의 색은 " car["color"] "이고, 크기는 " car["size"] "입니다."
MsgBox, % "형태는 " car["form"] "입니다."

연관 배열 1.ahk
0.00MB

 

 


 연관 배열에서 사용할 수 있는 메서드 

 

연관 배열도 당연히 객체입니다. 메서드를 사용할 수 있다는 뜻이지요. 연관 배열에서 사용할 수 있는, '키'를 이용하는 메서드 두 개가 있습니다. 또, 키를 사용하진 않지만 연관 배열에서 쓰는 또다른 메서드가 한개 더 있습니다.

 

1. Delete()

해당 키-값 쌍을 지우는 메서드입니다. RemoveAt()과 동일한 동작이지만, 인덱스가 아닌 키를 받네요. Delete("name")처럼 쓰면 name이라는 키와 그 키가 가진 값을 지웁니다.

dog := {"name": "바둑이", "age": 2}
dog.Delete("name")

→ dog 배열은 age값만 가지게 됩니다.

 

2. HasKey()

연관 배열에 해당하는 키가 있는지 확인합니다. 존재한다면 참(true)을, 없으면 거짓(false)를 반환합니다.

dog := {"name": "바둑이", "age": 2}
dog.Delete("name")
MsgBox, % dog.HasKey("name")

→ dog 배열엔 name값이 삭제되었으므로 HasKey는 false(0)를 반환할 것입니다.

연관 배열 2.ahk
0.00MB

 

3. Count()

단순 배열과 달리 Length()는 정상적으로 작동하지 않을 것입니다! 연관 배열의 길이를 가져오고 싶으시면 Count() 메서드를 호출하는 것이 좋습니다. 사용법은 Length()와 동일하니 생략하겠습니다.

 

여담으로, 인덱스 접근이 불가능하기 때문에 연관 배열에서 값을 순차적으로 어떻게 탐색하는지에 대해 궁금하신 분도 있을 것입니다. 이럴땐 for-loop를 쓰면 됩니다.

for-loop는 아래와 같이 씁니다.

for Key [, Value] in 배열(객체)
{
    ..// Key변수에 키, Value 변수에 값이 담깁니다.
    ..// 배열을 전부 탐색한 뒤 자동으로 탈출됩니다.
    ..// value를 생략하면 키만 탐색합니다 (그래도 arr[key]로 값을 가져올 순 있겠죠?)
}

즉, 위 예제 2의 dog 배열을 for-loop로 탐색하려면 

dog := {"name": "바둑이", "age": 2}
for key, val in dog
{
    MsgBox, % A_index "번째로 가져온 키는 " key ", 값은 " val "입니다."
}

처럼 쓰면 됩니다. 

연관 배열 3.ahk
0.00MB

 

그런데 키를 첫번째 요소부터 안가져오고, age부터 가져오네요.

 

여기서 알 수 있는 사실은, 연관 배열은 자료를 선형적으로 저장하지 않는다는 것입니다. 즉, 첫번째로 쓴 값이 1번 인덱스로 저장되지는 않습니다. 해시 알고리즘을 통해서 키를 이용해 만든 특정 위치에 자료를 넣어두어, 연관 배열에 자료가 아무리 많아도 순식간에 찾을 수 있도록 구현해두었기 때문입니다. 만약 연관 배열이 선형적으로 이루어져있다면 특정 키가 나올때까지 1번 인덱스부터 순차탐색 했어야겠죠. 이러한 성질 때문에 연관 배열은 키가 아무리 많아도 탐색 속도가 느려지지 않는다는 장점을 가지고 있습니다. 


 

Q. 배열은 객체라면서요, 그리고 단순 배열의 필드는 인덱스였습니다. 비록 비권장 형태라곤 하나 arr.1 의 형태로 인덱스 접근이 가능했는데, 그렇다면 연관 배열도 arr.key의 형태로 접근이 가능합니까?

A. 네. 가능합니다. 배열은 객체이고, 당연히 멤버 접근 연산자를 사용하여 그 필드에 접근할 수 있습니다. 그리고, 연관 배열에서 키를 이렇게 써주는 것은 비권장 방법이 아닙니다! 단순 배열에서 arr.1 arr.2 등으로 인덱스 접근을 하는 것은 가독성과 통일성을 위해 비권장하였으나, 연관 배열에서 arr.key형태로 필드 접근을 한다면 이는 보기에도 좋으므로 웬만하면 허용됩니다. 예를 들어서, 위의 자동차 예제는 car.color, car.size 등으로 쓸 수 있습니다. 단순 배열보다 객체의 향이 물씬 나죠? 실제로 연관 배열은 Object()라는 객체 생성 함수를 이용하여도 선언할 수 있습니다. (여기서는 더욱 단순한 arr := {} 형태를 강좌했지만요). 아무튼, 연관 배열은 (배열명.키)의 형태로 접근해도 좋습니다. 가독성이 떨어지지 않기 때문입니다. 이질적인 형태도 아니고요.

Q. 연관 배열을 선언과 동시에 할당할 때 실수로 키에 따옴표를 안쳤습니다. 그런데 정상 작동하는데 왜죠?

A. 배열 안은 표현식이라서 문자열엔 따옴표를 붙이는게 원칙입니다. 다만 오토핫키에서 연관 배열 선언시 '키' 자리에 따옴표를 붙이지 않아도 변수 취급을 하지 않도록 예외를 두었습니다. 즉, 위의 '연관 배열 2.ahk' 예제에서 dog := {name: "바둑이"} 처럼 키에 따옴표를 붙이지 않아도 잘 작동합니다. 다만 강좌에선 여러분이 헷갈리실까봐 표현식 자리에 문자열은 항상 따옴표 표시를 해주었다는 점을 말씀드립니다.

 


 

 

 

 

 

 

반응형