자바로 특정 폴더의 이미지 파일 리스트를 불러와 이름 변경하기

|

개요

마크다운을 이용하여 깃허브 블로그에 올릴 때, 스크린샷을 찍고 파일명을 변경하고, 폴더에 복사 및 붙여넣기하는 일련의 과정이 꼭 필요하면서도 시간이 소요되는 반복 업무였다는 것을 깨달았다.

그래서 간단한 fileIO를 하면 되는 일이니, 자바를 이용해서 만들어 보기로 결정했다.

요구사항

  1. 폴더로부터 이미지 파일 리스트 읽기
  2. 이미지 파일 이름 변경하기
  3. 아톰 assets/post-img 폴더에 저장하기
  4. markdown에 쓸 html 생성하기

저장 폴더나, 이미지 이름 저장 양식 등은 통일되는 것이었기 때문에 꼭 필요한 것만 그때그때 입력받기로 했다.

저장될 파일명 (예 : javascriptstudy-image) 시작하는 숫자 (하나의 포스팅을 이어서 쓸 경우 필요하다.) 아톰에서 저장될 경로 (예 : javasctipt 폴더)

완성된 코드

package imagenaming;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class readimage {


	public static void main(String[] args) throws IOException {

		Scanner sc = new Scanner(System.in);

		String screenshotpath = "/Users/eunha/desktop/screenshot";
		// 저장하고자 하는 문자열
		System.out.println("바꾸고자 하는 이미지 파일명을 주제-imge 형태로 입력하세요.");
		String basename = sc.nextLine();
		// 시작하는 숫자
		int startnum;
		System.out.println("시작하는 숫자를 입력하세요.");
		startnum = sc.nextInt();
		String basepath = "/Users/eunha/desktop/milkyway103.github.io";
		System.out.println("아톰에서 저장될 경로를 입력하세요.");
		String savepath = sc.next();
		savepath = "/assets/post-img/" + savepath + "/";

		// 이미지 목록을 가져올 폴더 위치를 지정 (절대경로, 상대경로 모두 가능)
		List<File> list = getImgFileList(screenshotpath);

		// 오름차순 정렬
		list.sort(null);

		for (File f : list) {
			// 이름 변경
			File file = new File(screenshotpath + "/" + f.getName());
			String savename = basename + Integer.toString(startnum++) + ".png";
			file.renameTo(new File(basepath + savepath + savename));
			System.out.println("<center>\n" +
					"   <figure>\n" +
					"   <img src=\"" +
					savepath +
					savename +
					"\" alt=\"views\">\n" +
					"   <figcaption></figcaption>\n" +
					"   </figure>\n" +
					"</center>"); // 마크다운용 html 출력
		}

	}

  // 해당 경로의 이미지파일 목록 반환
  public static List<File> getImgFileList(String path){
  	return getImgFileList(new File(path));
  }

  // 해당 경로의 이미지 파일 목록 반환
  public static List<File> getImgFileList(File file){
  	List<File> resultList = new ArrayList<File>(); // 이미지 파일을 저장할 리스트 생성
  	// 지정한 이미지 폴더가 존재하지 않을 경우 빈 리스트 반환
  	if(!file.exists()) return resultList;

  	File[] list = file.listFiles(new FileFilter() { // 원하는 파일만 가져오기 위해 FileFilter 정의
  		String strImgExt="jpg|jpeg|png|gif|bmp"; // 허용할 이미지타입

  		@Override
  		public boolean accept(File pathname) {

  			boolean chkResult = false;
  			if(pathname.isFile()) {
  				String ext = pathname.getName().substring(pathname.getName().lastIndexOf(".")+1);
  				chkResult = strImgExt.contains(ext.toLowerCase());
  			} else {
  				chkResult = true;
  			}
  			return chkResult;
  		}

  	});

  	for (File f : list) {
  		if (f.isDirectory()) {
  			// 폴더이면 이미지 목록을 가져오는 현재 메서드를 재귀 호출
  			resultList.addAll(getImgFileList(f));
  		} else {
  			// 폴더가 아니고 파일이면 리스트(resultList)에 추가
  		resultList.add(f);
  		}
  	}

  	if (resultList.isEmpty()) {
  		System.out.println("This directory is empty");
  	}

  	return resultList;
  }
}
views
views

좀더 사용하기 편리하도록 GUI 방식으로도 만들어 보아야겠다.

참고

200316TIL

|

함수형 프로그래밍과 JavaScript ES6+ 7. 지연성 1 (3)

