Python에서 실행 중이던 프로그램을 종료시키는 방법에는 아래 4가지가 있다.

1. quit()
2. exit()
3. sys.exit([arg])
4. os._exit([arg])


1. quit() 를 사용하는 방법

quit() 메소드를 이용하여 종료 작업을 수행할 경우 quit() 는 실행 중이던 프로그램뿐만 아니라 python 쉘까지 완전히 닫아버린다.
이러한 이유로 quit()는 python 쉘에서만 사용해야하고 프로그램 내에서는 사용하지 않는 것을 권장하고 있다.

 

2. exit() 를 사용하는 방법

exit() 메소드 역시 python 프로그램을 종료시킬 수 있는 메소드이지만 quit() 와 비슷하게 실행 중이던 프로그램뿐만 아니라 python 프로세스까지 완전히 종료시킨다. 따라서 python 쉘에서 사용하도록 하고 프로그램 내에서는 사용하지 않을 것을 권장한다.

 

3. sys.exit([arg]) 를 사용하는 방법

sys.exit()는 import sys 를 필요로 한다.
quit()나 exit()와는 달리, 쉘까지 날리지는 않으면서 프로그램만 정상적으로 종료시킬 수 있다.
특히 sys.exit() 메소드는 인자를 받는데, 0의 경우 정상적인 종료를 의미한다.
이러한 이유로 sys.exit()는 프로그램 내에서의 종료 동작을 수행하기 위한 가장 적합한 방법으로 소개된다.

sys.exit()의 인자로 0 이외의 다른 값을 사용할 경우 비정상적인 종료를 의미하는데,
아무런 인자도 사용하지 않을 경우에는 정상적인 종료(default: 0)를 하는 것으로 여겨진다.
0 이외의 값도 사용할 수 있다고는 하지만 대부분의 시스템에서는 이 값으로 0-127 범위의 정수를 사용하도록 한다.

인자로 정수가 아닌 오류 메세지를 입력할 경우 이를 출력하면서 종료시킬 수 있어, 예외 처리를 하는 데에도 유용하게 쓰일 수 있을 것 같다.

정상적인 종료를 할 경우
메세지를 출력하도록 하는 경우

 

4. os._exit([arg]) 를 사용하는 방법

os._exit()는 import os를 필요로 한다.
sys.exit()와 같이 프로그램만을 종료할 수 있도록 하지만, os._exit()에서는 인자로 상태 코드가 꼭 필요하다.
(python.org에서는 인자가 필수가 아니라고 하지만, 테스트해보았을 때에는 인자없이 실행할 경우 에러가 발생하였다..)
정상적인 종료를 의미할 경우 os.EX_OK 를 인자로 넘겨주면 된다.
이것 외의 다른 상태 코드는 아래 링크에서 확인할 수 있다.

 

os — Miscellaneous operating system interfaces — Python 3.9.5 documentation

os — Miscellaneous operating system interfaces Source code: Lib/os.py This module provides a portable way of using operating system dependent functionality. If you just want to read or write a file see open(), if you want to manipulate paths, see the os.

docs.python.org

 

정상적인 종료를 할 경우

 

다음에 또 다시 이런 에러를 만나게 되었을 때 꺼내보기 위한 글


오류 상황1

$ sudo apt-get update
...
Temporary failure resolving 'us.archive.ubuntu.com'
...

상황1 해결

$ sudo vi /etc/resolv.conf

# 아래 내용 추가
nameserver 8.8.8.8
nameserver 8.8.4.4
 

[Linux] Ubuntu 업데이트 서버 연결 에러 (apt-get update 에러)

Q : 상황 사용 버전 : Ubuntu 12.04.2 LTS 서버 윈도우 상에서 VMPlayer를 이용하여 우분투를 설치하였고 네트워크는 NAT 외부접속을 위한 NAT Setting을 마친 상태이다. apt-get install을 통한 ssh 등은 잘 설..

notpeelbean.tistory.com

 

스택만큼이나 기본적인 자료구조인 큐에 대해 알아보려고 한다.

 

큐란

가장 먼저 삽입된 데이터가 가장 먼저 삭제되는 형태의 자료구조이다.

