심심해서 하는 블로그 :: 심심해서 하는 블로그
Tayasui Sketches 리뷰

Tayasui Sketches 리뷰

2019년 1월 기준 아이패드 프로 2세대와 애플 펜슬 1세대 사용 유저입니다.

그림을 발로 그리고 있는 초보자의 입장에서 작성된 리뷰입니다.



애플 펜슬도 샀겠다.

누구나 그렇듯 애플 펜슬을 샀으니 이제 내 마음 속에서 없던 화가 세포를 깨웁니다.

참고로 중학교, 고등학교 미술시간마다 C,D를 차지할 만큼 그림에 자신이 없지만, 자그마치 10만원 넘는 연필을 샀으니 (ㅠㅠ) 그림을 안 그려 볼 수는 없었습니다.


그림을 전문적으로 그리는 것이 아니라 무료 앱이 가장 큰 선정 기준이였습니다.

procreate가 좋은 건 금손 분들의 작업 영상 만으로도 충분히 느꼈지만, 발로 그리기 때문에 만 원이라는 거금을 선뜻 투자하긴 힘들었습니다.


다양한 무료 그림 앱이 있지만 Tayasui Sketches는 생산성 카테고리의 무료 앱에서 높은 순위를 오랫동안 차지하고 있는 앱이라 일단 설치해보고 그림을 발로 그려 보았습니다.

약 한 달동안 사용하면서 느낀 장단점을 소개해드리겠습니다.


장점 1 : 필요한 것만 딱 있다.

무료버전에서 제공하는 툴들


설정을 다양하게 할 수 있는 것은 전문적으로 사용하는 사람에게는 큰 이점이라 생각합니다만, 초심자들에게는 심플한 것이 최고입니다.

무료 버전에서 많은 도구들을 제공하지 않지만, 기본 설정 만으로도 초보자 기준입니다만 그림을 그리는데 딱히 불편함을 느끼지 않았습니다.

색상도 자주 사용하는 색상들이 미리 배치되어 있기 때문에 그 색상에서 약간씩 수정하면 다른 색상으로 만들 수 있다는 게 장점이였습니다.

(그라데이션 기능이 무료 버전에 없는 것은 조금 아쉽지만...)


장점 2 : 뒤로 가기 제스쳐 기능

Tayasui Sketches는 그림을 그리다가 두 손가락으로 톡 쳐서 취소가 가능합니다.

이게 뭐 대단한 기능이냐 하겠지만, 그림을 그리다가 실행 하기 전으로 되돌리기를 하는 경우에 다른 손으로 뒤로가기 버튼을 찾아서 누르는 것이 생각보다 정말 귀찮고, 그림을 그리다가 맥이 끊기고 합니다.

하지만 이 앱에서는 그림을 그리다가 두 손가락으로 톡 화면을 두드리면 취소되니까 편리했습니다.

이게 익숙해지면 다른 앱에서도 실행 취소를 하기 위해 두 손가락으로 톡 화면을 두드리는 것을 경험을 하게 될 겁니다.


장점 3 : 동영상 촬영 기능

요새는 유튜브에서 금손 분들이 그림 그려지는 과정을 보여주는 영상을 올리시는 분들이 있는데, 이 앱에서는 그런 영상을 만들기 위한 동영상 쵤영 기능이 있습니다.

물론 타입랩스 같은 것은 다른 편집 앱을 통해서 사용해야 합니다.



단점 1 : 부족한 레이어

레이어를 생성할려고 하면 프로버전 구입 팝업이 뜬다


무료 버전에서는 레이어가 단 2개만 제공됩니다.

레이어 생성 기능이 프로 버전에서 제공되는 기능 이라, 제공해준 2개의 레이어 중 하나를 삭제한 후 새로운 레이어를 만들기를 시도하면, 프로로 업그레이드가 필요하다는 팝업을 접할 수 있습니다.

