코드스테이츠 백엔드 부트캠프 22일차 - 재귀, JSON

wish17·2023년 1월 12일
0
post-thumbnail

[Java] Daily Coding

연이율을 이용해 원금이 2배가 되는 시점을 년단위로 리턴해보자.

public class Solution { 
	public int computeWhenDouble(double interestRate) {
		double principal = 100;
		int result = 0;
    for(int i; i<100; i++){
			result=i;
			principal = principal*interestRate;
			if(principal>=200) {
				break;
			}
		}
		return result;
	} 
}
// error: variable i might not have been initialized

int i << 값을 선언만 하고 할당을 안했다 (실수)

public class Solution { 
	public int computeWhenDouble(double interestRate) {
		double principal = 100;
		int result = 0;
    for(int i=1; i<100; i++){
			principal = principal*(100+interestRate)/100;
			if(principal>=200) {
				result=i;
				break;
			}
		}
		return result;
	} 
}

for문 수행식도 문제가 있어 조금 바꾸니 정상적으로 작동했다.

ans

public class Solution { 
	public int computeWhenDouble(double interestRate) {
    double rate = 1 + interestRate / 100;
    double principal = 1;
    int year = 0;
    while (principal < 2) {
      principal = principal * rate;
      year++;
    }
    return year;
  } 
}

while을 쓰는게 좀 더 깔끔했을 것 같다.

Section2 - [자료구조/알고리즘] 재귀, JSON

과제 / StringifyJSON

JSON

데이터 교환을 위해 만들어진 객체 형태의 포맷
(자바 외의 프로그램과 데이터 교환하기 위한 포맷)

jackson 라이브러리에서 제공하는 ObjectMapper클래스를 사용하여
JSON형태로 변경하는 방법

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(message); // 직렬화(serialize)

System.out.println(json);

writeValueAsString(objectMapperName) = 직렬화(serialize), 데이터를 JSON으로 변환하는 메서드

다시 객체의 형태로 만드는 방법

ObjectMapper mapper = new ObjectMapper();
String json = "{\"createdAt\":\"2021-01-12,10:10:10\",\"receiver\":\"박해커\",\"sender\":\"김코딩\",\"message\":\"밥먹을래?\"}";

Map<String, String> deserializedData = mapper.readValue(json, Map.class); // 역직렬화(deserialize)
System.out.println(deserializedData);

readValue(jsonName, Map.class) = 역직렬화(deserialize), JSON을 데이터로 변환하는 메서드

JSON의 기본 규칙

  • 반드시 key에 쌍따옴표를 붙여야 함
  • 반드시 문자열 값(Value)을 쌍따옴표로 감싸야 함
  • key-value (쌍) 사이에 공백이 없어야 함

과제 진행

Java 객체를 JSON타입으로 만들어주는 stringify메서드를 직접 만들어 보자.

git주소에서 fork, clone부터 해보자.

fork한 다음 내 저장소에서 code버튼 누르면 아래와 같이 나온다.

터미널 -> 우분투에서

// clone받을 파일 위치로 이동
cd /mnt/d/AAWonJong/it/java/intelliJ/be-sprint-stringify-json

// git 실행
git init

// 주소 복사해서 clone
git clone HTTPS주소

clone 해온 파일을 열어 Java 객체를 JSON타입으로 만들어주는 stringify메서드를 직접 만들어 보았다.

//초기 상태
public class stringifyJSON {

  public String ObjectMapper(Object data) throws JsonProcessingException {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(data);
  }

  public String stringify(Object data) {

    //입력된 값이 문자열일 경우

    //입력된 값이 Integer일 경우

    //입력된 값이 Boolean일 경우

    //입력된 값이 Object[]일 경우

    //입력된 값이 HashMap일 경우

    //지정되지 않은 타입의 경우에는 "null"을 리턴합니다.

  }
}

우선 문자열일 경우

//입력된 값이 문자열일 경우
    if(data instanceof String){
      String text = "\"";  // """로 쓰면 오류 \가 "를 인식해준다.
      return text+data+text;
    }

문자열 앞뒤에 "를 붙여서 출력하면 끝

if(data instanceof String){
        return String.format("\"%s\"",data); // format을 이용해 더 간단하게 정리가능.
    }

format을 이용하면 한줄로도 가능하다.

Integer일 경우

//입력된 값이 Integer일 경우
    else if(data instanceof Integer){
        String changed =  Integer.toString((Integer) data);
        return changed;
    }

처음에 String일 경우와 마찬가지로 양끝에 "를 붙여줬었는데 그럴필요 없이 Integer를 String타입으로 변환만 해주면 JSON타입이 됐다.

