<서론>

한동안 알고리즘을 쉬고 있었다. 사실 한창 할때만큼은 아니라도 하루에 간단한 문제 하나 정도는 풀려고 했었는데, 태생이 나태한 인간이라 정말 몇 개월을 알고리즘 문제 하나 안풀고 보냈다. 그러다 최근 코딩 테스트를 몇 번 보게 될 기회가 생겼는데, 오랜만에 해서 그런지 역시나 제대로 풀 지 못했다. 특히 배열을 파라메터로 사용할 때 얕은 복사 문제가 발생해서, 계속 오답이 나오는 경우가 있었다.

그래서 이 글을 통해 JAVA 배열의 얕은 복사와 깊은 복사에 관해 정리하려 한다.


 

 

<본론>

얕은 복사와 깊은 복사는 자료형이 참조형(Reference type)인 경우에 나뉜다. 기본형(Primitive type)에 경우 값 복사만 이뤄지기 때문에 얕은 복사인지 깊은 복사인지 고민할 필요가 없다.

그래서 참조형의 얕은 복사와 깊은 복사에 대해 알아보자.

 

1. 얕은 복사

얕은 복사는 해당 객체의 주소값을 복사하는 것이다. 결국 객체명은 다를 지언정 같은 값을 참조하는 것이다. 객체명은 다르지만 각 객체의 주소는 같다.

1
2
3
int[] orgArr = {1,2,3,4,5};
int[] copyArr = new int[5];
copyArr = orgArr;
cs

위와 같이 그저 "="로 표시하면 얕은 복사가 된다.

위 코드에서는 얕은 복사가 되었기 때문에 orgArr의 원소 값이 변경되면 copyArr의 값도 같이 변화한다.

orgArr과 copyArr의 주소 역시 동일하다.

1
2
3
4
5
6
7
8
9
10
class Test {
    public static void main(String[] args) {
        int[] test = {1,2,3,4,5}
        methodTest(test);
    }
    