|

inflearn의 함수형 프로그래밍과 JavaScript ES6+를 보고 공부한 것을 정리합니다.

엄격한 계산과 느긋한 계산의 효율성 비교

즉시 실행되는 코드와 지연적으로 동작하는 코드의 가장 큰 차이는 다음과 같다.

즉시 실행하는 코드는 map함수가 배열의 모든 크기만큼 옆으로 다 돈 다음, filter에서 역시 결과를 옆으로 다 가면서 만들고 해당하는 값이 take에 들어와 두 개의 원소를 담고 끝나게 된다.

지연적으로 동작하는 코드의 경우에는 take로 가장 먼저 들어와, 첫 번째 next()를 해 봤을 때 L.map으로 가서 하나의 값(0)만을 받아서 바로 yield를 하면, L.filter로 가고, L.filter의 조건에 맞지 않기 때문에 yield를 하지 않는다. (여기서는 take를 실행하지 않는다.) 그 다음에도 역시 올라갔다가 내려왔다가를 반복하면서 필요한 값까지만을 돌고 끝나게 된다.

즉시 평가하는 경우 만약 range(10000) 등의 큰 값을 받게 되면 실행시간이 길어진다. 하지만 지연적 동작의 경우에는 아무리 숫자가 커도 원하는 값까지만 받고 동작을 종료하기 때문에 성능상에 아무 문제가 없다.

console.time('');
go(range(10000),
    map( n => n + 10),
    filter(n => n % 2),
    take(2),
    log);
console.timeEnd('');

console.time('L');
go(L.range(10000),
    L.map(n => n + 10),
    L.filter(n => n % 2),
    take(2),
    log);
console.timeEnd('L');
views

지연적으로 동작하는 코드의 경우에는 아무리 숫자가 커져도 take하는 만큼의 시간이 걸린다. 무한수열 (range(Infinity)) 을 넣더라도 정상동작한다.

map, filter 계열 함수들이 가지는 결합 법칙

map, filter 계열 함수에는 특정한 방식으로 다르게 평가 순서를 바꾸어도 똑같은 결과를 만든다는 결합 법칙이 있다.

  • 사용하는 데이터가 무엇이든지
  • 사용하는 보조가 순수 함수라면 무엇이든지
    • 데이터 추출, for문 순회
  • 아래와 같이 결합된다면 둘 다 결과가 같다.
[[mapping, mapping], [filtering, filtering], [mapping, mapping]]
=
[[mapping, filtering, mapping], [mapping, filtering, mapping]]

ES6의 기본 규약을 통해 구현하는 지연 평가의 장점

이러한 지연 평가 방식이 이전의 자바스크립트에서는 굉장히 복잡하거나 지저분한 방식으로 구현할 수밖에 없었다. 왜냐하면 공식적인 자바스크립트에 있는 값이 아니라 전혀 다른 형태의 규약들을 만들어, 해당하는 라이브러리 안에서만 동작할 수 있는 방식으로 구현해야 했기 때문이다.

ES6 이후에는 자바스크립트의 공식적인 값을 통해서, 함수와 리턴되는 실제 자바스크립트의 값을 통해서 지연 여부를 확인하고 원하는 시점에 평가하겠다는 등, 자바스크립트와 개발자가 약속된 규약을 가지고 만들어갈 수 있도록 개선되었다.

ES6 이후에는 지연성을 다루는 데에 있어서 자바스크립트의 고유한, 약속된 값을 통해 구현, 합성, 동작이 가능해졌다.

이러한 방식으로 구현된 지연성은 서로 다른 라이브러리 또는 서로 다른 사람들이 만든 함수 등 어디에서든지 자바스크립트의 기본 값과 기본 객체를 통해 소통하기 때문에 조합성이 높다.

함수형 프로그래밍과 JavaScript ES6+ 7. 지연성 1 (2)

|

inflearn의 함수형 프로그래밍과 JavaScript ES6+를 보고 공부한 것을 정리합니다.

range, map, filter, take, reduce 중첩 사용

하나의 문제를 지연성을 가진 함수와, 그렇지 않은 함수들을 통해 해결해보며 둘 간의 차이를 명확히 하고 평가 시점에 대하여 알아볼 것이다.

go(range(10),
    map( n => n + 10),
    filter(n => n % 2),
    take(2),
    log);
views

브레이크 포인트를 찍어서 확인한다.

views

인자로 받은 10이 지역변수로 표시된다.

