반응형

12755번 수면 장애

https://www.acmicpc.net/problem/12755

 

12755번: 수면 장애

수면 장애를 가진 강민이는 잠이 오지 않아 적잖은 고통을 느끼고 있다. 강민이는 잠이 오지 않을 때마다 속으로 양을 세고 있었는데, 오늘따라 백만 마리까지 세었는데도 잠이 오지 않았다. 한

www.acmicpc.net


Comment
진짜 처음엔 별거 없을거라 생각했는데 생각보다 난이도가 높았던 문제
좀 애먹으면서 문제를 풀었던 것 같다. 메모리 제한이 생각보다 너무 컸고, 정답숫자 위치에서 인덱스 범위 잡는 것에 대한 고민...


hint
for문의 범위는 상관없고 번호와 길이를 따로 저장해서 확인하면 좀 편하다.


Solution
temp 인트를 선언해서 길이를 계속 저장해준다! 길이 저장 후 length가 n과 같거나 커졌을 때 for문을 탈출, 해당 i의 값이 n의 범위에 걸친 i이고, for문의 증강연산으로 인해 i-1이 정답번호이다. 여기서 값 추출은 length-n을 통해 0 ~ i.length()-1 까지 위치를 잡아서 출력하는데, 인덱스를 통해 출력하기 때문에 StringBuilder를 만들어서 reverse메서드로 먼저 뒤집어줬다.

솔직히 깔끔하지는 못한 코드지만 그래도 어떻게든 해결했단 것에 만족..!

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
import java.io.*;
public class p12755 {
    public static void main(String[] args) throws IOException{
        BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
 
        int n = Integer.parseInt(br.readLine());
        int length = 0;
        int i = 1;
        for(i = 1length < n; i++){
            //10을 하려면 어떻게 하는데?
            //10일 때 1카운트 0 카운트
            length++;
 
            int temp = i;
            while(temp >= 10){
                length++;
                temp /= 10;
            }
        }
        StringBuilder sb = new StringBuilder();
        sb.append(i-1);
        sb.reverse();
        System.out.println(sb.charAt(length-n));
    }
}
cs
반응형
반응형

Chapter04 스트림 소개

컬렉션의 역할

컬렉션은 자바에서 빠져서는 안되는 데이터를 그룹화하는 도구다.

모든 자바 애플리케이션에서 만들고 처리하고 있는 컬렉션이지만, 이 컬렉션에서 지원해주는 연산을 지원하지않는다.

때문에 우리가 해당 연산을 처리할 때 데이터를 처리하는 코드를 구현하는데, 해당 구현 방식이 다른 개발자가 봤을 때 명시적이지 않아 명확하지 않을 수 있다. 또 많은 요소를 포함하는 컬렉션은 처리 성능을 높이려면 병렬처리가 불가피한데, 병렬 처리 코드는 어렵다.

헛둘님

스트림이란 무엇인가

스트림은 자바 8 API에 추가된 기능이다.

스트림 API의 장점

  • 컬렉션 데이터 반복을 편리하게 처리할 수 있다.
  • 데이터를 멀티스레드를 활용하지 않아도 투명하게 병렬로 처리할 수 있다.
  • 선언형으로 코드를 구현할 수 있다.
    • 루프와 if조건문 등의 블록 구현이 필요없음
  • 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다.
  • 선언형과, 블록연산을 파이프라인으로 연결하기 때문에 가독성과 명확성이 유지된다.

gpt답변

image

자바 8의 스트림API의 특징

  • 선언형 : 더 간결하고 가독성이 좋아진다.
  • 조립할 수 있음 : 유연성이 좋아진다.
  • 병렬화 : 성능이 좋아진다.

스트림 시작하기

자바 8부터 Collections에서 스트림을 반환하는 stream() 메서드가 추가되었다.

스트림을 정의하자면 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소 이다.

  • 연속된 요소 : 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공한다.
    • 컬렉션의 주제는 데이터 스트림의 주제는 계산 이다.
  • 소스 : 스트림은 컬렉션, 배열 등의 자원 데이터 제공 소스를 소비할 뿐이다. 소스를 그대로 사용하기 때문에 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지된다.
  • 데이터 처리 연산 : 데이터베이스와 비슷한 연산을 지원한다. filter, map, reduce, find, match, sort… 등으로 데이터를 조작할 수 있는 연산을 지원한다.
  • 파이프라이닝(Pipelining) : 스트림 연산 대부분은 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다.
    • 게으름, 쇼트서킷 등과 같은 최적화도 얻을 수 있다.(5장 설명)
    • 연산 파이프라인은 데이터 소스에 적용하는 데이터베이스 질의와 비슷하다.
  • 내부 반복 : 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.

