gnidoc 2019. 1. 10. 23:32
반응형

2019/01/09 - [프로그래밍/알고리즘] - 3일차 리뷰


step4는 if문


어려운 문제는 없었지만 채점 시스템의 문제로 고통 받은 문제를 리뷰 ㅠㅠ




4344번

대학생 새내기들의 90%는 자신이 반에서 평균은 넘는다고 생각한다. 당신은 그들에게 슬픈 진실을 알려줘야 한다.

첫째 줄에는 테스트 케이스의 개수 C가 주어진다.

둘째 줄부터 각 테스트 케이스마다 학생의 수 N(1 ≤ N ≤ 1000, N은 정수)이 첫 수로 주어지고, 이어서 N명의 점수가 주어진다. 점수는 0보다 크거나 같고, 100보다 작거나 같은 정수이다.

각 케이스마다 한 줄씩 평균을 넘는 학생들의 비율을 반올림하여 소수점 셋째 자리까지 출력한다.


매우 슬픈 문제이자 날 고통스럽게한 문제이다.


내 풀이 : https://github.com/gnidoc327/one-day-one-problem/blob/master/src/step_by_step/step4/Problem4344/Main.java


문제만 길지 그냥 평균 아래 인원 비율을 구하는 것이다.

그런데 아래는 내가 고통 받았던 이유의 코드이다.

1
2
3
4
5
6
7
System.out.printf("%.3f"100.0 * loser / n);
System.out.println("%");
 
//같은 결과이지만 오답임
//double result = 100.0 * loser / n;
//result = Math.round(result * 1000) / 1000.0;
//System.out.println(result + "%");
cs


결과인 백분율 값을 소수 3번째 자리로 반올림해서 출력하는 코드인데

아래 주석코드가 처음에 작성하고 계속 틀렸던 부분이다.

콘솔 코딩이 오랜만이라 printf가 있는지 까먹었지만 

꼼수로 반올림&소수점자리 처리를 해서

예제 결과와 똑같이 출력했다


그런데 계속 오답인 것이다 ㄷㄷ



왜 틀렸는지에 대한 최소한의 피드백(예제 케이스 정답 정도)이 없으니

뭐가 문제인지 몰랐는데

다른 사람들이 푼 정답을 보니 printf를 사용했다.

그래서 나도 printf를 쓰니 정답이더라....(눙물)