이러한 형태를 선입선출(FIFO, First-In First-Out)이라 한다.

 

큐는 특히 일상생활에서 매우 쉽게 볼 수 있는 형태의 자료구조인데, 가장 쉽게 생각해볼 수 있는 것이 줄을 서는 것이다.

일렬로 줄을 서서 어떤 서비스를 받기 위한 차례를 기다릴 때 제일 먼저 줄을 선 사람이 제일 먼저 서비스를 받는다.

이것이 큐이다.

 

스택에서와 달리 큐를 구현할 때에는 데이터의 맨 앞과 맨 뒤를 가리키도록 하는 변수가 각각 필요하다.

데이터의 맨 앞에서는 삭제가 일어나고 맨 뒤에서는 삽입이 일어난다.

여기에서 맨 앞을 가리키는 것을 front라 하고 맨 뒤를 가리키는 것을 rear라 한다.

이러한 개념을 가지고 큐를 직접 구현해보면서 동작 방식에 대해 설명하겠다. (구현에는 C언어를 사용하였다.)

쉬운 이해를 위해 선형 배열을 사용하여 구현하려고 한다. (선형 배열을 사용할 경우에 발생하는 문제는 잠시 후에 살펴보려고 한다.)


1. 초기화

#define SIZE 5

int queue[SIZE] = { 0 };
int front = 0, rear = 0;

우선 크기가 5인 배열을 선언하고 0으로 초기화를 하였다.

그런 뒤, front와 rear의 값을 모두 0으로 초기화하였는데, 이 값은 구현하려고 하는 방식에 따라 달라질 수 있다.

여기서 front에는 삭제가 될 곳을 가리키는 곳을 저장하도록 하였고, rear는 추가가 될 곳을 저장하도록 하였다.

 

 

2. 삽입

다음으로, 큐에 데이터를 추가하는 메소드인 enqueue()를 구현해보자.

void enqueue(int *queue, int front, int *rear, int data){
	if(*rear - front + 1 == SIZE){
    	fullQueueException();
    }
    
    queue[*rear] = data;
    *rear += 1
}

큐에 데이터를 추가할 때에는 front의 값은 변화하지 않으므로 rear만 call by reference 형태로 하였다.

배열을 사용하여 구현하고 있기 때문에 최대 삽입 가능 원소 개수에 제한이 있는 배열의 특성상 가득 찰 경우에 대한 예외 처리를 해주어야 한다.

만약 큐가 가득 찬 상태가 아닐 경우 queue의 rear 위치에 data를 넣고 rear의 값을 하나 증가시킨다.

 

 

3. 삭제

이번에는 큐에 있는 데이터를 삭제하는 메소드인 dequeue()를 구현해보자.

int dequeue(int *queue, int *front, int rear){
	int data;
	if(rear == front){
    	emptyQueueException();
    }
    
    data = queue[*front];
    *front += 1;
    return data;
}

dequeue를 할 때에는 front에서 작업이 일어나므로 값이 변경될 일이 없는 rear는 call by value로 넘겨받도록 하였다.

또한, 큐에 있는 데이터를 삭제할 때에는 구현방법과 관계없이 큐가 비어있을 때에 대한 예외처리를 꼭 해주어야 한다.

큐가 비어있지 않을 경우에는 큐의 front 에 위치한 값을 저장해둔 뒤에 front의 값을 하나 증가시킨다.

 

이렇게 선형 배열을 사용하여 큐를 구현하면 구현이 단순하다는 장점은 있지만.. 일회성 큐가 되어버린다.

왜냐하면 지금과 같이 크기가 5인 배열을 이용하여 큐를 만들게 되면

5개의 값을 삽입하고 5개의 값을 삭제할 경우 front와 rear값이 모두 배열의 마지막 인덱스를 가리키게 될 것인데,

이후로는 더 이상 가리킬 수 있는 곳이 없어 데이터를 삽입하거나 삭제할 수 없다.

따라서 이를 해결하기 위해 원형 배열이라는 것을 사용하여 다시 구현해보려고 한다.


 

원형 배열이란

물리적으로는 선형 배열과 다를 것이 없는 일반 배열이지만,