views

while문 안으로 들어와, 현재 i값은 0이고, 그 0이 push된다.

views

반복함에 따라 i가 바뀌고 res에도 값이 담긴다. i++한 값이 l보다 작은 때까지만 동작하기 때문에 해당하는 값까지만 담고 종료한다.

map, filter, reduce의 for of 코드는 숨겨져 있는 내용이 많다. 그 안에서 정확히 어떤 일이 일어나는지에 대해 확인하기 위해, 세세하게 명령형으로 작성하여 대체한 뒤 브레이크포인트를 찍을 것이다.

    const range = l => {
        let i = -1;
        let res = [];
        while(++i < l) {
            res.push(i);
        }
        return res;
    };

    const map = curry((f, iter) => {
        let res = [];
        iter = iter[Symbol.iterator]();
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value;
            res.push(f(a));
        }
        return res;
    });

    const filter = curry((f, iter) => {
        let res = [];
        iter = iter[Symbol.iterator]();
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value;
            if (f(a)) res.push(a);
        }
        return res;
    });

    const reduce = curry((f, acc, iter) => {
        if (!iter) {
            iter = acc[Symbol.iterator]();
            acc = iter.next().value;
        } else {
            iter = iter[Symbol.iterator]();
        }
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value;
            acc = f(acc, a);
        }
        return acc;
    });

    const take = curry((l, iter) => { // limit, iterable
        let res = [];
        iter = iter[Symbol.iterator]();
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value;
            res.push(a);
            if(res.length == l) return res;
        }
        return res;
    });

    const L = {};
    L.range = function *(l) {
        let i = -1;
        while(++i < l) {
            yield i;
        }
    };

    L.map = function *(f, iter) {
        iter = iter[Symbol.iterator]();
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value;
            yield f(a);
        }
    };

    L.filter = function *(f, iter) {
        iter = iter[Symbol.iterator]();
        let cur;
        while (!(cur = iter.next()).done) {
            const a = cur.value;
            if (f(a)) {
                yield a;
            }
        }
    };
views

range 함수에서 return되면 range(10) 자리에 리턴된 값으로 평가된다. 평가되면 그 코드는 go를 통해서 map 함수로 들어간다.

views

현재 iter는 배열이고, 이터러블한 값이다.

views

반복문 안으로 들어가게 되면 위의 iterArray Iterator라는 값으로 바뀐다.

views

next를 통해 꺼낸 값을 curvalue로 참조하여 a에 담는다. 여기세서는 앞에서 range를 통해 만들었던 배열의 첫 번째 값인 0이 들어왔다.

views

반복되면서 계속해서 respush한다. 배열의 크기였던 10개만큼 반복한다.

views

반복이 끝나면 해당하는 결과를 go에서 받아 다음 함수에 넘겨준다.

views

filter에는 map을 통해 모든 원소에 10이 더해진 값의 배열인 iter가 들어온다.

views

역시 이터레이터로 변환하고 그를 통해 반복한다.

views

filter에서는 f를 실행하여 확인해보고 조건이 맞으면 respush한다.

views

array를 순회하면서 원하는 조건의 값만 res에 담는다.

views

역시 go를 통해 take 함수에 전달된다. take 함수에서는 다섯 개의 원소로 이루어진 배열을 받는다. 이터레이터를 만들어 반복한다.

views

l 값으로 2를 받았으므로, reslength가 같아질 때까지 담는다. a 값을 push하고 확인하는 과정을 반복한다. 12까지 res에 들어가면 llength가 같아지기 때문에 if문이 True로 평가된다. -> return!

views

결과가 떨어지면 log가 찍힌다.

L.range, L.map, L.filter, take의 평가 순서

    go(L.range(10),
        L.map(n => n + 10),
        L.filter(n => n % 2),
        take(2),
        log);

이 경우에는 다른 순서로 코드가 평가된다. 실행되었을 때, 어떤 함수에 가장 먼저 들어갈까? (마찬가지로 브레이크포인트로 확인) 나는 L.map일 거라고 예상했는데, take였다.

views
take가 가장 먼저 실행됨

코드에서는 L.range를 가장 먼저 실행했지만, take에 가장 먼저 들어갔다. 다시 말하면, L.range, L.map, L.filter 함수 안쪽의 어떤 연산도 하지 않고 바로 take 함수로 들어간 것이다.

views

take 함수에 들어온 iter를 확인해 보면 generator라고 되어 있는, 이전과는 다른 값이다.

views