그래서 println과 printf를 까보았다

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
class PrintStream {
    // printf
    public PrintStream format(String format, Object ... args) {
        try {
            synchronized (this) {
                ensureOpen();
                if ((formatter == null)
                    || (formatter.locale() !=
                        Locale.getDefault(Locale.Category.FORMAT)))
                    formatter = new Formatter((Appendable) this);
                formatter.format(Locale.getDefault(Locale.Category.FORMAT),
                                 format, args);
            }
        } catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        } catch (IOException x) {
            trouble = true;
        }
        return this;
    }
 
    public PrintStream printf(String format, Object ... args) {
        return format(format, args);
    }
 
    // println
    private void write(String s) {
        try {
            synchronized (this) {
                ensureOpen();
                textOut.write(s);
                textOut.flushBuffer();
                charOut.flushBuffer();
                if (autoFlush && (s.indexOf('\n'>= 0))
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }
 
    public void print(String s) {
        write(String.valueOf(s));
    }
 
    public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
cs

대충 보면 

printf는 format() -> formatter.format()으로 출력하고

println은 print() -> write()로 출력한다.


제대로 보면

printf의 format은 PrintStream의 멤버변수인 formatter의 format 호출해서 출력하는데

formatter의 format을 또 파고 들어가면 

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
class Formatter{
    public Formatter format(Locale l, String format, Object ... args) {
        ensureOpen();
 
        // index of last argument referenced
        int last = -1;
        // last ordinary index
        int lasto = -1;
 
        List<FormatString> fsa = parse(format);
        for (FormatString fs : fsa) {
            int index = fs.index();
            try {
                switch (index) {
                case -2:  // fixed string, "%n", or "%%"
                    fs.print(null, l);
                    break;
                case -1:  // relative index
                    if (last < 0 || (args != null && last > args.length - 1))
                        throw new MissingFormatArgumentException(fs.toString());
                    fs.print((args == null ? null : args[last]), l);
                    break;
                case 0:  // ordinary index
                    lasto++;
                    last = lasto;
                    if (args != null && lasto > args.length - 1)
                        throw new MissingFormatArgumentException(fs.toString());
                    fs.print((args == null ? null : args[lasto]), l);
                    break;
                default:  // explicit index
                    last = index - 1;
                    if (args != null && last > args.length - 1)
                        throw new MissingFormatArgumentException(fs.toString());
                    fs.print((args == null ? null : args[last]), l);
                    break;
                }
            } catch (IOException x) {
                lastException = x;
            }
        }
        return this;
    }
 
    private interface FormatString {
        int index();
        void print(Object arg, Locale l) throws IOException;
        String toString();
    }
 
    private class FixedString implements FormatString {
        private String s;
        private int start;
        private int end;
        FixedString(String s, int start, int end) {
            this.s = s;
            this.start = start;
            this.end = end;
        }
        public int index() { return -2; }
        public void print(Object arg, Locale l)
            throws IOException { a.append(s, start, end); }
        public String toString() { return s.substring(start, end); }
    }
}
cs

inner class인 formatString의 print를 사용해서 출력한다.

하지만 formatString은 interface이고 

실제로는 10번째 줄 parse에서 FixedString class를 사용한다.

그래서 실제로 출력하는 FixedString.print를 보아하니(61번째 줄)

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
class Formatter {
    private Appendable a;    // a = PrintStream
 
    public void print(Object arg, Locale l) throws IOException { 
        a.append(s, start, end); 
    }
 
class PrintStream {
    public PrintStream format(String format, Object ... args) {
        try {
            synchronized (this) {
                ensureOpen();
                if ((formatter == null)
                    || (formatter.locale() !=
                        Locale.getDefault(Locale.Category.FORMAT)))
                    formatter = new Formatter((Appendable) this);
                formatter.format(Locale.getDefault(Locale.Category.FORMAT),
                                 format, args);
            }
        } catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        } catch (IOException x) {
            trouble = true;
        }
        return this;
    }
 
    public PrintStream append(CharSequence csq) {
        print(String.valueOf(csq));
        return this;
    }
}
cs

그럼 저 a는 뭐였냐 바로 PrintStream(this)이다.

그럼 PrintStream의 append를 가보면....?

println과 같은 print를 사용하지만 String.valueOf(csq)로 출력한다.


정리해보자면

printf() => print(String.valueOf(csq))

println() => print(String x)

이거다.


print에서 write를 부를때 String.valueOf()를 무조건 호출하므로 다른 객체(타입)가 아니다.

new line(\n)에 대한 문제도 아닌게 "%"를 똑같이 println으로 출력해도 다르다는 것이다.


그런데 생각해보니 오답으로 나오는 부분은 내가 double로 출력했고

정답으로 나오는 부분은 float으로 출력했다.

double이 더 긴 소수점 보증해주는 타입인데

오히려 이걸 사용하므로써 오차가 생긴거 아닐까? 라는 생각이 들긴했다.

왜냐하면 컴퓨터의 소수점 계산은 약간 부정확하고 

cpu에 따라서 계산이 다를 수도 있기 때문이다.


백준 알고리즘 사이트가 aws를 사용하는걸로 아는데 amd서버라면 

intel cpu를 쓰는 내 컴퓨터와 결과가 다를 수도 있겠지....

가 내 결론이었다.


테스트하기는 케이스 찾기가 어려우니 앞으로 float 연산에서는 최대한 float만 쓰는걸로....

아 알고리즘 빡세다

반응형