String타입으로 변환하는 방법은 아래와 같이 여러가지 방법이 있다.

//입력된 값이 Integer일 경우
    else if(data instanceof Integer){
        return data + ""; // 문자열과 Integer를 합치면 자동으로 문자열로 변한다.
        return String.format("%d",(Integer)data);
        return String.valueOf(data); // valueOf의 리턴타입은 열거형
        return data.toString(); // 주소값이 출력될 수 있음. 좋은방법은 아님.
    }

toString()valueOf() 모두 Object의 값을 String으로 변환하지만

toString()은 null값을 입력받는 경우 Null PointerException(NPE)을 발생시킨다.
(cf. NPE = 실행 예외 클래스)

valueOf()는 null값을 입력받는 경우 "null"이라는 문자열이 할당된다.
(cf. valueOf()열거형, Integer 등 문자열 외에 다양한 형태변환에도 사용가능)

Boolean일 경우

//처음푼 방법
//입력된 값이 Boolean일 경우
    else if(data instanceof Boolean){
        String changed =  String.valueOf(data);
        return changed;
    }

Integer일 경우와 마찬가지다.

    else if(data instanceof Boolean){
        return data + "";
        return String.format("%b", data);
        return  data.toString();
        
        if((Boolean)data) return "true";
        else return "false"; //좀 미련한 방법.. 하드코딩..
    }

배열일 경우

만들고자하는 형태

//    "[8,"hi"]" 
//    "[8,[[],3,4]]"
//    "[[[]]]"

위와 같은 형태로 배열들이 String으로 출력되도록 만들어 보려고 한다.

