부족한 금액 계산하기

class Solution {
    public long solution(int price, int money, int count) {
        long answer = 0;
        long s = 0;
        for (int i = 1; i <= count; i++){
            s += price * i;
        }
        
        if (money - s > 0){
            return 0;
        } else {
            answer = (money - s) * -1;
            return answer;
        }
    }
}

 

문자열 다루기 기본

숫자로만 이루어졌는지 확인하는 함수 : "asdfs".isdigit()

문자로만 이루어졌는지 확인하는 함수 : "234".isalpha(), Character.isDigit(s.charAt(i))

def solution(s):
    if len(s) == 4 or len(s) == 6:
        return s.isdigit()
    else:
        return False
import java.util.*;

class Solution {
    public boolean solution(String s) {
        if (s.length() != 4 && s.length() != 6){
            return false;
        }
        
        for(int i = 0; i < s.length(); i++){
            if(!Character.isDigit(s.charAt(i))){
                return false;
            }
        }
        
        return true;
    }
}

 

 

n = int(input())
members = []
sequence = 1
for _ in range(n):
    age, name = input().split()
    members.append([int(age), name, sequence])
    sequence += 1

members.sort(key = lambda m : (m[0], m[2]))

for i in members:
    print(str(i[0]) + " " + str(i[1]))

 

JAVA

  • sc.next() : 입력 스트림에서 공백을 기준으로 하나의 단어를 읽어옴
  • Arrays.sort() : 안정 정렬 특성을 가짐 -> 정렬 기준이 같은 값들에 대해 기존 순서를 유지하는 정렬 즉, 입력받은 순서대로 유지
import java.util.Scanner;
import java.util.Arrays;
import java.util.Comparator;
 
public class Main {
	public static void main(String[] args) {		
 
		Scanner in = new Scanner(System.in);
		
		int N = in.nextInt();
		String[][] arr = new String[N][2];
		
 
		for(int i = 0; i < N; i++) {
			arr[i][0] = in.next();	// 나이
			arr[i][1] = in.next();	// 이름
		}
 
		
		Arrays.sort(arr, new Comparator<String[]>() {
			// 나이순으로 정렬
			@Override
			public int compare(String[] s1, String[] s2) {
				return Integer.parseInt(s1[0]) - Integer.parseInt(s2[0]);
			}
			
		});
		
		for(int i = 0; i < N; i++) {
			System.out.println(arr[i][0] + " " + arr[i][1]);
		}
        
		
	}
 
}

가운데 글자 가져오기

  • String을 char배열로 : 변수명.charAt(index접근 가능);
class Solution {
    public String solution(String s) {
        String answer = "";
        if (s.length() % 2 == 0){
            answer += s.charAt(s.length() / 2 - 1);
            answer += s.charAt(s.length() / 2);
        } else {
            answer += s.charAt(s.length() / 2);
        }
        return answer;
    }
}

 

수박수박수박수박수박수?

class Solution {
    public String solution(int n) {
        String answer = "";
        for (int i = 1; i <= n; i++){
            if(i % 2 != 0){
                answer += "수";
            }else{
                answer += "박";
            }
        }
        return answer;
    }
}

 

내적

class Solution {
    public int solution(int[] a, int[] b) {
        int answer = 0;
        for(int i = 0; i < a.length; i++){
            answer = answer + a[i] * b[i];
        }
        return answer;
    }
}

 

약수의 개수와 덧셈

class Solution {
    public int solution(int left, int right) {
        int answer = 0;
        for (int  i = left; i <= right; i++){
            int count = 0;
            for(int j = 1; j <= i; j++){
                if(i % j == 0){
                    count++;
                }
            }
            if(count % 2 == 0){
                answer += i;
            }else{
                answer -= i;
            }
        }
        return answer;
    }
}

 

문자열 내림차순으로 배치하기

import java.util.*;
class Solution {
    public String solution(String s) {
        char[] sArr = s.toCharArray();
        Arrays.sort(sArr);
        StringBuilder answer = new StringBuilder();
        for (int i = sArr.length-1; i >= 0; i--){
            answer.append(sArr[i]);
        }
        return answer.toString();
    }
}

 

 

 