스트림의 특징을 예제에 적용

import static java.util.stream.Collectors.toList;
public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes) {
    return dishes.stream() // 메뉴(요리 리스트)에서 스트림을 획득
                // 파이프라인 연산 시작
        .filter(d -> d.getCalories() < 400) // 1. 고칼로리 요리를 필터링으로 제외
        .sorted(comparing(Dish::getCalories)) // 2. 요리명 추출
        .map(Dish::getName) // 3. 선착순 세개만 선택
        .collect(toList()); // 4. 을 통해 나온 결과를 다른 리스트로 저장
  }
!!
.collect(toList()) -> add, remove 가능
.collect(Collectors.toUnmodfiableList()) -> 자바10
.toList() -> 자바16

불변 리스트, 변환가능한 리스트
List <- new ArrayList 변환가능
List.of() Arrays.asList() -> 불변리스트 set가능
toUnmodfiableList -> 불변리스트지만 List.of와 다름
  • 데이터 소스 : 요리 리스트 → 연속된 요소를 스트림에 제공
  • filter, sorted, map, collect : 데이터 처리 연산 - collect()를 제외한 연산은 파이프라인을 형성할 수 있도록 스트림을 반환
    • filter : 람다를 인수로 스트림에서 특정 요소를 제외시킨다.
    • map : 람다를 이용해서 한 요소를 다른 요소로 변환하거나 정보를 추출
    • limit : 정해진 개수 이상의 요소가 스트림에 저장되지 못하게 크기를 축소
    • collect : 스트림을 다른 형식으로 변환한다. toList()는 스트림을 리스트로 변환

스트림과 컬렉션

컬렉션과 스트림은 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공한다.

여기서 연속된 이라는 표현은 순차적으로 값에 접근한다는 것을 의미한다.

이 둘은 연속된 값을 접근해서 처리하는 방식인데 무엇이 다른가? 했을 때

데이터를 언제 계산하느냐가 컬렉션과 스트림의 가장 큰 차이라고 꼽을 수 있다.

스트림 vs 컬렉션

  • 스트림
    • 요청할 때만 요소를 계산하는 고정된 자료구조
    • 적극적 생성 : 모든 값을 계산할 때까지 기다림
    • 내부 반복(internal iteration)
  • 컬렉션
    • 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다.
    • 게으른(Laziness) 생성 : 필요할 때만 값을 계산
    • 외부 반복(external iteration)

딱 한 번만 탐색할 수 있다.

Iterator와 동일하게 한번만 사용되고 버려진다. 재탐색을 위해서는 초기 데이터 소스에서 새로운 스트림을 만들어야 함

List<String> title = Arrays.asList("Java8", "In", "Action");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); -> IllegalStateException 발생!

외부 반복 보다 내부 반복이 좋다

명시적으로 처리 된 컬렉션 목록을 가져와서 처리하고, 처리한 컬렉션을 다시 다른 컬렉션에 담아서 처리하고… 방식이 상당히 번거롭다.

작업을 실행할 때 명시적으로 값 처리를 바로 바로 담아서 넣어줄 수 있고, 값을 처리할 때 명시적으로 어떤 일을 행하는지 볼 수 있다.

또 병렬처리를 관리하는 부분에서 좋다.

예시 ) 외부 반복 vs 내부 반복

<외부 반복>
List<String> highCaloricDishes = new ArrayList<>();
Iterator<String> iterator = menu.iterator();
while(iterator.hasNext()){
        Dish dish = iterator.next();
        if(dish.getCalories() > 300){
                highCaloricDishes.add(d.getName());
        }
}

->

<내부 반복>
List<String> highCaloricDish = menu.stream()
        .filter(dish -> dish.getCalories() > 300)
        .map(Dish::getName)
        .collect(toList());

스트림 연산

이제 스트림의 연산 방식을 예시와 함께 명칭으로 구분을 해보자.