//    입력된 값이 Object[]일 경우
    else if(data instanceof Object[]){
      if(((Object[]) data).length == 0) return  "[]";
      String str = "[";
      Arrays.asList(data).stream()
              .flatMap(Array -> (Object)Array) // flatMap 자체는 이중배열이 아닌 요소를 문자단위로 다 뜯어버린다. 즉, 잘못된 방법이다.
              .forEach();// stream 1차 실패... inner타입이 뭘지 알 수 없어서 형변환 했는데 안됨.

      return str;

처음에 Stram을 이용해 이중,삼중 배열들을 다 피고 각 요소를 String으로 타입변환시켜 str에 더해주는 방식으로 하려고 했지만 재귀를 사용하기 적합한 형태도 아니고 flatMap 자체는 이중배열이 아닌 요소를 문자단위로 다 뜯어버린다는 것을 늦게 알게되었다. 즉, 잘못된 방법이다.
(또한 data타입 뭘지 정해지지 않아 람다식을 전개하는데 오류도 계속 났다.)

else if(data instanceof Object[]){

      if(((Object[]) data).length == 0) return  "[]";
      String str = "[";
      
      for (Object o : Arrays.asList(data)) { // data타입 선언 잘못해서 무한루프... 돌았었다..
          str += stringify(o)+",";
      }
      str = str.substring(0,str.length()-1);
      str += "]";
      
      return str;
}
      

우선 빈배열일 경우 빈배열모양의 String을 출력하도록 하고 str를 이용해 대괄호 사이에 요소들의 String형을 for문과 재귀를 이용해 쌓아가도록 해봤다.

java.lang.StackOverflowError
무한루프 오류가 계속 생겼다.

배열인 data를 ArrayList형으로 바꾼 다음 String으로 바꾸려 한 것인데
Arrays.asList(data).toString()이렇게 쓰니까 toString()에서 오류가 난다고 지워버렸더니 컴파일 에러가 안난다고 넘겨버리고 수행식을 만들어 버렸다. (런타임에러가 당연히 일어날걸 생각도 못함)

그래서 타입을 다시 맞추다보니 굳이 list형으로 바꿔서 할 필요가 없다는 생각이 들어 아래와 같이 했다.

else if(data instanceof Object[]){

      if(((Object[]) data).length == 0) return  "[]";
      String str = "[";

	  for (Object o : (Object[])data) { // data타입 선언 잘못해서 무한루프... 돌았었다..
          str += stringify(o)+",";
      }
      str = str.substring(0,str.length()-1);
      str += "]";


      return str;

정상적으로 기능수행하는 것을 확인했다.

위에서 언급한 내용과 같이 굳이 list를 사용할 필요는 없지만 사용해도 풀 수 있을 것 같아 해보았다.

else if(data instanceof Object[]){
      if(((Object[]) data).length == 0) return  "[]";
      
      String str = "[";
      
	  Iterator<?> it = Arrays.asList((Object[])data).iterator(); 
      
      while (it.hasNext()) {
      str += stringify(it.next()) + ",";
      }
      
      str = str.substring(0,str.length()-1);
      str += "]";   // Iterator 2차 실패... 무한루프..
      return str;
}

어떤 타입의 요소가 들어있을지 알 수 없어서 와일드카드(<?>)를 이용해주었다.
( 그냥 Object타입으로 해도 된다. )
똑같이 정상적으로 기능수행 한다.

else if(data instanceof Object[]){
	Object[] arr = (Object[]) data;
    
    for(int i = 0; i < arr.length; i++) {
    arr[i] = stringify(arr[i]);
    }

    return Arrays.toString(arr).replaceAll(" ", "");
}

일반적인 for문으로도 위와 같이 가능하다. 어찌보면 제일 간단하다...
array(배열)을 바로 String으로 바꿀 수 있는 것을 까먹고 있었다.

HashMap일 경우

//입력된 값이 HashMap일 경우

/*
	  만들고자하는 형태
ad : 3, fg : 5 ->   "{"ad":"3","fg":"5"}"
                    "{"a":"apple"}"
                    "{"bar":false,"foo":true,"baz":null}"
                    "{"bar":false,"foo":true,"baz":null,"
      
*/


else if(data instanceof HashMap){
      
      if(((HashMap) data).isEmpty()){
        return "{}";
      }
      String res = "{";
      for (Map.Entry<?, ?> o : ((HashMap<?, ?>) data).entrySet()) {
        res += stringify(o.getKey())+ ":" + stringify(o.getValue())+",";
      }
      res = res.substring(0,res.length()-1);
      res += "}";

      return res;
    }

    //지정되지 않은 타입의 경우에는 "null"을 리턴합니다.
    return "null";
  }

와일드카드를 이용해 요소의 타입에 상관없이 기능하도록 했지만 Object로 해도 상관없다.

위 방법은 res라는 String변수에 키와값을 하나씩 String으로 변환 시킨 다음 더해서 쌓아가는 방식이다.

아래와 같이 요소값들을 먼저 string타입으로 변환 시킨 후 map을 통째로 string으로 변화 시켜도 된다.

    else if(data instanceof HashMap){
    
        HashMap<?,?> map = (HashMap<?,?>) data;
        HashMap<String,String> result = new LinkedHashMap<>(); // 그냥 HashMap은 순차저장x LinkedHashMap은 순차적으로 저장해준다.

        for(Map.Entry<?,?> entry : map.entrySet()){
            String value = stringify(entry.getValue());
            String key = stringify(entry.getKey());

            result.put(key, value);
        }
        
        return result.toString().replaceAll("=",":").replaceAll(" ", "");
        
    }

for문안에 있는 재귀 내용이 빠지면 아래와 같이 요소값들이 JSON형태를 안하고 있어 실패하는 것을 확인할 수 있다.

전체 메서드

public String stringify(Object data) {

    //입력된 값이 문자열일 경우
    if(data instanceof String){
      String text = "\"";
      return text+data+text;
    }

    //입력된 값이 Integer일 경우
    else if(data instanceof Integer){
        String changed =  Integer.toString((Integer) data);
        return changed;
    }
    
    //입력된 값이 Boolean일 경우
    else if(data instanceof Boolean){
        String changed =  String.valueOf(data);
        return changed;
    }
    
    //입력된 값이 Object[]일 경우

    else if(data instanceof Object[]){
      if(((Object[]) data).length == 0) return  "[]";
      String str = "[";

      Iterator<?> it = Arrays.asList((Object[])data).iterator();
      while (it.hasNext()) {
      str += stringify(it.next()) + ",";
      }
      str = str.substring(0,str.length()-1);
      str += "]";   // Iterator 2차 실패... 무한루프..
      return str;
    }
    
    //입력된 값이 HashMap일 경우
    else if(data instanceof HashMap){
      
      if(((HashMap) data).isEmpty()){
        return "{}";
      }
      String res = "{";
      for (Map.Entry<?, ?> o : ((HashMap<?, ?>) data).entrySet()) {
        res += stringify(o.getKey())+ ":" + stringify(o.getValue())+",";
      }
      res = res.substring(0,res.length()-1);
      res += "}";

      return res;
    }

    //지정되지 않은 타입의 경우에는 "null"을 리턴합니다.
    return "null";
  }
// 간략하게 바꾸기

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class JSON {
    public String stringify(Object data) {

        //입력된 값이 문자열인 경우
        if(data instanceof String){
            //String text = "\"";  // """로 쓰면 오류 \가 "를 인식해준다.
            //return text+data+text;
            return String.format("\"%s\"",data); // format을 이용해 더 간단하게 정리가능.
        }

        //입력된 값이 Integer일 경우
        else if(data instanceof Integer){
//        String changed =  Integer.toString((Integer) data);
//        return changed;
            //return data + ""; // 문자열과 Integer를 합치면 자동으로 문자열로 변한다.
            //return String.format("%d",(Integer)data);
            return String.valueOf(data);
//        toString()은 null값을 입력받는 경우 Null PointerException(NPE)을 발생시킨다.
//        valueOf()는 null값을 입력받는 경우 "null"이라는 문자열이 할당된다.
            //return data.toString(); // 주소값이 출력될 수 있음. 좋은방법은 아님.

        }
        //입력된 값이 Boolean일 경우
        else if(data instanceof Boolean){
//        String changed =  String.valueOf(data);
//        return changed;
            //return data + "";
            //return String.format("%b", data);
            if((Boolean)data) return "true";
            else return "false"; //이렇게 하드코딩해도 됨.

            //return  data.toString();
        }
//    입력된 값이 Object[]일 경우
//    "[8,"hi"]"
//    "[8,[[],3,4]]"
//    "[[[]]]"

        else if(data instanceof Object[]){
            //if(((Object[]) data).length == 0) return  "[]";
            //String str = "[";

//      Arrays.asList(data).stream()
//              .flatMap(Array -> (Object)Array) // flatMap 자체는 이중배열이 아닌 요소를 문자단위로 다 뜯어버린다. 즉, 잘못된 방법이다.
//              .forEach();// stream 1차 실패... inner타입이 뭘지 알 수 없어서 형변환 했는데 안됨.

//      Iterator<?> it = Arrays.asList((Object[])data).iterator();
//      while (it.hasNext()) {
//          str += stringify(it.next()) + ",";
//      }
//      str = str.substring(0,str.length()-1);
//      str += "]";   // Iterator 2차 실패... 무한루프..
//      return str;


//      for (Object o : (Object[])data) { // data타입 선언 잘못해서 무한루프... 돌았었다..
//          str += stringify(o)+",";
//      }
//      str = str.substring(0,str.length()-1);
//      str += "]";
//      return str;

            Object[] arr = (Object[]) data;
            for(int i = 0; i < arr.length; i++) {
                arr[i] = stringify(arr[i]);
            }

            return Arrays.toString(arr).replaceAll(" ", "");
            //replaceAll(" ", "") = 공백 제거, regex를 replacement로 바꿔라.




//        Arrays.stream(data).flatMap(innerArray -> Arrays.stream(innerArray))
            // iter
            //Iterator<Object> iterator = data.iterator();
            //for (Object o : (Object[]) data) {
//        return new stringifyJSON(o);
//      }
        }
        //입력된 값이 HashMap일 경우
        else if(data instanceof HashMap){
      /*
      ad : 3, fg : 5
      "{"ad":"3","fg":"5"}"
      "{"a":"apple"}"
      "{"bar":false,"foo":true,"baz":null}"
      "{"bar":false,"foo":true,"baz":null,"
       */
//      if(((HashMap) data).isEmpty()){
//        return "{}";
//      }
//
//      String res = "{";
//      for (Map.Entry<?, ?> o : ((HashMap<?, ?>) data).entrySet()) {
//        res += stringify(o.getKey())+ ":" + stringify(o.getValue())+",";
//      }
//      res = res.substring(0,res.length()-1);
//      res += "}";
//
//      return res;


            HashMap<?,?> map = (HashMap<?,?>) data;
            HashMap<String,String> result = new LinkedHashMap<>(); // 그냥 HashMap은 순차저장x LinkedHashMap은 순차적으로 저장해준다.

            for(Map.Entry<?,?> entry : map.entrySet()){
                String value = stringify(entry.getValue());
                String key = stringify(entry.getKey());

                result.put(key, value);
            }

            return result.toString().replaceAll("=",":").replaceAll(" ", "");

        }

        //지정되지 않은 타입의 경우에는 "null"을 리턴합니다.
        return "null";
    }
}

이제 완료한 과제를 pr(Pull requests)해보자.

// push 하는 방법

git add .
(git status == add 확인)

git commit -m "변경내용"
(git log == commit 확인)

git remote add 주소명 HTTPS주소
(git remote --v == 주소 확인)

git push 주소명 main

이렇게 push한 뒤 git사이트 나의 저장소에서 pr하면 된다.

오늘의 정리

타입에 살고 타입에 죽는다.
타입 잘맞추자.

0개의 댓글