Dictionary

  • key- value 로 이루어진 순서가 없는 자료형
  • 선언 : f = {} #최빈값을 저장할 딕셔너리
  • 삽입 : f[n] = 1
print(student.items())  # 출력: dict_items([('name', 'John'), ('age', 21), ('year', 3)])
print(student.keys())  # 출력: dict_keys(['name', 'age', 'year'])
print(student.values())  # 출력: dict_values(['John', 21, 3])

 

정답

  • 최빈값을 구하기 위해서 딕셔너리를 선언하여 입력
  • around(값) -> 소수점 0번째 자리까지 반올림해서 출력 : 1.6. -> 2
  • around(값, 1) ->  소수점 1번째 자리까지 반올림해서 출력 : 1.23 -> 1.2
n = int(input())

number_list = []
for _ in range(n):
    k = int(input())
    number_list.append(k)

f = {} #최빈값을 저장할 딕셔너리
for n in number_list:
    if n in f: #키값이 이미 딕셔너리에 존재한다면, 기존에 있는 거에서 1증가
        f[n] += 1
    else:
        f[n] = 1

max_count = max(f.values()) #가장 많이 나온 빈도수
modes = []
m_result = 0

for key, value in f.items():
    if value == max_count:
        modes.append(key)


if len(modes) == 1:
    m_result = modes[0]
else:
    modes.sort()
    m_result = modes[1]


avg = round(sum(number_list) / len(number_list))
number_list.sort()
median = number_list[len(number_list) // 2]

number_range = max(number_list) - min(number_list)

print(avg)
print(median)
print(m_result)
print(number_range)

 

파이썬 정렬

오름차순 : 배열.sort()

내림차순 : 배열.sort(reverse = True)

n = int(input())
num = []
for _ in range(n):
    num.append(int(input()))

num.sort()
for i in num:
    print(i)

 

자바 정렬

  • 오름차순 : Arrays.sort(배열명)
  • 내림차순 :  Arrays.sort(배열명, Collections.reverseOrder());
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 입력 개수
        int n = sc.nextInt();
        int[] num = new int[n];

        // 배열에 숫자 입력
        for (int i = 0; i < n; i++) {
            num[i] = sc.nextInt();
        }

        // 정렬
        Arrays.sort(num);

        // 정렬된 배열 출력
        for (int i : num) {
            System.out.println(i);
        }
    }
}

음양 더하기

class Solution {
    public int solution(int[] absolutes, boolean[] signs) {
        int answer = 0;
        for (int i = 0; i < absolutes.length; i++){
            if(!signs[i]) {
                answer -= absolutes[i];
            }else {
                answer += absolutes[i];
            } 
        }
        return answer;
    }
}

 

핸드폰 번호 가리기

String 길이 : length()

Array 길이 : length

  • String은 불변객체이기 때문에 문자열을 수정할 때마다 새로운 객체를 생성
  • StringBuilder는 문자열을 수정해도 기존 객체를 그대로 사용
  • 단일스레드 환경에서만 사용 -> 멀티스레드는  위험 (동기화를 안함)
    • 멀티스레드 환경에서는 -> StringBuffer (동기화 지원)
  • 동기화 : 멀티스레드 환경에서는 여러 스레드가 동시에 실행되며, 같은 메모리 공간이나 데이터를 동시에 읽고/쓰기 가능 ->  이 과정에서 다음과 같은 문제가 발생 -> 메서드나 블록을 동기화하여 한 번에 하나의 스레드만 실행되도록 제한
    • 즉. 여러 스레드가 공유 자원에 접근할 때 데이터의 일관성과 안전성을 보장하기 위해 작업 순서를 제어하는 메커니즘
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");
class Solution {
    public String solution(String phone_number) {
        StringBuilder answer = new StringBuilder();
        for (int i = 0; i < phone_number.length(); i++){
            if(i < (phone_number.length() - 4)){
                answer.append("*");
            }else {
                answer.append(phone_number.charAt(i));
            }
        }
        return answer.toString();
    }
}

 

없는 숫자 더하기

class Solution {
    public String solution(String phone_number) {
        StringBuilder answer = new StringBuilder();
        for (int i = 0; i < phone_number.length(); i++){
            if(i < (phone_number.length() - 4)){
                answer.append("*");
            }else {
                answer.append(phone_number.charAt(i));
            }
        }
        return answer.toString();
    }
}

 