    static void methodTest(int[] arr) {
        arr[1= 99;
    }
}
 
cs

어찌보면 당연한 것일 수도 있지만 위 코드처럼 메소드의 파라미터로 배열을 사용하고

해당 배열을 메소드 내에서 수정한다면 당연히 배열 값도 변경한다.

나는 이 부분에서 원하지 않는 수정이 발생했기 때문에 고생을 했다.

이 부분을 해결하기 메소드 내에 깊은 복사를 진행하고 복사한 배열을 변경하도록 메소드를 수정했다.

 

2. 깊은 복사

깊은 복사는 해당 객체의 값을 복사하는 것이다. 따라서 해당 값을 갖는 또다른 객체를 신규로 생성한다고 생각할 수도 있다. 당연히 각 객체의 주소는 다른 값이다.

1
2
3
4
5
int[] orgArr = {1,2,3,4,5}
int[] copyArr = new int[5];
for(int i=0; i<orgArr.length; i++) {
    copyArr[i] = orgArr[i];
}
 
cs

위 코드처럼 각 원소에 값을 직접 넣는 식으로 작성한다면 깊은 복사가 발생한다. 이 경우 orgArr이 변경되더라도 copyArr의 값은 변경되지 않는다.

 

1
2
3
4
5
6
int[] orgArr = {1,2,3,4,5}
int[] copyArr1 = new int[5];
int[] copyArr2 = new int[5];
 
copyArr1 = Arrays.copyOf(orgArr, orgArr.length);
System.arraycopy(orgArr,0,copyArr2,0,orgArr.length);
cs

반복문을 통해서 직접 값을 입력하는 방법 말고도 위 코드처럼 메소드를 사용하여 깊은 복사를 진행할 수도 있다. for문과 같은 반복문을 통해서 배열을 복사하는 것보다 위와 같은 코드로 배열을 복사하는 편이 좀 더 직관적인 것 같아서 위 방법을 더 사용한다.

https://stackoverflow.com/questions/18638743/is-it-better-to-use-system-arraycopy-than-a-for-loop-for-copying-arrays

 

Is it better to use System.arraycopy(...) than a for loop for copying arrays?

I want to create a new array of objects putting together two smaller arrays. They can't be null, but size may be 0. I can't chose between these two ways: are they equivalent or is one more effici...

stackoverflow.com

Stackoverflow 형님들 글을 보자면, 반복문을 통해서 배열을 복사하는 것보다, System.arraycopy나 Arrays.copyOf가 더 빠른 것을 알 수 있다. 읽기에도, 쓰기에도, 성능에도 더 좋기 때문에 힘들게 반복문으로 쓰지 말자.

 

위의 메소드들을 응용하여 2차원 배열도 깊은 복사를 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
int[][] orgArr = new int[3][5];
int[][] copyArr = new int[3][5];
// orgArr 초기화
for(int i=0; i<orgArr.length; i++) {
    for(int j=0; j<orgArr[i].length; j++) {
        orgArr[i][j] = i*j;
    }
}
 
for(int i=0; i<orgArr.length; i++) {
    System.arraycopy(orgArr[i], 0, copyArr[i], 0, orgArr[i].length);
}
 
cs

위 코드처럼 배열을 원소로 갖는 배열이라고 생각하고, 각 원소를 깊은 복사하면 된다.


 

 

<결론>

원리를 안다면 정말 간단한 문제지만, 가끔씩 깜빡 잊고 넘어가는 부분이다. 이번에 코딩 테스트를 풀면서 이 문제 때문에 정말 아까운 시간만 많이 소비했었다. 평소 알고리즘 공부를 꾸준히 했으면 이런 문제는 일어나지 않았을 것이고, 혹시 똑같이 실수 했더라도 그 실수를 바로 잡는데 시간이 얼마 걸리지 않았을 것이다.

이번 코딩테스트를 하고 난 후 반성의 의미로 이 글을 정리했다. 앞으로는 실수하지 말자..

 

 

<참고자료>

https://dog-foot-story.tistory.com/27

 

[Java] 02. 배열의 복사(Arrays.copy)

배열 복사하는 방법 1. HashCode값 복사 (얕은 복사) scores의 해시코드 값이 scores1, scores2에 복사된다. 스택의 같은 공간에 접근함으로 힙에 담겨있는 값을 가져오게 된다. 따라서 기존 배열인 scores가 변경..

dog-foot-story.tistory.com

 

출처 네이버

평소 수학, 과학 교양 도서를 좋아했기때문에, 다시 독서를 하게 된다면 우선 이런 책들로 읽으려 했다.

최근 과학보다 수학 분야에 관심이 조금 더 생겨서 수학 관련 책을 읽기로 결정했다.

그러다 눈에 띈 책이 이번에 읽은 "숫자 없이 모든 문제가 풀리는 수학책"이다.

목차만 읽었는데도 느낌이 바로 팍 왔다.

이 책은 분명 재밌을 것이다!

책이 얇기도 해서 금방 읽을 수 있을 것 같았다.

 

 

결론부터 말하면 이 책은 약간 실망스러웠다. 책 자체는 읽기 쉬웠고 나름 흥미도 있었는데, 내가 원했던 수학 교양서는 이런 게 아니었다.

좀더 수학 관련된 이야기가 나왔으면 했었는데 아쉬웠다.

이 책의 부제인 '복잔합 세상을 심플하게 꿰뚫어보는 수학적 사고의 힘' 을 염두해두지 책을 접한 것이 실망의 원인이었다.

수학보다는 '수학적 사고' 란 것에 중점을 두고 이 세상을 어떤 식으로 대해야하는가에 대해 서술한 책이었다.

난 수학 그 자체에 대한 이야기를 읽고 싶었던 것인데 나의 이런 욕구를 충족시키지 못해 아쉬었다.

그래도 이 책의 도입부, 수학이 무엇인지에 대해 정의하고 설명하는 부분과 인공지능과 인간의 사고구조의 차이에 대한 부분은 무척 흥미진진했다.

 

수학은 하나의 언어일뿐이며, 수학이 어려운 이유는 수학 우주를 설명하는 언어를 알지 못하기 때문이라는 말은 공감이 가는 말이었다.

누군가 '번역'을 해줄 수만 있다면 충분히 이해할 수 있는 세계지만

수학 언어인 여러 수식이나 기호들을 우리가 알지못하기 때문에 이해할 수 없는 것이다.

난 '번역'을 통해 수학 우주의 일부를 이해하게 되었을 때의 즐거움때문에 계속 수학 교양 도서를 찾고 있다.

 

 

이번에 읽은 "숫자 없이 모든 문제가 풀리는 수학책"은 수학 우주의 또다른 곳을 이해할 수 있게 도와주는 '번역본' 같은 책일 것이라 예상했으나 그런 책은 아니라 아쉬웠다.

수학이란 학문이 어떤 것이며, 앞으로 수학을 어떻게 대해야하는 지에 대한 내 나름에 정의가 생긴 것 같아 읽기 잘한 것 같다.

아무튼 이번 독서도 끝!

'취미 > ' 카테고리의 다른 글

거의 모든 IT의 역사(정지훈 지음, 메디치)  (0) 2020.01.23

출처 네이버

2017년에 알라딘에서 중고로 구매한 책이었다.

주변 지인의 소개로 읽게된 책이었는데 취향과 맞지 않아서 얼마 못 읽고 중단한 책이다.

새해 목표로 책을 1달에 1권은 읽자고 잡아 그 기회로 이 책을 다시 집게 되었다.

목표 달성을 위해 이번엔 중도 하차는 하지 않았지만 이 책 한 권을 읽는데 꼬박 한달이 걸렸다.

 

 

우선 책 소개부터 하자면..

이 책은 70년대 개인용 컴퓨터 출시 이후부터 현재(현재라고 하기엔 책 출판이 2010년이다. 무려 10년 전 이야기)까지 IT 업계에 선두가 되기 위해 실리콘밸리의 기업들이 어떤 식의 전략을 들고 왔고 어떻게 성공했으며, 어떻게 실패했는지에 대해 이야기해준다.

좀더 이야기의 맛을 살리기 위해 딱딱한 역사적 사실을 나열하기보다는 마치 삼국지의 영웅들을 표현하듯 IT 업계에서 큰 영향을 끼친 인물에 대해 표현한다.

책을 읽다보면 우리가 다 알만한 스티브 잡스나 빌 게이츠같은 매우 유명한 사람 말고도 큰 성과를 낸 수많은 인물들이 등장한다. 그리고 각 인물들에 대해서도 간략하게 설명해주는데, 책 한 권을 위해 정말 큰 노력이 있었겠다는 생각이 들었다. 읽다보면 생생하다 라는 느낌을 넘어서 저자의 정보력에 경외심이 들 정도다.

IT업계의 역사에 대해 조금이라도 관심이 있다면 이 책은 정말 재밌게 읽을 수 있는 책임은 분명하다.

 

 

이 책을 처음 시도할 때 얼마 못 읽고 그만 뒀었는데, 1970년 대 개인용 컴퓨터 부분에 대한 이야기가 나에게는 지루했다.

사실 내가 IT에 관심이 가기 시작한 것은 2000년대 중반이 넘어서이고, 사실 2000년 이후에도 컴퓨터와 같은 IT보다는 핸드폰과 같은 디바이스에 관심이 있던터라 책의 앞 부분은 내 머리에 잘 들어오지 않았다.

그 당시에는 IT업계를 주도했던 기업들이었지만 이름을 들어보지 못한 기업들도 있었고, 그 당시에 컴퓨터 상황 얘기들을 들으면서 전혀 공감하지 못했다. 나도 알만한 이야기들이 나오는 2000년 대부터야 머릿속에 이야기들이 제대로 그려지면서 재밌게 읽을 수 있었다.

 

이 책의 시점으로 가장 최근일인 2000년대 후반과 2010년 이야기를 하면서 앞으로의 IT 업계에 대해 저자가 예상하는 글도 읽을 수 있었는데, 어떻게 보면 저자가 예상한 미래를 살고 있는 입장에서 이 글은 굉장히 흥미로웠다. 저자의 예상이 맞고 틀리고도 재미있는 점이긴 했지만, 2010년 이후 '기업들이 왜 이런 서비스나 디바이스를 개발했을까', '이 기업은 자신들의 방향을 왜 이쪽으로 했을까' 와 같은 질문에 대답이 되었다는 것이 제일 재밌었다. 그동안 그저 소비자의 입장에서 속칭 저 기업은 왜 삽질하는 걸까 라고 생각한 적이 꽤나 있었는데, 비록 실패로 돌아갔지만 해당 서비스를 하게 된 기업의 근거가 무엇이었는지 알 수 있었다.

 

 

책 분량으로 보면 절대로 한 달이나 걸릴 책은 아니었지만, 독서가 취미가 아니라 힘겹게 읽은 책 같다. 오래전부터 내 책장에 한 구석을 차지하고 선 언젠가는 해치워야할 숙제였는데, 이제야 그 숙제를 다한 것 같아서 책의 내용과는 별개로 후련하기도 하다.

<서론>

프로젝트 중 "java.sql.BatchUpdateException: 업데이트에 대한 결과 집합 이 생성되었습니다." 라는 오류 메시지가 콘솔창에 출력되었다.

처음 사용하는 프레임워크로 프로젝트를 진행하고 있기 때문에 프레임워크 문제인지도 많이 고민했었다.

혼자 고민해보고 이곳저곳 고쳐보다가 제발 있었으면 좋겠다 라는 심정으로 구글링한 결과, 해결책을 찾을 수 있었다.

 

역시 구글은 모든 걸 알고있다.

 

 


 

<본론>

1. sqljdbc4.jar 파일 문제

sqljdbc의 버전에 의해 문제가 발생할 수 있다는 글을 찾았다. 운영도 되고 있는 서비스라 sqljdbc 파일을 변경 후 검증가지 필요하므로 막무가내로 파일을 변경하기에는 조금 무리가 있어 최후의 수단으로 남겨두고 다른 방법을 찾았다.

 

2. 프로시저 내의 "NO COUNT ON"

프로시저의 "NO COUNT ON" 구문이 해당 문제를 야기한다는 글을 찾았다. 프로시저 마지막에서 리턴받을 데이터가 없는 처리용일 때는 해당 명령을 빼주면 오류가 발생하지 않는다고 한다.

일단 내 쿼리는 리턴값이 있었지만, 속는 셈 치고 해당 명령을 주석처리한 결과 오류는 해결되었다.

 

 


 

<결론>

엄밀히 말하면 완벽한 해결은 아니다. 위에 언급했듯이 내 프로시저에서는 리턴값이 있는 프로시저였음에도 위와 같은 오류가 발생했다. 정말 sqljdbc4.jar 파일 문제일 수도 있지만 아직 개발 초보라 NO COUNT ON이 정확히 어떤 명령어인지 파악하지 못했다.

NO COUNT ON 을 좀 더 공부해보고, 내 프로시저에 어떤 문제가 있었는지 분석한 다음 NO COUNT ON에 대한 정리글도 포스팅하겠다.

 

<참고자료>

https://blog.naver.com/PostView.nhn?blogId=lkssky2002&logNo=220477127663&proxyReferer=http%3A%2F%2Fwww.google.com%2Furl%3Fsa%3Dt%26rct%3Dj%26q%3D%26esrc%3Ds%26source%3Dweb%26cd%3D1%26ved%3D2ahUKEwjW0v3B05PnAhXMc3AKHYlyBhwQFjAAegQIAxAB%26url%3Dhttp%253A%252F%252Fm.blog.naver.com%252Flkssky2002%252F220477127663%26usg%3DAOvVaw08rR0Nv06OhNtsgJT1y43I

 

[해결]mssql 프로시져 호출시 "업데이트에 대한 결과 집합 이 생성 되었습니다" 오류 발생

java 비즈니스 로직에서 프로시져 호출시 "업데이트에 대한 결과 집합 이 생성 되었습니다" 라는 오류 메세...

blog.naver.com

 

<서론>

프로젝트에서 사용하던 DB가 MSSQL로 되어 있어서 MSSQL을 사용해볼 수 있었다. 그러다보니 문자열을 자르거나, 문자열을 찾거나 하는 등의 처리를 하는 일이 있었는데, 자주 쓰기보다 어쩌다 한 번 나오는 경우라 기억하지 못하고 계속 구글링으로 찾아봤다. 그렇게 계속 찾기엔 귀찮아서 내 블로그에 정리하려 한다. 앞으로는 내 블로그 봐야지 히힛.

 

 


 

<본론>

1. 문자 길이 확인 (LEN, DATALENGTH)

1-1. 글자 수 확인

LEN( string_expression ) / return int
ex) LEN( 'Hello' ) / return 5