물론 저같이 초보자는 2개로도 감사히 잘 쓰고 있지만, 가끔 레이어 하나만 더 있었으면 좋겠다고 생각이 들곤 합니다.


단점 2 : 팜 리젝션

다른 도구들을 사용할 때 팜 리젝션 부분에서는 불편함이 없었습니다.

애플 펜슬을 사용하다가 손가락으로 터치를 시도하면 애플 펜슬 사용 중이니까 손가락으로 그림은 안 그려진다고 끊임 없이 메세지를 보여 주니깐요.


그만 채워져라 좀!!! 빡치게 하지말고!!


하지만 영역 채우기 툴에서 팜 리젝션이 간혹 안되는 경우가 있습니다.

손가락으로 툭 쳐서 색을 칠하는 기능과 선을 그려서 영역을 만들어주면 해당 공간을 채워주는 기능이 한 도구안에 공존하다 보니, 펜슬을 잡은 손이 화면에 닿으면 색이 채워집니다.

손가락으로 툭툭쳐서 색을 칠하시는 분들은 상관 없겠지만, 저는 영역을 만들어 준 뒤 채우는 것을 선호하다 보니까 이 부분이 상당히 불편하고 거슬렸습니다.



총평 : 별 4개


그라데이션이나 레이어 추가와 같이 있으면 더 편하겠다 하는 기능들은 프로 버전을 통해서 구매가 가능합니다만, 그림을 가볍게 그리는 목적으로 사용하기에는 충분한 가치를 합니다.

아주 가끔 프로 업그레이드를 권장하는 팝업이 뜨긴 하지만 시도 때도없이 15초 짜리 동영상을 보여주는 앱들에 비하면 상당히 매너있는 편이라 생각합니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다. 

,

[Python] List 중복제거

Tip 2019. 1. 4. 00:38
리스트 중복 제거

리스트 중복 제거

필요에 따라서 List 등의 중복 제거를 해야하는 경우가 있는데요.

다양한 데이터 타입에서 중복을 제거하는 방법에 대하여 알아보도록 합시다.




일반적인 경우

Python에는 set자료형을 기본적으로 제공합니다.

set은 수학에서 사용하는 집합의 개념으로 중복된 원소를 가지지 않는 것이 특징입니다.


set타입은 list 타입과 Type Cast가 가능한 타입인데 이 점을 이용하여 중복을 제거 하는 것이 가능합니다.


dup_list = ['1', '1', '1', '4', '5', 5]
remove_dup = list(set(dup_list))
print(remove_dup)



실행 결과를 보면 중복이 제거되어 있음을 알 수 있습니다.

다만 '5'5 처럼 타입이 서로 다른 경우에는 중복을 제거를 하지 않습니다.

만약 리스트 안에 리스트가 있는 경우에 이 방법을 사용하면 어떤 결과가 발생할까요?





원소가 list로 이루어져 있는 경우

dup_list = [[1,2], [1,2], [1]]
remove_dup = list(set(dup_list))
print(remove_dup)


이 소스 코드를 실행하면 에러가 발생합니다. Hash를 지원하지 않는 list를 사용하였기 때문입니다.  

다행히 tuple의 경우에는 hash를 지원합니다

list를 모두 tuple타입으로 변환한 후 중복 제거를 시도합니다.

dup_list = [[1,2], [1,2], [1]]
remove_dup = list(set(map(tuple, dup_list)))
print(remove_dup)


하지만 이 방법은dup_list=[1, [1, 1], [1, 1]]과 같이 uniterable element이 포함된 list의 경우 tuple()로 변환이 되지 않습니다.

list 이외의 다양한 타입이 있다면, 약간 야매(?) 방법이 있긴 한데..





타입이 다양하면 str으로 변환 해보자.

import ast

def convert_str(x):
	if type(x) is str:
		return "'" + x + "'"
	else:
		return str(x)

dup_list = [1, '1', [2, 3], [2, 3], [2, '3'], {2, 3}, (2, 3)]
remove_dup = list(map(ast.literal_eval, set(map(convert_str, dup_list))))
print(remove_dup)