public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes) {
    return dishes.stream() // 요리 리스트에서 스트림 얻기
        .filter(d -> d.getCalories() < 400) // 중간 연산
        .sorted(comparing(Dish::getCalories)) // 중간 연산
        .map(Dish::getName) // 중간 연산
        .collect(toList()); // 최종 연산
  }
  • 중간 연산
    • 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것, 즉 게으르다 중간 연산을 합친 다음에 합쳐진 중간 연산 결과를 기반으로 최종 연산으로 한번에 처리하기 때문이다.
    • 쇼트서킷 기법을 통해 limit연산을 통해 갯수제한이 가능하다
    • 루프 퓨전 : 서로 다른 연산들을 한 과정으로 병합시키기도 한다.
  • 최종 연산
    • 중간 연산을 다 마친 후에 나온 결과를 최종 처리하는 연산이다.

스트림 이용하기

스트림은 세 가지 방법으로 요약할 수 있다.

  • 질의를 수행할 데이터 소스
  • 스트림 파이프라인을 구성할 중간 연산 연결
  • 스트림 파이프라인을 실행하고 결과를 만들 최종 연산

중간 연산 종류

  • filter : Predicate
  • map : Function
  • limit : 개수제한
  • sorted : 정렬
  • distinct : 중복 제거

최종 연산 종류

  • forEach : 각 요소를 소비하면서 람다 적용
  • count : 스트림의 요소 개수 반환
  • collect : 스트림을 리듀스해서 컬렉션을 만든다.
반응형
반응형

Chapter03 람다 표현식

목차

  • 람다란 무엇인가?
  • 어디에, 어떻게 람다를 사용하는가?
  • 실행 어라운드 패턴
  • 함수형 인터페이스, 형식 추론
  • 메서드 참조
  • 람다 만들기

람다란 무엇인가?

람다의 특징

  • 익명
    • 보통의 메서드와 달리 이름이 없으므로 익명 이라 표현한다.
  • 함수
    • 특정 클래스에 종속되지 않기 때문에 함수라고 부른다. But 메서드의 특성처럼 파라미터 리스트, 바디, 반환 형식, 예외 리스트 등을 포함한다.
  • 전달
    • 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
  • 간결성
    • 익명 클래스처럼 불필요한 코드를 생성할 필요가 없다.
<익명 클래스 활용>
Comparator<Apple> byWeight = new Comparator<Apple>(){
        public int compare(Apple a1, Apple a2) {
                return a1.getWeight().compareTo(a2.getWeight());
        }
}

->

<람다 활용>
Comparator<Apple> byWeight =
        (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

  • 파라미터 리스트
    • ()안의 파라미터 개수
  • 화살표
    • 람다의 파라미터와 바디의 구분자
  • 람다 바디
    • 두 사과의 무게를 비교

예제를 통한 람다 표현식

1. (String s) → s.length() 

2. (Apple a) → a.getWeight() > 150

3. (int x, int y) → {
        System.out.println("Result:");
        System.out.println(x + y);
}

4. () -> 42

5. (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

어디에, 어떻게 람다를 사용할까?

함수형 인터페이스

메서드를 파라미터화 할 수 있는 인터페이스

함수형 인터페이스의 특징으로는 추상 메서드를 하나만 선언하는 것이다.

함수형 인터페이스 활용

2장에서 구현했던 필터 메서드

public static List<Apple> filter(List<Apple> inventory, ApplePredicate p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}

람다를 통한 filter사용

List<Apple> greenApples
        = filter(inventory, (Apple a) -> GREEN.equals(a.getColor()));

Predicate의 자리에 람다가 들어간다.

그래서 함수형 인터페이스 이야기가 왜 나오는데?

람다가 인터페이스의 추상 메서드를 구현할 수 있는 추상 메서드의 구현체(디스크립터)가 될 수 있다.

함수 디스크립터

뜻 그대로 함수를 설명하는 것이라 생각하면 된다.

함수 Runnable의 run이 있을 때

파라미터 : null 반환 : void 으로 했을 때 해당 형태만 일치한다면, 해당 함수의 내부를 어떻게 사용할지 람다로 설명을 하는 것이다.

이 때 파라미터와 반환이 같은 것을 같은 시그니처를 갖는 함수라 볼 수 있는데 시그니처만 같게 한다면 람다를 생성할 수 있다는 뜻이다.

일치하는 시그니처

public Callable<String> fetch() {
        return () -> "Tricky example ;-)";
}

파라미터 : x

반환 : String

일치하지 않는 시그니처

Predicate<Apple> p = (Apple a) -> a.getWeight();

파라미터 : Apple

반환 : boolean

@FunctionalInterface

함수형 인터페이스를 구현할 때 해당 어노테이션을 인터페이스 위에 붙였을 때 함수형 인터페이스가 불가하면 컴파일 에러를 발생시킨다.

ex) 추상메서드 2개

람다 활용 : 실행 어라운드 패턴

public static String processFile(BufferedReaderProcessor p) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(FILE))) {
        return p.process(br);
    }
}