하지만 LEN은 자료형이 CHAR, VARCHAR인 경우에 사용할 수 있다(N이 붙어도 가능). 이 점을 잊고 쿼리를 짜다간 깊은 혼란에 빠질 수 있다.

 

1-2. byte 수 확인

DATALENGTH( expression ) / return int
ex) DATALENGTH( 'Hello' ) / return 5

입력 변수에 TEXT나 NTEXT 등도 올 수 있으며 심지어 IMAGE도 가능하다고 한다.

Byte를 체크하기 때문에 한글인 경우에는 글자 수보다 크게 나온다.

 

2. 문자열 자르기(LEFT, RIGHT, SUBSTRING)

2-1. 문자 앞(왼쪽)에서부터 자르기

LEFT( string_expression, integer_expression ) / return (varchar or nvarchar)
ex) LEFT( '123456', 3 ) / return '123'

자르기를 원하는 문자열을 첫번째 파라메터로 넣고, 몇글자 자를 건 지 숫자를 두번째 파라메터로 넣는다.

입력하는 문자열은 varchar, nvarchar, char, nchar 만 가능하다. text나 ntext는 사용할 수 없다.

입력하는 문자열이 유니코드인 문자 데이터 형식인 경우 nvarchar를 return

입력하는 문자열이 유니코드가 아닌 문자 데이터 형식인 경우 varchar를 return

 