프로그래밍을 할 때 배열의 인덱스를 나타내는 값을 증가시켜줌과 동시에 그것을 다시 배열의 크기로 나눈 나머지를 인덱스로 이용하는 것이다.

 

Circle Queue

원형 배열을 사용할 경우 배열의 인덱스를 마지막 위치(7)까지 가리키게 된다고 하더라도, 여기에 하나 증가시킨 값인 8을 배열의 크기인 8로 나눠 다시 0번째 인덱스를 가리키는 방식으로 사용할 수 있으므로, 배열이 가득 차지 않는 한 배열의 나머지 공간을 계속해서 사용할 수 있게 된다.

 

이러한 개념을 이용하여 enqueue와 dequeue를 다시 구현하면 다음과 같다.

#define SIZE 5

int queue[5] = { 0 };
int front = 0, rear = 0;

void enqueue(int *queue, int front, int *rear, int data){
	if((SIZE + rear - front) % SIZE == SIZE){
    	fullQueueException();
    }
    
    queue[*rear] = data;
    *rear = (*rear + 1) % SIZE;
}

int dequeue(int *queue, int *front, int rear){
	int data;
    
    if(front == *rear){
    	emptyQueueException();
    }
    
    data = queue[*front];
    *front = (*front + 1) % SIZE;
    return data;
}

enqueue와 dequeue에서 각각에 대한 처리를 한 후, front와 rear에 1을 더해주지만 이 때 더해준 값을 배열의 전체 크기로 나눈 나머지 값을 front와 rear의 값으로 사용하기 때문에 배열의 범위를 넘어서게 되더라도 다시 0번 인덱스로 돌아오게 된다.

 

이렇게 원형 배열을 이용하여 구현하면 단순 선형 배열로 큐를 구현할 때에 발생되는 일회적인 사용에 대한 문제는 해결이 된다.

하지만 원형 배열은 새로운 문제점을 야기한다.

바로, 배열이 비어있는 상태일 때와 가득찬 상태에 대한 구분이 안된다는 것이다.

 

이렇게 코딩을 한 후 실행하면 배열이 꽉 찼음에도 불구하고 비어있는 배열처럼 인식하여 fullQueueException이 발령되지 않는다.

배열이 비어있는 경우 front와 rear 값이 같아지게 되는데, 배열이 꽉 찼을 때에도 front와 rear값이 같아지기 때문이다.

여기에는 이를 해결하기 위한 두 가지 방법이 있다.

첫 번째는 배열에 저장되어있는 원소의 개수를 저장하는 것이고,

두 번째는 배열의 한 자리를 남겨둔 채로 사용하는 것이다.

 

첫 번째 방법을 사용하면 배열의 메모리를 모두 사용할 수 있지만, 매번 저장되어있는 원소의 개수를 유지해야 한다는 단점이 있고

두 번째 방법을 사용하면 배열의 메모리 한 칸을 낭비하게 되지만,  원소의 개수를 유지하지 않아도 된다.

둘 중 어느 방법을 사용할지는 개발하는 사람의 몫이다.

각각의 방법을 사용하여 구현한 경우를 남겨두겠다.


- 첫번째: 원소의 개수를 저장하는 방법

#define SIZE 5

int queue[SIZE] = { 0 };
int front = 0, rear = 0;
int count = 0; // 원소의 갯수를 저장할 변수

void enqueue(int *queue, int front, int *rear, int *count, int data){
	if(*count == SIZE){
    	fullQueueException();
    }
    
    queue[*rear] = data;
    *rear = (*rear + 1) % SIZE;
    *count += 1;
}

int dequeue(int *queue, int *front, int rear, int *count){
	int data;
    
    if(*count == 0){
    	emptyQueueException();
    }
    
    data = queue[*front];
    *front = (*front + 1) % SIZE;
    *count -= 1;
    return data;
}

 

- 두번째: 배열의 한 칸을 비우고 사용하는 방법

#define SIZE 5

int queue[SIZE] = { 0 };
int front = 0, rear = 0;

void enqueue(int *queue, int front, int *rear, int data){
	if((*rear + 1) % SIZE == front){
    	fullQueueException();
    }
    
    queue[*rear] = data;
    *rear = (*rear + 1) % SIZE;
}