제일 작은 수 제거하기

  • Arrays.sort(sortedArr) : 해당 배열의 원소값을 오름차순으로 정렬하는 메서드, 정렬되어버림
  • int[] sortedArr = Arrays.copyOf(arr, arr.length) : 배열을 복사
  • int[] answer = new int[arr.length - 1] : 배열의 길이는 뒤에
  • int[] ans = {-1} : 생성과 동시에 초기화는 중괄호와 수 명시 안하고 하기 
import java.util.*;

class Solution {
    public int[] solution(int[] arr) {
        int[] answer = new int[arr.length - 1];
        int[] sortedArr = Arrays.copyOf(arr, arr.length);
        
        if(arr.length == 1) {
            int[] ans = {-1};
            return ans;
        }
        
        Arrays.sort(sortedArr);
        int min = sortedArr[0];
        int idx = 0;
        
        for (int i = 0; i < arr.length; i++){
            if (arr[i] != min) {
                answer[idx] = arr[i];
                idx++;
            }
        }
        
        return answer;
    }
}

기존 OperatorType

  • 연산자 클래스별로 연산자를 나타내는 char값만 존재함
public enum OperatorType {
    ADD('+'),
    SUBTRACT('-'),
    MULTIPLY('*'),
    DIVIDE('/');
    private final char operator;
    public char getOperator(){
        return operator;
    }
    OperatorType(char operator) {
        this.operator = operator;
    }
}
public class ArithmeticCalculator {
    private OperatorType addOperator = OperatorType.ADD;
    private OperatorType subtractOperator = OperatorType.SUBTRACT;
    private OperatorType divideOperator = OperatorType.DIVIDE;
    private OperatorType multiplyOperator = OperatorType.MULTIPLY;
    private ArrayList<Double> results = new ArrayList<>();
    public <T extends Number> Double calculate(T number1, T number2, char operator){
        double answer = 0.0;
        //연산 수행
        if (operator == addOperator.getOperator()) {
            answer = number1.doubleValue() + number2.doubleValue();
        } else if (operator == subtractOperator.getOperator()) {
            answer = number1.doubleValue() - number2.doubleValue();
        } else if (operator == multiplyOperator.getOperator()) {
            answer = number1.doubleValue() * number2.doubleValue();
        } else if (operator == divideOperator.getOperator()) {
            answer = number1.doubleValue() / number2.doubleValue();
        }
        results.add(answer);
        return answer;
    }
}

 

이런식으로 Enum 클래스에 연산자만 선언해서 계산기 클래스 내부에서 해당 연산자값을 비교해오는 방식으로 사용했었는데, 아무리 생각해봐도 뭔가 연산자만 비교하기에는 아쉬운 것 같다는 생각이 들었다. 연산자 char 값만 비교하기에는 너무 메모리상으로도 낭비같고 차라리 계산기 클래스 내부에서 static final로 상수를 선언하면 되지 않나 싶었다.

 

그래서 연산자 값 뿐만 아니라 차라리 메서드를 Enum 클래스에서 따로 구현해서 수행하는 편이 훨씬 의미가 있을 거라는 생각이 들었다. 나중에 더하기 로직을 내부적으로 +10 씩 더한다거나 등등 유지보수성과 명확성을 위해서는 훨씬 유용할 것 같다는 생각이 들어서 수정하게 되었다

 

수정된 OperatorType

  • calculate 메서드를 추상 메서드로 선언 후 각각의 연산자 타입에서 오버라이딩 하여 다르게 구현
package com.example.calculator3;

import com.example.common.exception.DivideByZeroException;

// 연산자 및 해당 연산자에 해당하는 연산메서드를 선언한 enum class
public enum OperatorType {
    PLUS("+") {
        @Override
        public <T extends Number> double calculate(T firstInput, T secondInput) {
            return firstInput.doubleValue() + secondInput.doubleValue();
        }
    },
    MINUS("-") {
        @Override
        public <T extends Number> double calculate(T firstInput, T secondInput) {
            return firstInput.doubleValue() - secondInput.doubleValue();
        }
    },
    MULTIPLY("*") {
        @Override
        public <T extends Number> double calculate(T firstInput, T secondInput) {
            return firstInput.doubleValue() * secondInput.doubleValue();
        }
    },
    DIVIDE("/") {
        @Override
        public <T extends Number> double calculate(T firstInput, T secondInput) throws DivideByZeroException {
            if (secondInput.doubleValue() == 0) {
                throw new DivideByZeroException();
            }
            return firstInput.doubleValue() / secondInput.doubleValue();
        }
    };