2-2. 문자 뒤(오른쪽)에서부터 문자 자르기

RIGHT( string_expression, integer_expression ) / return (varchar or nvarchar)
ex) RIGHT( '123456', 3 ) / return '456'

문자 앞에서부터 자르는 방식과 동일하다.

 

2-3. 중간부터 자르기

SUSBSTRING( expression, start, length ) / return (varchar or nvarchar or varbinary)
ex) SUBSTRING( '123456', 3, 2 ) / return '34'

입력받은 문자열의 중간을 잘라낼 때 사용. 시작값은 0이 아니라 1이다.

입력할 수 있는 데이터 형식은 char, binary, text, image가 가능하다.

입력값의 데이터 형식에 따른 리턴값의 데이터 형식은 다음과 같다.

char, varchar, text / return varchar

nchar, nvarchar, ntext / return nvarchar

binary, varbinary, image / reutnr varbinary

 

3. 숫자형인지 확인(ISNUMERIC)

ISNUMERIC( expression ) / return int
ex) ISNUMERIC( '123456' ) / return 1
ex2) ISNUMERIC('123k456') / return 0

입력한 문자열이 숫자형으로 변환이 가능한 지 확인하는 함수.

입력한 문자열이 숫자형으로 변환이 가능한 경우 1을 불가능한 경우에는 0을 반환한다.

 

4. 문자열 치환(REPLACE)

REPLACE( string_expression, string_pattern, string_replacement ) / return (varchar or nvarchar)
ex) REPLACE( '123456789123', '123', 'test' ) / return 'test456789test'

문자열 치환은 텍스트 에디터에서 쓰는 기능과 동일하다. 다만 하나만 바꾸는 것이 아니라 찾은 패턴은 모두 치환해 버린다.

입력한 문자열이 varchar나 nvarchar의 최댓값을 초과하는 값이라면 반환값을 8,000 byte에서 자른다.

 

5. 문자열 찾기(CHARINDEX, PATINDEX)

CHARINDEX( expression_to_find, expression_to_search [, start_location] ) / return (varchar or bigint or int)
ex) CHARINDEX( '123', '123456789123' ) / return 1
ex2) CHARINDEX( '123', '123456789123', 2) / return 10

처음 입력값을 두번째 입력한 값 내에서 찾고 첫번째 위치를 반환하는 함수. 세번째 입력 값으로 검색하는 위치를 조정할 수도 있다. 찾는 값이 없을 경우, 0을 반환한다.

 

5-1. 패턴 처음 위치

PATINEDX( '%pattern%', expression ) / return (varchar or nvarchar or bigint or int)
ex) PATINDEX( '%123%', '123456789123' ) / return 1
ex) PATINDEX( '123', '123456789123' ) / return 0

CHARINDEX와 비슷하지만 패턴을 LIKE 처럼 찾는다. CHARINDEX와 마찬가지로 찾는 패턴이 없을 경우 0을 반환한다.

 

6. 문자열 끝의 공백 지우기(LTRIM, RTRIM)

6-1. 문자 앞(왼쪽) 공백 지우기

LTRIM( character_expression ) / return (varchar or nvarchar)
ex) LTRIM( '        he llo  ') / return 'he llo  '

문자 앞(왼쪽)에 있는 공백을 제거하는 함수. 문자 끝(오른쪽)이나 중간의 공백은 제거하지 않는다.

 

6-2. 문자 뒤(오른쪽) 공백 지우기

RTRIM( character_expression ) / return (varchar or nvarchar)
ex) LTRIM( '        he llo  ') / return '        he llo')

문자 앞(왼쪽)에 있는 공백을 제거하는 함수. 문자 끝(오른쪽)이나 중간의 공백은 제거하지 않는다.

 

번외. 문자열 어디에 있든 공백을 한꺼번에 지울 땐 REPLACE를 사용

 

7. 공백 넣기(SPACE)

SPACE( integer_expression ) / return varchar
ex) SPACE( 3 ) / return '   '

입력한 숫자만큼 공백을 반환하는 함수. 만약 숫자가 음수라면 Null 을 반환한다.

 

번외. 유니코드 데이터에 공백을 포함하거나 8,000개가 넘는 공백을 반환하려면 SPACE 대신 REPLICATE를 사용

 

8. ASCII 코드 변환(ASCII, CHAR)

ASCII( character_expression ) / return int
CHAR ( integer_expression / return char
ex) ASCII( 'A' ) / return 65
ex2) CHAR( 65 ) / return 'A'

ASCII와 문자로 변환하는 함수.

CHAR와 관련해서는 몇 가지 제어 문자를 삽입할 수 있다.

 

CHAR(9)   : 탭

CHAR(10: : 줄바꿈

CHAR(13) : 캐리지 리턴

 

9. 대소문자 변환(UPPER, LOWER)

UPPER( character_expression ) / return (varchar or nvarchar)
LOWER( character_expression ) / return (varchar or nvarchar)
ex) UPPER( 'AbcdE' ) / return 'ABCDE'
ex2) LOWER( 'AbcdE' ) / return 'abcde'

입력받은 문자열을 모두 대문자 혹은 소문자로 변경하는 함수. 대소문자가 섞여있는 문자열을 비교할 때, 대문자 혹은 소문자로 맞추기 위해 사용하기도 한다.

 