int dequeue(int *queue, int *front, int rear){
	int data;
    
	if(*front == rear){
    	emptyQueueException();
    }
    
    data = queue[*front];
    *front = (*front + 1) % SIZE;
    return data;
}

시간 복잡도

배열을 사용하여 큐를 구현한 경우 각 작업의 시간 복잡도는 선형 배열에서와 원형 배열에서 모두 O(1)이 된다.

 

그 밖에도 배열로 큐를 구현하여 사용하게 될 경우, 배열의 특성상 front와 rear 위치가 아닌 다른 위치의 인덱스에도 곧장 접근이 가능하다. 그러나 큐를 큐답게 쓰기 위해서는 꼭 enqueue와 dequeue 작업을 이용하여 삽입과 삭제 위치를 지키면서 사용해야 한다.

 

끝으로 연결 리스트를 이용하여 큐를 구현한 코드를 첨부하고 마무리하려고 한다.

연결리스트를 이용하여 큐를 구현한 경우에도 시간 복잡도는 배열에서와 마찬가지로 O(1)이 되고,

배열에서와 달리 메모리의 한계가 존재하지 않으므로 fullQueueException 처리를 할 필요가 없게 된다.

#include <stdio.h>
#include <stdlib.h>

typedef struct _node {
	int data;
    struct _node *next;
} NODE;

int *front = NULL, *rear = NULL;

NODE* getnode(int data){
	NODE *node = (NODE*)malloc(sizeof(NODE));
    node->next = NULL;
    return node;
}

void enqueue(int **front, int **rear, int data){
	NODE *node = getnode(data);
    
    if(*rear == NULL) *front = node;
    else (*rear)->next = node;
    *rear = node;
}

int dequeue(int **front, int **rear){
	int data;
    NODE *tmp = NULL;
    
    if(*front == NULL){
    	emptyQueueException();
    }
    
    data = (*front)->data;
    tmp = *front;
    *front = (*front)->next;
    
    if(*front == NULL) *rear = NULL; // 값을 삭제한 뒤 남은 노드가 없는 경우 rear에도 NULL 대입
    
    free(tmp);
    return data;
}

 

 

'DataStructure' 카테고리의 다른 글

힙(Heap)의 정의와 구현  (0) 2021.10.22
스택(Stack)의 정의와 구현  (0) 2021.05.17

자료구조의 가장 기본이 되면서, 실제로 정말 많이 쓰이고 있는 스택에 대해 알아볼 것이다.

 

스택이란

쉽게 말해 가장 최근에 들어온 데이터를 가장 먼저 빼내는 형태의 자료구조이다.

이를 후입 선출(LIFO, Last-In First-Out)이라 한다.

 

위의 이미지도 스택의 한 예가 될 수 있다.

여러 권의 책을 차례로 쌓아놓고 이 중 한 권의 책을 들어 올리라고 했을 때, 중간이나 맨 아래의 책보다는 맨 위의 책을 들어 올리는 것이 가장 자연스러울 것이다.

 

하지만 무작정 맨 위의 것을 선택하라는 것은 인간의 사고방식이지,

컴퓨터에게는 어떤 데이터가 맨 위에 있는 것인지를 알려줄 필요가 있다.

이 때 사용되는 것이 top이다.

 

top은 항상 스택에 마지막으로 들어온 데이터를 가리키며, 이것을 이용해 스택에 데이터를 삽입하거나 삭제할 수 있다.

 

스택을 직접 구현하면서 동작 방식에 대해 살펴보기로 하자. (구현에는 C언어를 사용하였다.)

쉬운 이해를 위해 선형 배열을 사용하여 구현하려고 한다.

 


1. 초기화

#define SIZE 5

int stack[SIZE] = { 0 };
int top = -1;

 

우선 크기가 5인 배열을 선언하고 0으로 초기화해주었다.

그 후 top을 저장하기 위한 변수를 선언하였다.

여기서 top을 -1로 초기화한 이유는 top이 가장 마지막 데이터가 있는 인덱스를 저장하고 있게 하기 위함이다.

 

이것은 구현하려고 하는 방식에 따라 달라질 수 있는데,