well-formed iterator는 본인이 이터레이터이면서, symbol.iterator 함수를 가지고 있고, 이것을 실행했을 때 이터레이터인 자기 자신을 다시 리턴한다. 그렇기 때문에 symbol.iterator line을 지나도 여전히 이터레이터이다. 그래서 take 함수는 제너레이터가 만든 이터레이터도 여전히 잘 이터레이터로 만들 수 있게 된다. 자바스크립트는 이런 방식으로 다형성을 잘 유지할 수 있도록 만들어져 있다.

views

while문 안쪽으로 들어가려고 하면, L.filter 함수 안으로 들어오게 된다. 즉, iter.next()를 호출했떠니 L.filter에 들어온 것이다. 이유는 L.range를 한 결과, 즉 안쪽의 코드들이 평가되기를 미뤄둔 제너레이터가 바로 L.map으로 들어가게 되고, L.map 역시도 바로 평가되기를 미뤄둔 이터레이터를 리턴하기 때문에 L.filter 역시 이터레이터를 리턴한다. 그래서 여기에서 take를 실행했을 때에는 L.filter가 리턴한 이터레이터를 take가 받고 있고, take가 받아둔, L.filter가 리턴한 이터레이터에 처음 next()를 실행했을 때, L.filter함수 안쪽에서 평가가 시작된 것이다.

views

L.filter 함수 역시 제너레이터로 만들어진 이터레이터를 받고 있다.

views

여기서 while 문 안쪽으로 들어가려고 하면 또 iter.next()를 실행하게 된다. 그럼 다시 L.map으로 넘어간다. (L.map 함수가 만들어준 이터레이터이기 때문에!)

views

L.map에서 iter로 받은 것 역시 L.range가 만든 이터레이터이다.

views

다시 while문으로 들어가려고 하면 역시 L.range가 만든 이터레이터에 next()를 실행하기 때문에 L.range로 가게 된다. 즉, 위에서부터 평가되는 것이 아니라 go로 들어가서 take함수가 마치 먼저 실행되는 것처럼 보이고, 순회를 하려고 하자 역으로 L.filter, L.map, L.range의 순으로 올라간다.

views

L.range 함수에서 계속 진행하게 되면 드디어 while문 안쪽으로 들어가서, 0yield한다.

views

이 값은 L.map에서 next를 통해 얻고자 했던 값이기 때문에 map의 while문으로 넘어가게 된다. L.range로 만들어진 이터레이터의 next를 통해 해당하는 0이라는 값을 받아서 mapn => n + 10 함수를 적용하고 또 yield하게 되면

views

마찬가지로 L.filter에서 L.map을 통해 만들어진 이터레이터에 next()를 요청했기 때문에 L.filter로 넘어가게 된다. 이제 L.filter에서 a가 홀수인지 확인해 보고, 아니기 때문에 다시 L.range로 돌아간다.

views

즉시 평가되는 map, filter, take의 경우에는 range에서 10개짜리 배열을 먼저 다 만든다. 그 후에 map을 하면서 10개짜리 배열에 모두 10을 더하고, 모두 확인하면서 filter한 값을 만들어 그 값이 take에 들어간다.

그러나 이 경우에는 똑같은 take 함수임에도 불구하고, 제너레이터 이터레이터 방식으로 만들어진 함수들을 통해 take 함수의 next를 했을 때 순서가 반대로 된다. take를 하나 하고자 했떠니 반대로 위로 올라가면서 filter할 것, map할 것을 달라고 range에게 요청하면 0을 map이 받아서 10을 더한 것을 적용하고 filter가 받아서 조건을 확인한다. 즉, 가로로 진행되는 것이 아니라 세로로 진행되는 것이라고 할 수 있다.

0       1     ...
10      11
false   true

올라갔다가 내려오고 하는 식으로 동작한다! 아까의 iternext()를 했을 때 0이었던 값은 take로 내려오지 않았고, 1이 들어왔을 때 L.filter가 처음으로 yield를 했기 때문에 그 전 값은 take가 아예 확인조차 하지 않았다. L.filter에서 True로 내려온 값만 확인한다. 그리고 l과 비교해본 후 작기 때문에 while문을 돌면서 다시 iter.next()를 진행한다. 그럼 다시 L.range로부터 값이 내려오게 된다.

views

200313TIL

|

오늘 한 것

  • 자바스크립트 함수형 프로그래밍 7강 나머지 수강 및 정리
  • 지원서 작성 및 제출