원소들을 모두 문자열로 바꾼 후 set을 취합니다.

다만, str 타입의 경우에는 앞뒤로 따옴표를 추가해줍니다.

그 후 ast.literal_eval()로 str으로 변환된 것들을 다시 원래 데이터 타입으로 변환해줍니다.

참고로 ast.literal_eval() 함수는 () -> tuple, {} -> set / dict, [] -> list, '' -> str와 같이 변환하는 해줍니다.




실행 결과를 보면 중복이 타입에 따라 제거가 된 것을 확인할 수 있습니다.

사실 이 정도 중복을 제거 해야 하는 일이 많지는 않습니다만 이렇게도 할 수 있다 정도만 참고하시면 될 거 같습니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기 부여가 됩니다.



,
Singleton

싱글톤이란?

하나! 오직 하나!


싱글톤 패턴은 클래스의 인스턴스가 오직 하나임을 보장하는 디자인 패턴이다.

어떤 클래스의 인스턴스를 생성하고 소멸하는 과정이 빈번하게 발생한다면, 고민해도 될 만한 패턴


대표적인 형태 - Eager initialization

public class Singleton{
	// 인스턴스를 Static 생성한다. 
	private static final Singleton instance = new Singleton();

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 인스턴스를 사용할 때는 이렇게 호출한다.
	public static Singleton getInstance(){
		return instance;
	}
}


싱글톤 패턴을 사용하는 코드에서 나타나는 특징은 크게 세 가지 입니다.


  1. private로 선언된 생성자

단 하나의 인스턴스를 보장하기 위해서, 클래스 외부에서 새로운 인스턴스의 생성을 막아야 하므로 private로 접근을 제어한다.


  1. Static으로 선언된 인스턴스 멤버 변수

일반적으로 우리가 new 를 사용하여 클래스의 인스턴스를 선언하는 방법을 동적 생성이라고 하는데, 이 때는 메모리 구조상 Heap에 위치하게 된다.

Heap에 있는 인스턴스는 메모리 할당과 해제를 거치게 되는데, 이 연산이 많은 사용자들이 사용하는 서버에서 동시에 이루어 진다면, 부하가 될 수 있다.

반면 Static으로 선언된 인스턴스는 최초 클래스 로드시에 메모리에 적재된 후, 계속 메모리에 남게 된다.


  1. 오직 getInstance() 함수로 Instence 접근

이 클래스의 인스턴스를 접근할 수 있는 곳은 오로지 getInstance() 입니다.


위의 세 가지 조건을 만족한다면 클래스의 인스턴스가 1개인 클래스를 생성가능합니다.

이 방법이 가지는 단점은 이 클래스를 사용하든 안하든 항상 메모리 상에 상주한다는 겁니다.

getInstance() 가 실행 되기 전부터 메모리에 상주하여 낭비를 한다는 것이 마음에 썩 들지는 않습니다.

그래서 클래스를 사용할 때 메모리에 인스턴스를 로드하는 방법을 고안합니다.





Lazy Initialization