만약 top이 미래에 데이터를 추가할 인덱스를 가리키도록 할 경우 top을 0으로 초기화할 수도 있다.

 

 

2. 삽입

다음으로 스택에 원소를 추가하는 메소드인 push()를 구현해보자.

void push(int *stack, int *top, int element){
	if(*top == SIZE-1){
    	fullStackException();
    }
    
    *top += 1;
    stack[top] = element;
}

 

스택을 배열로 구현할 경우 배열에 저장할 수 있는 원소의 개수에는 한계가 있다는 것을 생각하고 꼭 예외 처리를 해주어야 한다.

그렇지 않으면 배열이 가득찬 상태에서 push를 시도할 경우 런타임 오류가 발생하고 말 것이다.

배열의 이러한 한계는 연결리스트로 구현할 경우 해결할 수 있다.

 

스택에 아직 여유가 있을 경우 top을 하나 증가시켜준 뒤 값을 넣는다.

 

 

3. 삭제

이번에는 스택의 원소를 삭제하는 메소드인 pop()을 구현해보자.

int pop(int *stack, int *top){
	int element;
    
    if(*top == -1){
    	emptyStackException();
    }
    
    element = stack[*top];
    *top -= 1;
    return element;
}

여기서도 마찬가지로 스택이 비어있을 때 삭제를 시도할 경우에 대해 예외 처리를 해주어야 한다.

스택이 비어있는 것에 대한 예외처리는 연결리스트로 구현하려고 할 때에도 반드시 있어야 하는 부분이다.

 

그 후, 스택의 top에 저장되어 있는 데이터를 저장해 두고 top의 값을 하나 감소시킨 뒤 저장해둔 데이터를 반환한다.

 


시간복잡도

이렇게 배열을 사용하여 스택을 구현할 경우 각 작업의 시간복잡도는 O(1)이 된다.

 

이밖에도

배열을 사용하였으므로 추가나 삭제가 아닌 단순 접근을 할 때 굳이 top을 이용하지 않고도 나머지 원소에 접근할 수 있게 되는데,

정말 스택을 스택답게 이용하기 위해서는 스택에 있는 데이터에 접근할 때에도 반드시 top을 이용해서 마지막 원소에만 접근하도록 해야 한다.

 

 

마지막으로 스택을 연결리스트로 구현한 코드를 첨부하고 마무리 하려고 한다.

앞서 배열을 사용한 스택의 push에서 배열의 한계 때문에 fullStackException이 발생하였는데, 이 경우에는 그것에 대한 예외 처리를 해주지 않아도 된다.

또한 연결리스트로 구현하였을 경우에도 각 작업의 시간복잡도는 O(1)이 된다.

#include <stdio.h>
#include <stdlib.h>

typedef struct _node {
	int value;
    struct _node *next;
} NODE;

NODE* getnode(int value){
	NODE *node = (NODE*)malloc(sizeof(NODE));
    node->value = value;
    node->next = NULL;
    return node;
}

void initialize(int **top){
	*top = NULL;
}

void push(int **top, int element){
	NODE *node = getnode(element);
    
    node->next = *top;
    *top = node;
}

int pop(int **top){
	int element;
    NODE *tmp;

	if(*top == NULL) emptyStackException();

	element = (*top)->value;
    tmp = *top;
    *top = (*top)->next;

	free(tmp);
    return element;
}

 

 

 

'DataStructure' 카테고리의 다른 글

힙(Heap)의 정의와 구현  (0) 2021.10.22
큐(Queue)의 정의와 구현  (0) 2021.05.17

1. 리스트

C언어에서의 배열과 비슷하며, 같은 형태의 여러 자료를 함께 관리하기에 좋다.

다만 배열과의 차이점이라고 할 수 있는 것은,

배열은 하나의 배열에 저장되어 있는 데이터의 자료형이 모두 같아야 하지만,

리스트에서는 서로 다른 자료형의 데이터를 하나의 리스트에 저장할 수 있다. (그러나 대게 같은 자료형의 데이터를 저장함)

 

1) 선언 및 초기화

 

빈 리스트를 선언하고자 할 때에는 다음과 같이 한다.