10. 문자 반복(REPLICATE)

REPLICATE( string_expression, integer_expression ) / return (string_expression과 같은 형식)
ex) REPLICATE( '0', 4 ) / return '0000'

원하는 문자열을 원하는 횟수만큼 반복하는 함수. 숫자로 이루어진 데이터의 앞에 원하는 만큼 0을 넣어 자리를 맞춰주는 데 쓰일 수 있다.

 

11. 문자열을 반대로(REVERSE)

REVERSE( string_expression ) / return (varchar or nvarchar)
ex) REVERSE( 'abcdEABcdeFg' ) / return 'gFedcBAEdcba'

입력한 문자열을 거꾸로 반환하는 함수. 대소문자도 당연히 구분한다.

 


 

<결론>

문자열과 관련된 내장함수가 적진 않지만 자주 쓴다면 못 외울 것도 없다. 하지만 이렇게 많은 종류를 어쩌다 한 번씩 쓰니 일일히 외우고 다니기엔 여간 귀찮은 일이 아니다. 그리고 이번 프로젝트가 끝난다면 언제 또 MSSQL을 만나게 될지도 알 수 없다. 오라클 열심히 하자.

 

 

<참고자료>

1. https://docs.microsoft.com/ko-kr/sql/t-sql/language-reference?view=sql-server-ver15

사진 1. 삼성 스페이스 모니터(S32R750Q)

2019년 마지막을 장식할 지름.

공간 활용에 특화되어 있다는 삼성 스페이스 모니터를 지르고 말았다.

9년을 고생한 모니터는 은퇴시키기로 결정하자 32 Inch, QHD 이상의 모니터를 질러보자 라고 생각하던 찰나,

삼성 스페이스 모니터가 특가로 풀려서 지르고 말았다.

생긴 것도 너무 이뻐서, 안살래야 안살 수가 없었다.

 

힌지가 불안하지만, AS의 삼성이니 10년 정도 써도 괜찮겠..

 

 


박스

 

 

사진 2. 스페이스 모니터 박스

박스가 너무 거대해서 놀랐다. 내가 사용했던 모니터들 중 가장 큰 32인치긴 했지만, 박스가 엄청 컸다.

가로로 긴 와이드 모니터도 아니었는데, 모니터 박스가 가로로 무척 길어서 당황했다.

가로로 긴 이유는 모니터 암과 바이스처럼 조이는 부분을 수납하려고 공간을 만들어서 옆으로 긴 것이었다.

실제로 모니터 본체는 한 쪽으로 패키징 되어 있었고 반대쪽은 비어있었다.

AR87 키보드와 마찬가지로 언박싱 사진은 제대로 찍지 못했다.

 

 


간단 사용기

 

조립하는 사진과 설치하는 사진은 찍지 못했다. 영롱한 모니터의 자태에 정신이 홀려 부랴부랴 설치하느라 깜빡했다.

아직 리뷰 포스팅이 부족해서 그런갑다.

 

사진 4. 스페이스 모니터를 가장 위로 올린 상태

조립 및 설치 후기를 글로만 대신해보자면, 우선 조립은 예상한대로 정말 간단했다. 메뉴얼대로만 한다면 큰 문제는 없는 편. 다만 책상에 설치할 때가 걱정이었다. 모니터가 크기에 비해 덜나간다고 해도 무게가 있는 편인데 설치할 때, 고정이 안돼서 애먹지 않을까 걱정했는데, 제일 높게 고정한 상태에서 설치하니 쓰러지는 일은 없었다. 생각보다 설치도 쉬운 편이었다.

 

사진 5. 고정부 하단

유리 위에 설치하는 것이었지만 고정하는 위, 아래 부분 모두에 나름 푹신한 패드가 있어서 유리가 깨질 염려는 없어보인다.

 

사진 6. 스페이스 모니터 측면부

의외로 모니터 두께는 좀 있는 편인데, 베젤이 얇은 편이어서 그런지 크게 신경쓰이진 않는다. 그리고 어차피 정면만 보니 모니터 두께를 의식할 일도 없다.

 

사진 7. 스페이스 모니터 베젤

바깥에 튀어나와있는 정말 얇은 선만 베젤인 줄 알았는데, 안으로도 조금 더 있다. 그래도 무척 얇아서 거의 없는 편.

 

사진 8. 스페이스 모니터를 가장 내렸을 때

스페이스 모니터의 단점이라고 지적하는 것 중 하나는 높이 조절만 할 수는 없다는 것. 모니터 암의 구조상 높이를 내리면 모니터가 사용자에게 가까워질 수 밖에 없고, 높이를 올리면 사용자에게서 멀어질 수 밖에 없다. 이런 점은 확실히 아쉬운 부분이다. 원래 컴퓨터 할 때 자세가 안좋아서 최적의 모니터 거리 같은 건 없는 편인 나에게도 높이를 가장 낮춰서 나와 가깝게 하니 부담스러울 정도로 모니터와 나 사이가 가까워 진 편이었다. 모니터를 책상에 대고 쓸 일은 없으니, 큰 문제까지는 아닌 것 같다.

 

사진 9. 공간 활용을 극대화한 스페이스 모니터

이 모니터는 공간 활용을 극대화 했다는 것이 가장 큰 장점인데, 확실히 모니터를 가장 위로 올리면, 꽤나 많은 자리가 생긴다. 모니터를 책상에 고정하는 부분도 크지 않은 편이고, 책상의 가장 끝에 있어서인지 책상을 사용할 때, 신경쓰이지 않는 정도이다. 확실히 이런 점은 이 모니터의 매력 포인트인 것 같다.

 

 


마치며

 

아직 이 모니터를 진득하게 사용해본 건 아니고, 게임 몇 개 돌려보는 정도로만 사용해봤다. 현재 사용하는 그래픽 카드가 QHD를 144Hz 모두 쏴주긴 조금 벅찬 감이 있어서 평균 110Hz대긴 하지만, 게임에서 아주 만족도 높은 디스플레이를 보여주고 있다.

 