    private final String operator;

    OperatorType(String operator) {
        this.operator = operator;
    }

    //계산을 수행하는 메서드를 추상메서드로 선언하여 각각의 연산자 객체가 오버라이딩하여 구현
    public abstract <T extends Number> double calculate(T firstInput, T secondInput) throws DivideByZeroException;
}
public class ArithmeticCalculator {

    private List<Double> results = new ArrayList<>();

    public <T extends Number> Double calculate(T firstInput, T secondInput, String operator) throws DivideByZeroException {
        double answer = switch (operator) {
            case "+" -> OperatorType.PLUS.calculate(firstInput, secondInput);
            case "-" -> OperatorType.MINUS.calculate(firstInput, secondInput);
            case "*" -> OperatorType.MULTIPLY.calculate(firstInput, secondInput);
            case "/" -> OperatorType.DIVIDE.calculate(firstInput, secondInput);
            default -> throw new IllegalArgumentException("Invalid operator: " + operator);
        };

        results.add(answer);
        return answer;
    }
    }

 

예외처리 추가

  • 잘못 입력받은 연산자 값과 피연산자 값, 0으로 나눈 경우의 예외 처리 클래스를 생성

  • ArithmeticCalculator에 입력값 예외처리에 대한 내용을 넣기에는 ArithmeticCalculator의 책임이 커지는 것 같다고 생각했기에 Parser를 생성하여 Parser객체에서 입력값에 대한 예외처리에 대한 내용을 처리하도록 책임을 분리
  • ArithmeticCalculator에서는 연산 수행과 관련된 예외 처리만 수행하도록 함
/*
* 입력값을 검증하는 class, ArithmeticCalculator에 입력값 검증까지 포함하면 책임이 너무 커지기에 분리
*/
public class Parser {
    private static final String OPERATION_REG = "[+\\-*/]";
    private static final String NUMBER_REG = "^[0-9]*$";

    private String firstInput;
    private String secondInput;
    private String operator;

    private final ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculator();

    public ArithmeticCalculator getArithmeticCalculator() {
        return arithmeticCalculator;
    }

    //입력받은 피연산자가 숫자가 아닌 경우룰 처리하는 예외 처리 메서드
    public void validateInputNumbers(String firstInput, String secondInput) throws BadInputException {
        if (!Pattern.matches(NUMBER_REG, firstInput) || !Pattern.matches(NUMBER_REG, secondInput)) {
            throw new BadInputException("숫자타입");
        }
        setFirstInput(firstInput);
        setSecondInput(secondInput);
    }

    //입력받은 연산자가 사칙연산자에서 벗어난 경우룰 처리하는 예외 처리 메서드
    public void validateOperator(String operator) throws InvalidOperatorException{
        if (!Pattern.matches(OPERATION_REG, operator)) {
            throw new InvalidOperatorException();
        }
        setOperator(operator);
    }

    //필드로 가지고 있는 ArithmeticCalculator를 호출하여 연산을 수행
    public Double executeCalculator() throws DivideByZeroException {
        // 여기서 String 타입의 피연산자들을 실수형이나 정수형으로 변환하여 자유롭게 넣어 줄 수 있음, Integer도 가능
        Double firstNumber = Double.parseDouble(firstInput);
        Double secondNumber = Double.parseDouble(secondInput);
        return arithmeticCalculator.calculate(firstNumber, secondNumber, operator);
    }

    public void setFirstInput(String firstInput) {
        this.firstInput = firstInput;
    }

    public void setSecondInput(String secondInput) {
        this.secondInput = secondInput;
    }

    public void setOperator(String operator) {
        this.operator = operator;
    }

}
public class ArithmeticCalculator {

    private List<Double> results = new ArrayList<>();