# 빈 리스트 선언
l = []

 

리스트를 선언과 함께 초기화까지 하고 싶은 경우 다음과 같이 작성할 수 있다.

l = [0]

 

 

이렇게 할 경우 리스트의 원소 자리 하나만을 0으로 초기화하는 것이 된다.

현재 l의 길이는 1이 된다.

그렇다면 리스트의 크기를 3으로 하면서 0으로 초기화하고 싶은 경우에는 다음과 같이 작성할 수 있을 것이다.

l = [0, 0, 0]

 

그렇다면 리스트의 크기를 100으로 하고 싶을 때에는 0을 100번 써주어야 하는 것일까?

이 경우에는 다음의 방법을 이용해 좀 더 편리하게 초기화를 할 수 있다.

l = [0] * 100

 

 

2) 원소 접근 및 수정

C언어의 배열과 같이 Python의 리스트에서도 리스트의 첫 원소의 인덱스 번호는 0부터 시작한다.

그렇기 때문에 제일 첫 번째 원소에 접근하고 싶은 경우에는 다음과 같이 0에 접근해야 한다.

l = [0, 2, 3]

print(l[0]) # 0
print(l[2]) # 3

l[0] = 1 # l = [1, 2, 3]
print(l[0]) # 1

 

 

3) 원소 추가

이미 선언된 리스트에 새로운 원소를 추가하고 싶은 경우 다음의 방법을 사용할 수 있다.

l.append(4) # l = [1, 2, 3, 4]

 

 

리스트의 중간에 원소를 추가하고 싶은 경우 다음의 방법을 사용한다.

l.insert(1, 5) # l = [1, 5, 2, 3, 4]

insert 함수의 첫 번째 인자로 추가할 원소가 갖게 될 인덱스를 입력하고, 두 번째 인자로 추가할 원소의 값을 입력하면 된다.

 

 

4) 원소 삭제

리스트의 값을 기준으로 원소를 삭제하고 싶은 경우

만일, 리스트에 원하는 값이 중복될 경우 인덱스 번호가 가장 낮은 위치에 있는 원소를 삭제한다.

l.remove(2) # l = [1, 5, 3, 4]

 

리스트의 인덱스를 기준으로 원소를 삭제하고 싶은 경우

만일, 인덱스를 지정해주지 않으면 리스트의 가장 마지막 원소를 삭제하고 반환한다.

e = l.pop(1) # l = [1, 3, 4]
print(e) # 5

* pop() 말고 del 키워드를 이용하는 방법도 있다. 이 경우에는 삭제되는 원소를 반환하지 않는다.

del l[2] # l = [1, 3]

 

리스트의 모든 원소를 삭제하고 싶은 경우

l.clear() # l = []
# 또는
del l[:]

 


 

2. 튜플

튜플은 리스트와 비슷하지만 변경할 수 없어, 참조형 리스트를 만들 때 사용된다.

 

1) 초기화

리스트는 대괄호[]를 사용했다면 튜플은 소괄호()를 사용한다.

또한 튜플은 단순히 여러 데이터를 콤마로 연결한 것으로 초기화를 할 수도 있다.

t1 = (1, 2, 3) # type(t1) : tuple
t2 = 1, 2, 3 # type(t2) : tuple

 

 

2) 튜플의 데이터가 변경될 수 있는 경우

튜플 자료형 자체는 원래 그 안의 데이터를 변경할 수 없게 되어있다.

그러나 튜플 안의 데이터가 리스트라면?

튜플에 또 다른 데이터를 추가하거나 수정하는 것은 안되지만, 튜플 내의 리스트를 수정하는 것은 가능하다.

t = (1, [1, 2, 3])

t[0] = 2 ## 사용 불가능한 코드

t[1][0] = 0 # t = (1, [0, 2, 3])
t[1].append(4) # t = (1, [0, 2, 3, 4])
t[1].clear() # t = (1, [])

 


 

3. 집합

집합은 중복된 요소가 없는 리스트라고 생각하면 된다.

중괄호{}를 사용하고, 집합을 사용하면 합집합, 교집합, 차집합과 같은 집합 연산이 가능하다.

따라서 대게 리스트의 중복 요소를 제거하거나 집합 연산이 필요할 때 사용된다.

 