사실 이러니 저러니 해도 생긴게 이뻐서 다 괜찮아.

 

 

<사진 출처>

사진 5. http://news.zum.com/articles/57182095

'취미 > 전자기기' 카테고리의 다른 글

[키보드] ABKO AR87 NAVY 갈축  (0) 2019.12.24

사진 1. 그저 빛발란스

블랙 프라이데이의 신호탄이었던 뉴발 993

해외 직구가 서툴러서 이거 하나 사는데도 꽤나 신중하게 주문했다.

 

사진 2. 갓발란스

그렇게 신중했는데도, 사이즈를 잘못 주문해서 취소하고 다시 했었다.

왜 여자 사이즈랑 남자 사이즈를 나눠서 하는 건지..

 

 

사진 3. 회색이라 어디든 좋다.

아무튼 그래도 사놓고 한번도 후회는 커녕 오히려 잘 샀다고 생각하는 신발이다.

웃긴 건 마음으로 '질러야해!' 하고 지른 신발이 아닌,

머리로 '이건 색깔도 무난하고 사면 여기저기 잘 신을 수 있을 거 같다.' 란 생각으로 산 신발이라

사고 나서도 감흥이 없었는데, 신고다니면서 점점 좋아지는 신발이다.

 

사진 4. 대충 찍어도 이쁘다.

역시 뉴발란스는 날 실망시킨 적이 없다. 이번 뉴발란스가 내 생의 두번째 뉴발란스라는 건 비밀..

 

사진 1. ABKO AR87 Navy

2019년, 연말 지름 아이템 중 하나인 키보드. 그 전에 쓰던 리얼포스 님의 타건감에 질리기 시작해서 키보드를 사야겠다고 마음 먹었다. 지인 중에 키보드 덕후가 있어 그 사람의 추천으로 지른 하이엔드 키보드 AR87이다.

 

하이엔드라고 하기엔 커스텀 키보드들이 가격이 더 나가긴 하지만 기성품 중에서는 가격이 꽤 있는 편이니..

그리고 내 입장에선 꽤나 큰 지출!

그럼 이제 대충 간단 사용기를 올려보겠다.

 


구성품

 

원래는 AR87이 담겨져 있을 박스 사진도 있어야하지만..

아직 리뷰나 사용기를 올리기엔 내공이 부족해서 박스는 깜빡하고 촬영하지 못했다. 심지어 바로 버렸다..

다음번에 하게 될 리뷰에서는 박스를 버리기 전에 사진부터 찍는 걸로...

 

사진 2. 구성품

대강 구성품은 위 사진과 같다.

1. 키보드

2. USB type C to USB type A 케이블 

3. 청소용 솔

4. 키캡 리무버

5. 케이스.

 

사진 3. 케이스

케이스 내부에는 별도의 쿠션 같은 건 있지 않아서 케이스가 외부 충격으로부터 키보드를 잘 지켜줄 것 같진 않다. 하지만 이런 케이스가 있어줘서 고맙다. 케이스가 있었기 때문에 키보드가 있던 박스는 뒤도 안돌아보고 버렸다.

 

사진 4. 청소용 솔

키보드 청소용 솔이 있는 건 무척 좋았다. 이런 걸 별도로 구매하기엔 뭔가 아깝다는 생각이 들었기 때문이다.

이런 점은 좋았지만 개인적으로 아쉬운 점은 키보드 가격대가 어느정도 있는 편인데 루프정도는 있었으면 좋지 않았을까 생각한다.

 

 


외관

 

사진 5. 사이드 LED

외관을 보면 사이드에 LED가 있다. LED를 있는 것과 없는 것을 고르라 했으면 없는 걸 골랐겠지만 요즘은 LED가 없는 걸 찾는게 더 어려운 듯하다.

 

사진 6. 키보드 정면

밝은 곳에서 타건을 하게 된다면 사이드에 LED는 잘 보이지 않는다. 밑에 패드 같은 걸 이용해서 빛이 반사된다면 잘 보일것 같아 장패드도 구매할 예정이다.

 

사진 7. 키보드 항공뷰

하우징은 풀 알루미늄으로 무게가 제법 나가는 편이다. 집에 저울이 없어서 직접 무게를 재진 못했지만 스펙상으로 2.9kg이라는 묵직한 키보드다. 처음 박스 채로 받았을 때도 꽤 무거웠던 기억이 있다. 당연한 이야기겠지만 그래서 휴대용으로 들고다니기엔 힘들 것 같다.

 

사진 8. USB Type C

하우징 색상은 실버, 다크 그레이, 네이비가 있다. 한정판으로 레드 색상도 있던 것 같지만 현재 판매는 3가지 색상만 하는 중이다. 이번에 내가 구매한 네이비 색상은 최근에 추가된 색상이기도 한데, 호불호가 많이 갈리는 듯하다.

조금 더 어두운 네이비였다면 이만큼 호불호가 갈릴 일은 없었을 것 같다.

하우징의 윗부분과 아랫부분 색상 차이도 있다는데, 내가 받은 제품은 딱히 없는 것 같았다.

케이블을 연결하는 단자는 USB Type C이며 정중앙에서 살짝 벗어난 곳에 위치해있다.

 

사진 9. 기본 키캡

기본 키캡은 이렇게 생겼다. 다크 그레이에 들어가는 키캡과 동일한 키캡이라고 생각된다. 다크 그레이를 실물로 본적은 없어서 확실하진 않지만, 인터넷에서 보이는 사진 상으로는 둘이 거의 동일한 것 같다.

다크그레이 색상에는 기본 키캡이 어울리는 색인 것 같지만, 네이비에는 기본 키캡이 안어울리는 것 같다는 생각이 든다.