public class Singleton{
	// 미리 생성하지 않는다.
	// private static final Singleton instance = new Singleton();
	private static Singleton instance = null;

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 인스턴스를 사용할 때는 이렇게 호출한다.
	public static Singleton getInstance(){
		// 추가!! 최초로 사용할 때 인스턴스를 생성한다.
		if (instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}


이전 코드와 비교 하기 위해서 주석으로 이전 코드를 남겨 두었다.


  1. 미리 생성하지 않는다.

클래스의 인스턴스를 사용하기 전부터 메모리에 상주하던 방법이 아닌 최초로 사용하는 곳에서 인스턴스를 생성합니다.

getInstance() 함수가 한 번이라도 실행되어야 이 클래스를


  1. 단 하나의 Instance 보장 문제

우연히 같은 시간에 여럭 개의 쓰레드가 getInstance()를 호출된다면, 하나의 인스턴스를

이 경우에 단 하나의 Instance를 보장 가능하다고 말 할 수 없습니다.

아래의 코드는 Thread-Safe 를 붕괴해보는(?) 소스코드 입니다.


import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Runner implements Runnable{
   public static final CyclicBarrier gate = new CyclicBarrier(20);

   @Override
   public void run() {
      try {
         gate.await();
         Singleton s = Singleton.getInstance();
      } catch (InterruptedException | BrokenBarrierException e) {
        e.printStackTrace();
      }
   }

   public static void main(String[] args) {      
      for(int i=0; i<20; i++) {
         Runner broker = new Runner();
         Thread worker = new Thread(broker);
         worker.start();
      }
   }
}


이 소스 코드는 20개의 쓰레드가 동시에 getInstance() 를 실행하는 소스 코드입니다.

실행 결과를 보면 여러 개의 인스턴스가 생성되는 것을 확인 할 수 있습니다.



Lock을 걸어버리자

public class Singleton{
	private static Singleton instance = null;

	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}

	// 추가!! Lock을 걸어보자
	public static synchronized Singleton getInstance(){
	// public static Singleton getInstance(){
		if (instance == null){
			instance = new Singleton();
		} 
		return instance;
	}
}


synchronized 로 선언된 영역은 Lock을 걸어 주는 역할을 수행하므로 하나의 쓰레드씩 이 함수를 수행하게 되므로 Thread-Safe 문제를 보완해 줍니다.

하지만 우리가 병렬로 수행하기 위한 목적으로 멀티쓰레드를 쓰는데, synchronized 구간에서 병렬성이 무너지기 때문에 성능저하가 발생할 수 있습니다.






holder에 의한 초기화

public class Singleton{
	// private로 선언된 생성자
	private Singleton(){
		System.out.println("Create Instance");
	}
	
	// Holder Class 추가
	private static class Holder {
		static{
			System.out.println("Holder Class");
		} // Holder 클래스를 메모리에 로드할 때 메세지를 보여주기 위함
		private static final Singleton instance = new Singleton();
	} 	

	public static Singleton getInstance(){
		return Holder.instance;
	}
	// getInstance() 말고 다른 함수를 호출하였을 때 인스턴스 생성 여부 확인용
	public static void print(){
		System.out.println("Singleton.print()");
	}
}


Singleton 클래스 내부에 Holder 클래스를 작성하고 그의 멤버 변수로 Singleton 클래스의 인스턴스를 생성합니다.

앞서 소개한 두 가지 방법에서 발생한 문제점을 어느 정도 보완하는지 체크해 보았습니다.


  1. 사용 전에 미리 생성하지 말 것

getInstance() 호출 전 Singleton 클래스의 인스턴스가 static 맴버 변수로 작성되지 않았으므로 인스턴스가 생성되지 않습니다.

위의 소스코드에서getInstance() 를 실행하지 않고 print() 함수만 실행했을 때 결과를 보면 쉽게 이해하실 수 있을 겁니다.


  1. Thread-Safe 보장

getInstance() 함수가 실행되면 Static으로 선언된 Holder 클래스는 JVM의 Class Loader 에 의해 메모리에 적재됩니다.

Holder는 Singleton 클래스의 인스턴스를 가지고 있으니 Thread-Safe를 붕괴할 수 있는 경우는 Holder 클래스가 Class Loader에 의해 메모리에 적재될 때 중첩되어 Load되는 경우 뿐입니다.

하지만 Class가 초기화되는 과정은 Java Language Specification (JLS)에 의해 시퀀스하게 진행됩니다.

그래서 synchronized로 실행했을 때처럼 Lock을 제공하지만 더 좋은 성능을 보장하는 방법을 제공합니다.


긴 글 읽어 주셔서 감사합니다.

공감 버튼은 작성자에게 큰 동기부여가 됩니다.


,