1) 선언 및 초기화

여기서 주의해야할 점은 빈 집합을 선언하려고 할 때 s = {} 라고 할 경우, 빈 집합이 아닌 빈 딕셔너리가 선언된다.

또한 집합 초기화 시 중복된 원소를 입력하면 집합의 원소에는 입력된 값 중 중복된 원소를 제거한 나머지 원소들만 저장이 된다.

# 빈 집합 선언
s = set()

# 집합 초기화
s = { 1, 2, 3, 2, 3, 4 }

print(s) # { 1, 2, 3, 4 }

 

 

2) 원소 추가 및 삭제

# add(): 추가
>>> a.add(5)
>>> a
{1, 2, 3, 4, 5}


# remove(): 삭제
>>> b.remove(8)
>>> b
{2, 4, 6}

 

 

3) 집합 연산 기능

a = { 1, 2, 3, 4 }
b = { 2, 4, 6, 8 }

# in, not in: 원소의 존재 유무 반환
>>> 1 in a
True
>>> 1 not in b
True

# &: 교집합 반환
>>> a & b
{2, 4}

# |: 합집합 반환
>>> a | b
{1, 2, 3, 4, 6, 8}

# -: 차집합 반환
>>> a - b
{1, 3}

 

여기서 교집합, 합집합, 차집합은 다음의 함수를 이용해서도 구할 수 있다.

# intersection(): 교집합 반환
>>> a.intersection(b)
{2, 4}

# union(): 합집합 반환
>>> a.union(b)
{1, 2, 3, 4, 6, 8}

# difference(): 차집합 반환
>>> a.difference(b)
{1, 3}

 


 

4. 딕셔너리

딕셔너리는 JSON 과 비슷하게 생각하면 좋을 것 같다.

 

리스트나 튜플과 마찬가지로 여러 데이터를 함께 이용할 때 사용하지만,

차이점이라면 딕셔너리는 각 데이터를 인덱스가 아닌 key값으로 접근한다.

따라서 key값은 딕셔너리 내에서 고유한 값이어야 한다.

 

딕셔너리는 중괄호{}를 사용한다.

 

1) 선언 및 초기화

# 빈 딕셔너리 선언
d = {}

# 딕셔너리 초기화
d = { 'key' : 'value' }

 

만약 딕셔너리 초기화 시 동일한 key값을 입력하면 오류가 발생할까?

d = { 'key' : 'value1', 'key' : 'value2' }
print(d) # { 'key' : 'value2' }

오류는 발생하지 않고, 동일한 key값이 입력되면 value는 가장 마지막에 입력한 값으로 초기화되는 것을 알 수 있다.

 

 

2) 원소 접근 및 수정

인덱스 대신 key를 사용하므로 원소에 접근할 때에도 key값을 사용하여 접근하면 된다.

d = { 'a' : 'A', 'b' : 'B', 'c' : 'C' }

print(d['a']) # 'A'

d['a'] = 'a' # d = { 'a' : 'a', 'b' : 'B', 'c' : 'C' }

 

 

3) 원소 추가

딕셔너리에 새로운 원소를 추가하고 싶은 경우

추가하고자 하는 key와 value를 이용해, 원소에 접근할 때와 마찬가지 문법으로 추가할 수 있다.

d['d'] = 'D'

# d = { 'a' : 'a', 'b' : 'B', 'c' : 'C', 'd' : 'D' }

 

 

4) 원소 삭제

딕셔너리의 원소를 삭제하고 싶은 경우

삭제하고자 하는 key값과 del 키워드를 이용해 삭제할 수 있다.

del dic['a']

# d = { 'b' : 'B', 'c' : 'C', 'd' : 'D' }

 

 

5) 딕셔너리 순회

딕셔너리는 key를 인덱스로 사용하기 때문에 순회를 하려고 할 때 난감하게 느껴질 수 있다.

그러나 다음의 함수를 사용하면 쉽게 해결이 된다.

 

- 1 : 순회할 때 key와 value 를 사용하려고 하는 경우, items()를 이용하면 된다.

d = { 'first' : 1, 'second' : 2, 'third' : 3 }