네이비 색상이 생각보다 채도가 있는 편이라 이런 어두운 색 보단 나름 알록달록한 키캡이 어울릴 것 같다.

사진에서 알 수 있듯이 키캡 마감이 좋은 편은 아니다.

 

 


키감

 

사진 10. 키보드 스위치

키보드 스위치는 체리 스위치 갈축이다.

보통 이 키보드는 적축이나 흑축같은 리니어들을 많이 선택하고, 내 취향도 리니어 방식을 선호하긴 하지만,

예전에 다른 지인의 레오폴드 갈축을 타건 해보고는 다음 키보드는 무적권 갈축이다! 라고 마음 먹었었다.

아직 사용한 지 일주일도 채 안됐지만 아직은 질리지 않고 재밌게 타건 하고 있다.

 

키보드 커뮤니티에선는 순정 상태의 키보드로는 윤활유 상태나 스테빌 상태가 매우 좋지 않아 공방을 맡겨야 한다는 말을 들었다.

확실히 shift나 space bar 같은 커다란 키들은 조금 어색한 느낌도 들긴 하지만 나는 키보드 매니아들처럼 아주 예민한 감각은 없기에 기분좋게 타건하고 있다.

축 방식도 리니어가 아닌 넌클릭이기때문에 윤활유에 대한 압박도 덜한 편이기도 하다.

그래서 굳이 공방에 맡기는 일은 없을 것 같다.

 

여담으로 기본 키캡이 생긴 것이 마음에 들지 않아 MAXKEY 사의 키캡을 하나 지름 상태다. 호불호가 많이 타는 SA 키캡이라 기대 반, 걱정 반이긴 하지만 일단, 키보드를 매우 좋아하는 지인의 SA 키캡 키보드를 타건했을 때는 꽤나 만족스러웠다. 키캡이 바뀌면 키감도 많이 달라질 텐데, 과연 어떤 느낌일지 궁금하다. 키캡이 도착하면 키캡도 포스팅할 것이다.

 

 


여담

 

 

IT 관련 공부를 주로 포스팅하고 나머지 내 관심사는 짧고 대충 올리려고 했는데, 쓰다보니 계속 더 쓸게 생각나서 길이 처음 계획보다 많이 길어졌다. 오히려 주객이 전도된 느낌..?

AR87 네이비를 사려고 마음 먹었을 때, AR87 네이비에 관한 정보를 찾아보기가 힘들었는데 그때의 기억때문인지 쓰다보니 길게 쓰게 된 것 같다.

 

아무튼 연말에 지른 아이템 중 하나인 AR87. 좀 더 오래 써보고 리뷰 생각나면 리뷰할 것이다.

 

<사진 출처>

사진 1. http://www.abko.co.kr/shop/product_item.php?ItId=2586312747

https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV14vXUqAGMCFAYD&categoryId=AV14vXUqAGMCFAYD&categoryType=CODE

 

SW Expert Academy

SW 프로그래밍 역량 강화에 도움이 되는 다양한 학습 컨텐츠를 확인하세요!

www.swexpertacademy.com

<서론>

문제 링크는 위에.. 오랫동안 알고리즘 쉬다가 다시 공부를 시작하려한다. 이번 문제는 복귀 문제. 한창 풀때였으면 기계적으로 빠르게 풀었겠지만, 너무 오랜만에 해서 그런지 좀많이 허둥댔다. 역시 알고리즘은 빡세게 안할 때라도 하루에 한문제는 풀어야할 것 같다.

이번 문제는 제목에서 알 수 있듯이 경로 탐색 문제! 그럼 이번 문제도 풀어보자.


 

 

<본론>

이번엔 테스트 케이스 수를 입력 받아서 그 수만큼 반복문을 실행하는 것은 아니고, 테스트 케이스는 10개로 고정되어있다. 처음 받는 숫자는 테스트케이스 개수가 아니라 테스트케이스 번호라는 것을 알아야한다. 이걸 처리하는 방법은 많겠지만, 난 귀찮아서 입력만 받고 그냥 넘겼다. 이번 문제는 경로 문제답게 간단하게 bfs 방법을 이용해서 풀었다. 알고리즘은 별반 다를 것 없다.

1. 시작 좌표를 큐에 저장
2. 큐가 빌 때까지 또는 목표에 도착할 때까지, 다음을 반복
  2-1 큐 pop
  2-2 큐에서 꺼낸 좌표의 4방향 체크
    2-2-1 4방향에 있는 값을 방문한 적이 없고, 0이면 큐에 추가
    2-2-2 4방향에 있는 값을 방문한 적이 없고, 3이면 목표 도착 체크

평범한 bfs 문제였다. 특별히 신경써서 처리할 것도 없고, 최단 경로의 길이를 구하는 문제도 아니고 그저 도착할 수 있는지만 확인하는 문제. 그런데 오랜만에 풀어서 어처구니 없는 실수들을 반복했다. 다음은 실수들에 대한 정리이다. 바로 코드를 보고 싶은 사람은 바로 결론으로..

 

1. Input 값 처리 실수

이 문제를 풀 때 발생한 첫 예외처리. 로직을 확인하기도 전에 값을 입력받을 때 문제가 있었다. 아무 생각 없이 nextInt() 메소드를 이용해서 MAP[][] 배열에 값을 넣으려고 했는데, 모든 숫자가 붙어있어서 nextInt를 해버리면 1자리 숫자가 아닌 16자리 숫자로 입력되는 것이었다. 하는 수 없이 String 객체로 입력 받고, split 메소드를 이용해서 잘라 넣었다. MAP 배열을 String 배열로 선언해서 풀 수 있었지만, 이미 모든 로직을 정수형일 경우로 정해놓고 풀어서 input 값을 integer로 파싱했다.

2. 배열 범위 초과