    public <T extends Number> Double calculate(T firstInput, T secondInput, String operator) throws DivideByZeroException {
        double answer = switch (operator) {
            case "+" -> OperatorType.PLUS.calculate(firstInput, secondInput);
            case "-" -> OperatorType.MINUS.calculate(firstInput, secondInput);
            case "*" -> OperatorType.MULTIPLY.calculate(firstInput, secondInput);
            case "/" -> OperatorType.DIVIDE.calculate(firstInput, secondInput);
            default -> throw new IllegalArgumentException("Invalid operator: " + operator);
        };

        results.add(answer);
        return answer;
    }
    }

 

Lambda

Lambda란 인스턴스를 생성하지 않고 작성할 수 있는 익명함수!

  • 메서드는 클래스에 포함되어야 하기 때문에 번거로운 제약들이 많음 → 람다의 등장으로 메서드를 변수처럼 다루는 것이 가능 → 람다를 통해서 메서드가 1급 객체가 됨 (이를 통해 자바는 객체지향프로그래밍 뿐만 아닌 함수지향 프로그래밍도 가능)
    • 1급객체 : 메서드(함수)를 변수, 인자, 리턴값에 할당 가능한 것
  • 람다식은 사실 메서드가 아닌, 익명 클래스의 객체

사용법

  • 람다의 모든 매개변수 타입은 생략하는 것이 좋음
  • 매개변수가 하나라면 괄호도 생략  (a) -> a * a 이 아닌 a -> a * a

실체

(int a, int b) -> a > b ? a : b; 의 실제 모습은 아래와 같음 즉, 람다식은 익명 클래스의 객체

new Objcet() {
  int max(int a, int b) {
    return a > b ? a : b;
  }
}

 

Stream

연속적인 데이터들의 순회로, 여러 필터링, 변환과 같은 작업들을 할 수 있게 도와주는  자바의 API

  • 한번에 한개씩 만들어지는 연속적인 데이터 항목들의 모임
  • 프로그램들은 입력 스트림을 통해 데이터를 하나씩 읽고, 출력 스트림을 통해 한개씩 기록
  • 기존에 반복문과 조건문을 활용하여 만든 로직보다 각 단계별로 어떤 "무엇을" 수행할 것인지 확실하게 명시되어 있어 의도를 파악하기 좋음
  • 스트림은 데이터 소스를 변경하지 않음
  • 일회용임

 

특정 값보다 큰 값들만 조회하기

1.  첫 번째 시도

  • cannot resolve symbol “r” 에러
 public List<Double> getResult(int minimum){
        return this.results.stream()
                .filter( r > minimum)
                .collect(Collectors.toList());
    }

 

의문이 들었다.. 내가 찾아본 예시와 다를게 없었기 때문이다. 아래 예시랑 다를게 없다고 생각했는데 이건 정말 실행이 잘 됐기 때문이다.

void filterTest3() {
    List<Human> tmpHumans = humans.stream()
            .filter(h -> h.getMoney() > 2000)
            .collect(Collectors.toList());

 

2.  두 번째 시도

  • 아직 리스트 내부의 값들이 선언이 안되서 그런건가 싶어서 한번 값을 선언해보고 했는데 이것도 동일한 에러가 뜨면서 실행되지 않음
  • 아까와 동일한 cannot resolve symbol “r” 에러
List<Integer> ra = Arrays.asList(1, 2, 3, 4);

    public List<Integer> getResult(int minimum){
        List<Integer> filterdResults = this.ra.stream()
                .filter( r > minimum)
                .collect(Collectors.toList());

        return filterdResults;
    }

 

하지만...!! 아주 바보같은 실수를 하고 만 것이다...!!

다시 차분히 진정하고 비교해보니 람다 표현식에서 매개변수 값을 안넣어버린 것이다...!! (바보🙈)

매개변수는 안넣고 냅다 리턴만 해버리면 될리가 없지..!

 

3.  완성

이번엔 람다 표현식을 정확하게 썼더니 에러가 발생하지 않았다!

public String getResult(int minimum){
        String filterdResults =  this.results.stream()
                .filter( result -> result > minimum)
                .collect(Collectors.toList())
                .toString();
        
        return filterdResults;
    }

 

 

덕분에 람다식을 쓸 때 앞으로는 확실히 알 수 있을 것 같다!!

람다는  매개변수 -> 리턴 형식!

 

+ Recent posts