함수형 인터페이스 사용

함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터라고 한다.

람다 표현식을 사용하려면 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.

이를 위해 자바에서는 java.util.function 패키지로 여러 함수형 인터페이스를 제공한다.

  • Predicate T → boolean
  • Consumer → void
  • Function<T, R> T → R
  • Suplier → T
  • UnaryOperator → T

기본형 특화

기본형의 경우 함수형 인터페이스 특성 상 제네릭을 받아서 디스크립터가 구성되는데, 이를 행하기 위해 박싱, 언박싱 과정이 불필요하게 들어가게된다.

때문에 이러한 불필요한 연산을 피할 수 있도록 돕는 IntPredicate, IntFunction 등 각 인터페이스에 맞게 지원한다.

형식 검사, 형식 추론, 제약

형식 검사

List<Apple> heavierThan150g =
filter(inventory, (Apple, apple) -> apple.getWeight() > 150);

filter(List<Apple> list, Predicate<Apple> p){
        ...        
        p.test();
        ...
}

interface Predicate<T>{
        boolean test(T t);
}

람다가 사용되는 콘텍스트를 이용해서 람다의 형식을 추론할 수 있다.

  1. 람다가 사용된 콘텍스트가 무엇인가?
  2. 대상 형식은 Predicate
  3. Predicate 인터페이스의 추상 메서드는 무엇인가?
  4. Apple을 파라미터로 받아 boolean을 반환한다.
  5. 함수 디스크립터는 Apple → boolean이기 때문에 형식 검사가 성공적으로 완료된다.

특별한 void 호환 규칙

람다의 바디에 일반 표현식이 있으면 void를 반환하는 함수 디스크립터와 호환된다

void를 반환하는 함수 디스크립터는 다른 값이 반환되어도 사용이 가능함

형식 추론

Comparator<Apple> c = 
        (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

->

Comparator<Apple> c = 
        (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

컴파일러가 람다의 표현식과 관련된 함수형 인터페이스를 추론한다.

지역 변수 사용

람다는 자기 바깥에 있는 접근 가능한 변수에 대해서도 사용이 가능하다

이와 같은 동작을 람다 캡처링 이라고 부른다.

쓰면 안되는 예시

<컴파일 에러>
int n = 0;
Runnable r = () -> System.out.println(n);
n++;

->

int n = 0;
do {
    int finalN = n;
    n++;
    Runnable r = () -> System.out.println(finalN);
    r.run();
} while (n != 5);

메서드 참조

기존에 있던 메서드 정의를 재활용해서 람다처럼 코드뭉치를 전달할 수 있다.

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

->

inventory.sort(comparing(Apple::getWeight));

그래서 왜 사용하는데?

기존 메서드 구현을 명시적으로 참조함으로써 가독성을 높일 수 있고, 재사용도 가능하기에 효율이 좋다.

사용가능한 메서드 참조 형식

  • 정적 메서드 참조
    • static 유틸메서드 등
    • List::of
    • 정적 팩토리 메서드
  • 다양한 형식의 인스턴스 메서드 참조
    • String::length
  • 기존 객체의 인스턴스 메서드 참조
    • this::getWeight

기존의 표현식을 메서드 참조로 바꾸는 예시

람다 : (args) -> ClassName.staticMethod(args)

메서드 참조 : ClassName::staticMethod

생성자 참조

Apple apple1 = new Apple();

Apple apple1 = Apple::new;

위의 메서드 참조와 형식은 같다.

람다 표현식을 조합할 수 있는 유용한 메서드

몇몇 함수형 인터페이스에서 지원하는 유용한 유틸리티 메서드 소개

Comparator

  • comparing() → Comparator.comparing()
  • comparing().reversed() → 역정렬
  • thanComparing() → 두번째 비교자를 통한 추가비교

Predicate

  • negate() → 결과반전
  • and() → 비교연산 추가
  • or() → 비교연산 추가

Function

  • andThen() → 함수를 입력 후 다른 함수의 입력으로 전달
  • compose() → andThen()의 반대순서로 함수의 입력전달
반응형

+ Recent posts