입력 문제를 해결하자 곧바로 다른 예외 발생. 배열 범위를 벗어난다는 어처구니없는 오류였다. 배열을 0,0에서부터 15,15까지 4방향을 체크하게 짜두었는데 가장자리 부분에서 예외가 발생한 것이다. 정말 초보적인 실수를 하고야 말았다. 배열을 2칸 키워서 처리했다. 이래서 알고리즘은 쉬면 안되는 것이다.

3. 무한 루프

배열까지 처리했더니 프로그램이 종료가 안되는 것이었다. 그래서 디버깅 모드로 확인했더니... 블로그에 적지말까? 하고 고민했을 정도로 창피한 실수를 했다. que의 첫번째 원소를 가져오기만 하고 제거를 안한 것이었다. 큐가 빌 때까지 도는 반복문이었기 때문에 곧바로 무한루프.. 버그를 고치면서도 스스로 민망했다.

4. 시작점 설정 실수

아직도 실수가 남았다. 배열을 2칸 늘렸으니 저장되는 시작점의 위치도 그에 따라 바꿔야하는데 그러질 못했다. 그래서 자꾸 답이 다른 답이 나오는 것이었다. 디버깅할 때도 도대체 왜 시작점을 이상하게 잡는 건지 한참을 고민했다. 시작점을 찾는 if문에는 이쁘게 잘 j+1이라고 적어놓고선 실제로 저장할 땐 그냥 j만 저장했다. 간신히 찾고 다시 돌린 결과 정답.. 힘들었다.

 

<결론>

요약은 패스.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package d190610;
 
import java.util.LinkedList;
import java.util.Scanner;
 
// 1226 미로 1
public class Solution {
 
    static int[][] MAP = new int[18][18];
    static int[] dx = {0-101};
    static int[] dy = {10-10};
 
    static public void main(String[] args) {
 
        Scanner sc = new Scanner(System.in);
 
        for(int testCase = 1; testCase <= 10; testCase++) {
            boolean[][] visited = new boolean[18][18];
            String t = sc.next();
            int[] start = new int[2];
            int ans = 0;
            for(int i=0; i<18; i++) {
                if(i!=0 && i!=17){
                    String str = sc.next();
                    String[] strArr = str.split("");
                    for(int j=0; j<strArr.length; j++) {
                        MAP[i][j+1= Integer.parseInt(strArr[j]);
                        if(MAP[i][j+1]==2) {
                            start[0= j+1;
                            start[1= i;
                        }
                    }
                }
                MAP[0][i] = 8;
                MAP[i][0= 8;
                MAP[i][17= 8;
                MAP[17][i] = 8;
            }
 
            /*  이건 그냥 MAP 확인용
            System.out.println("====="+testCase);
            for(int i=0; i<18; i++) {
                for(int j=0; j<18; j++) {
                    System.out.print(MAP[i][j]+"");
                }
                System.out.println();
            }
            System.out.println("====="+testCase);
            */
 
            LinkedList<Cord_1226> que = new LinkedList<>();
            que.add(new Cord_1226(start[0], start[1]));
            while(!que.isEmpty()) {
                if(ans==1break;
                Cord_1226 cd = que.get(0);
                que.removeFirst();
                int x = cd.getX();
                int y = cd.getY();
                for(int dir=0; dir<4; dir++){
                    if(MAP[y+dy[dir]][x+dx[dir]]==0 && !visited[y+dy[dir]][x+dx[dir]]) {
                        que.add(new Cord_1226(x+dx[dir], y+dy[dir]));
                        visited[y+dy[dir]][x+dx[dir]] = true;
                    } else if(MAP[y+dy[dir]][x+dx[dir]]==3 && !visited[y+dy[dir]][x+dx[dir]]) ans = 1;
                }
            }
 
            System.out.println("#"+testCase+" "+ans);
        }
        sc.close();
    }
 
}
 
class Cord_1226{
    private int x;
    private int y;
 
    Cord_1226(){}
 
    Cord_1226(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
    public int getX() {
        return x;
    }
 
    public int getY() {
        return y;
    }
 
    public void setY(int y) {
        this.y = y;
    }
 
    public void setX(int x) {
        this.x = x;
    }
 
}
 
cs
;

<서론>

자바로 개발 중, ConcurrentModificationException이 발생했다. set을 사용할 때 나온 exception인데, 거의 처음 만난 exception이라 당황했다. 대충 찾아보니 동시성 문제라고 한다. set을 iterator에서 불러서 iterator.hasNext()를 while문 조건으로 넣어놓고 set을 수정했더니 이런 문제가 발생했다.


 

 

<본론>

set을 하나 복사해서 하면 될거라고 생각했다. 하지만 set이 깊은 복사가 되어서 복사한 set을 수정해도 원본의 set이 수정되었다. 결국 깊은 복사가 아닌 얕은 복사로 해당 문제를 해결하였다.

아래는 이해를 돕기 위한 코드이다.

 

1) 깊은 복사인 경우. (이때는 ConcurrentModificationException이 발생)

1
2
3
4
5
6
Set<Integer> tmpSet = set;
tmpSet.addAll(set);
Iterator<Integer> it = tmpSet.iterator();
while(it.hasNext()) {
    set.add(it.next()+num);
}
cs

 

2) 얕은 복사인 경우.(addAll( )을 이용하여 얕은 복사)

1
2
3
4
5
6
Set<Integer> tmpSet = new HashSet<Integer>(); 
tmpSet.addAll(set);
Iterator<Integer> it = tmpSet.iterator();
while(it.hasNext()) {
    set.add(it.next()+num);
}
cs

 

 

<결론>

굉장히 간단하고 쉬운 문제지만, 갑자기 Exception을 마주하니 당황하게 되었다. 평소 깊은 복사, 얕은 복사 별 생각없이 개발했었는데, 이런 문제를 겪어보면서 실력있는 개발자가 되기에는 갈 길이 멀다고 느꼈다. 이런 문제들도 미리 생각해서 개발하는 참 개발자가 되어야겠다.(갑자기 분위기 일기)

+ Recent posts