for key, value in d.items():
	print(key, value)

'''
print:

first 1
second 2
third 3
'''

 

- 2 : 순회할 때 index와 key를 사용하려고 하는 경우, enumerate()를 이용하면 된다.

d = { 'first' : 1, 'second' : 2, 'third' : 3 }

for index, key in enumerate(d):
	print(index, key)

'''
print:

0 first
1 second
2 third
'''

 

 

'Programming > Python' 카테고리의 다른 글

Ubuntu에서 uWSGI - Nginx 로 웹서버 배포하기  (0) 2021.09.11
[Python] 프로그램 종료  (0) 2021.06.12
[Python] 파일 입출력  (0) 2021.05.17

파일을 사용하는 전체적인 방식: 파일 열기 -> 파일 읽기/쓰기 -> 파일 닫기

 


 

1. 파일 열기

f = open(filename, mode [, encoding])

filename: 사용하고자 하는 파일 이름

mode: 사용하고자 하는 파일의 사용 목적(모드)

구분(mode) 텍스트 모드 이진 모드 방식
파일 입력 r rb 읽기(read)
파일 출력 w wb 쓰기(write)
a ab 추가(append)

* 텍스트 모드: 일반 텍스트 형태 그대로 사용하는 경우

* 이진 모드: 파일을 바이트 형태로 사용할 경우,

                   일반 텍스트 파일을 이진 모드로 열게되면 텍스트 글자를 바이트 단위로 사용할 수 있다.

 

* 인코딩 방식을 지정하는 경우(선택)

f = open("file.txt", "r", encoding="utf-8")

 

이렇게 만든 스트림을 f 객체에 넣어 이후 파일을 읽거나 쓸 때, 그리고 스트림을 다시 닫을 때 사용할 수 있도록 한다.

 

 

2. 파일 읽기/쓰기

- 파일 읽기 (mode: r)

1) 파일 내용 전부 읽기

str = f.read()

파일 내용이 전부 str 변수에 저장된다.

파일의 끝에 도달하면 빈 문자열('')을 반환한다.

 

2) 원하는 크기만 읽기

str = f.read(size)

size만큼의 내용만 str변수에 저장된다.

 

3) 파일 한 줄만 읽기

str = f.readline()

빈 문자열의 경우 대게 문자열의 끝에 붙게 되는 개행문자('\n')를 반환하고,

아예 파일의 끝에 도달한 경우 빈 문자열('')을 반환한다.

 

4) 파일의 모든 줄을 리스트로 읽기

s = list(f) # 또는
s = f.readlines()

 

파일의 모든 내용이 줄 단위로 리스트에 저장된다.

 

- 파일 쓰기 (mode: w)

f.write(value)

value 내용이 파일 안에 저장된다. (덮어쓰기 방식으로 저장.)

 

- 파일 쓰기 (mode: a)

f.write(value)

value 내용이 파일 안에 저장된다. (파일 안에 원래 다른 내용이 있었으면 그 내용 뒤에 덧붙여짐.)

 

- 파일 객체 위치 반환

f.tell()

파일 객체 위치를 반환한다.

f가 이진 모드로 열린 경우에는 바이트 수를 반환하고, 텍스트 모드로 열린 경우에는 글자수를 반환한다.

seek함수에의해 위치가 변경된 경우 변경된 위치를 반환한다.

 

- 파일 객체 위치 변경

f.seek(offset, whence) # whence: 0, 1, 2

기준점 whence에 offset을 더한 위치로 커서를 이동한다고 생각하면 된다.

그 위치로 이동시킨 후 원하는 읽기 또는 쓰기 작업을 실행하면 그 위치에서부터 읽거나 쓸 수 있다.

whence가 0이면 파일의 시작위치, 1이면 현재 위치, 2이면 파일의 끝이 기준점이 된다.

whence를 입력하지 않으면 기본으로 0에다가 offset을 더하는 것이 된다.

 

 

3. 파일 닫기

f.close()

동적할당에서 사용한 메모리를 꼭 free 시켜주는 것과 같이 파일 스트림 작업에서도 작업 후 꼭 close 시켜주도록 해야 한다.

 

 

 

+ Recent posts