충전이후 운행시간 구하기

등록번호별로 운행, 충전중, 완충과 구동시간이 표시된 데이터를 기준으로 월별 최소, 최대, 평균 충전 사용 시간을 구하려고 합니다. 그런데, 운행과 충전중, 완충 데이터가 연속되는 경우도 있고, 최초 충전의 경우는 이전 충전 기록이 없음에 따라 이들을 고려한 수식을 만들려고 합니다.

1. 문제

아래 표를 보면 A열이 구동시간, B열이 년-월, C열이 날짜, D열이 배터리 상태, E열이 등록번호, F열이 배터리 잔량, G가 최종시간으로 되어 있고,

구동시간, 월, 날짜, 배터리상태, 등록번호, 배터리 잔량, 최종시간이 있는 기초 데이터

오른쪽에 원하는 집계표 양식이 있습니다. 월별로 1회 충전 사용 시간의 최소, 최대, 평균 값을 구하는 것입니다.

월별 최소, 최대, 평균 1회 충전 사용 시간 집계표 양식

2. 문제의 정의

문제를 풀려면 문제가 뭔지 알아야 합니다.

처음에는 완충이 왜 여러 번 나오지, 다른 차라서 그렇겠지 했는데, 차는 같은데 계속 꽂아 놓아서 10분 간격으로 완충이라고 표시되는 것이었다.

완충 데이터가 연속돼서 다른 차량인 줄 알았으나 동일한 차량이 완충 데이터를 계속 보내고 있음

완충 시간이 연속될 경우 시간을 어떻게 구하는지 물어보니 첫번째 완충에서 이전 마지막 완충시간을 빼서 구한다고 한다.

운행이후 최초 완충 시간에서 운행 이전 마지막 완충 시간을 빼서 운행 시간을 구함

완충이 아니라 충전중이다가 운행을 할 경우에도 최초 충전중 시간에서 이전 최종 완충 또는 충전중 시간을 뺀다

처음에는 충전중이었다가 완충이 되는 경우에 완충에서 이전의 충전중 또는 완충시간을 빼는 거라고 잘못 생각하고,

아래와 같은 경우 완충이 179행에 있고, 이전 충전중이 168행에 있으니 179행의 A열에서 168행의 A열을 빼니 1:49:35이고, 중간에 운행에서 충전중일 때는 게산 안하는 것인 줄 알았다.

충전중이다가 완충인 경우 수식과 최초 충전중으로 이전에 충전중 또는 완충이 없을 때 처리 방법 고민

그러나. 운행에서 충전중으로 바뀌는 시점에 충전중인 172행에서 168행을 빼는 것이 맞았다.

정리를 하면 운행에서 충전중 또는 완충으로 바뀔 때(신기하게 충전중을 거치지 않고 바로 완충 표시가 나옴) 운행 이전의 충전중 또는 완충 시간을 빼서 운행 시간을 구하는 것이다. => 기준 1

그리고, 이전 충전 기록이 없을 때 0으로 표시할 것이지, 아니면 최초 운행 시간을 기준으로 할 것인지 의문이 있는데, 0으로 표시하는 것보다는 최초 운행시간을 기준으로 하는 것이 맞다. => 기준 2

3. 해결방법

이제 문제와 기준을 알았으니 수식만 만들면 된다.

가. 텍스트로 된 날짜 데이터 변환하기

A2셀에서 A4셀까지 범위를 잡은 후 상태 표시줄을 보니 개수: 3이라고 표시된다. 날짜 형식이 아니라 텍스트이다.

날짜인 줄 알았더니 텍스트라 개수만 표시되고 숫자인 경우 표시되는 합계 등은 표시되지 않음

(1) 날짜 시간 데이터 바꾸기

H2셀에 1이라고 입력하고 복사한 후 A2셀부터 맨 아래까지 선택한 후 마우스 오른쪽 버튼을 누른 후 선택하여 붙여넣기를 누르고(오른쪽 꺾기를 누르면 한번 더 선택하여 붙여넣기를 눌러야 하므로 불편),

1을 복사한 후 마우스 오른쪽 버튼을 누르고 선택하여 붙여넣기 메뉴를 클릭함

곱하기를 누르고, 확인 버튼을 누른다.

선택하여 붙여넣기 대화 상자에서 곱하기 선택 화면

그러면 모두 실수로 바뀌는데 날짜만 있으면 정수인데, 시간까지 있어서 실수가 되는 것입니다.

텍스트 형식의 날짜에 1을 곱하니 실수로 표시됨

이제 A열의 표시형식을 yyyy-mm-dd hh:mm:ss로 바꿉니다.

그런데, 마우스 오른쪽 버튼을 누른 후 셀 서식을 선택하고 사용자 지정으로 들어가봐도 yyyy-mm-dd h:mm까지만 있고, hh:mm:ss는 없습니다. 그러면 형식 바로 아래에 있는 yyyy-mm-dd h:mm를

표시형식에 yyyy-mm-dd hh:mm:ss가 없고 yyyy-mm-dd h:mm만 있음

yyyy-mm-dd hh:mm:ss로 수정하면 됩니다. 형식을 수정하니 위의 보기도 바로 변경됩니다. 이제 확인 버튼을 누르면

날짜 형식 데이터의 표시형식을 숫자에서 yyyy-mm-dd hh:mm:ss 으로 변경

A열은 날짜 표시 형식이 연-월-일 시:분:초로 바뀌었는데, B열과 C열은 여전히 실수입니다.

날짜 형식의 데이터를 yyyy-mm-dd hh:mm:ss 표시형식으로 변경한 화면

(2) 연-월 바꾸기

B2셀의 수식을 살펴보니 =LEFT(A2,7)이라고 문자열 기준으로 수식이 되어 있습니다.

A열에 텍스트 형식의 날짜가 있다는 전제로 Left 함수를 이용해 연월을 구했으나, A열이 날짜 형식으로 변경됨에 따라 실수로 표시됨

따라서, 수식부터 Year와 Month 함수를 이용하거나 Text 함수를 이용해 수정해야 합니다.

Text 함수를 이용하면 =TEXT(A2,”yyyy-mm”)가 됩니다. 표시형식을 지정하는 것과 비슷합니다.

연월 데이터를 Text 함수를 이용해 수정 입력

(3) 날짜(상세) 바꾸기

C2셀의 수식으 보면 =LEFT(A2,10)으로 문자열 기준으로 수식이 되어 있습니다.

이 때도 Text함수를 이용할 수 있는데, 이번에는 복잡하지만 Date 함수와 Year, Month, Day 함수를 이용해서 구해보겠습니다.

C2셀에 =DATE(YEAR(A2),MONTH(A2),DAY(A2))라고 입력하니 숫자가 아닌 날짜 표시형식으로 표시됩니다.

텍스트 형식으로 입력된 수식을 Date 함수를 이용해 수정하니 날짜 표시형식으로 표시됨

이제 B2셀과 C2셀을 마우스로 끌어서 선택한 후 C2셀의 채우기 핸들을

연월과 날짜 수식이 있는 B2셀과 C2셀을 선택하면 C2셀에 채우기 핸들이 표시됨

더블 클릭하면 맨 아랫줄까지 자동으로 수식이 복사됩니다.

월과 날짜 수식을 맨 아랫줄까지 복사

이제 날짜 변환이 모두 끝났으니 H2셀의 1을 지웁니다.

나. 운행시간 구하는 수식 작성하기

그런데 https://overmt.com/pyhub-mcptools로-엑셀-문제-풀기/ 방법을 알고 나니 생각이 없어집니다.

claude desktop을 실행한 후 아래와 같이 질문하니, 이 때는 이전 완충 또는 충전중인 구동시간이 없을 때는 0으로 처리해야 하겠다고 생각할 때입니다.

클로드에게 수식에 필요한 요구사항을 명시한 프롬프트

계산을 잘 못하므로 H561셀에 수식을 =A561-A541이라고 넣고 다시 물어봅니다.

클로드가 헤매고 있어서 맞는 수식을 예시로 입력한 화면

수식을 넣으면 실수로 표시돼서 E열의 표시형식을 [h]:mm:ss로 바꿔야 합니다.

실수를 시:분:초 표시형식으로 변경

그리고 시간이 좀 지나서 H168에 수식을 넣었다고 해서 확인해보니

클로드가 입력한 수식

=A168-A101로 계산한 값과 일치합니다.

다른 셀에서도 수식이 맞는지 체크하는 화면

J168셀의 채우기 핸들을 J90까지 끌고 값을 =A90-A2로 확인해보니 맞습니다.

클로드가 제시한 수식을 검산하는 장면

중간에 아래와 같이 수식을 제공해서

프롬프트에 따라 클로드가 제시한 수식

아래와 같이 수정 요구하고,

claude가 제시한 수식을 수정 요청하는 프롬프트

수식에서 OFFSET(D561,-1,0)를 간단하게 D560으로 수정하는 등 약간의 손길이 가야합니다.

=IF(AND(E561=E561,OR(D561=”완충”,D561=”충전중”),OFFSET(D561,-1,0)=”운행”), A561-IFERROR( INDEX(A$2:A560,MAX(IF((E$2:E560=E561)*((D$2:D560=”완충”)+(D$2:D560=”충전중”)),ROW(A$2:A560)-1))), INDEX(A$2:A560,MIN(IF((E$2:E560=E561)*(D$2:D560=”운행”),ROW(A$2:A560)-1))) ),””)

그리고, J168셀의 수식을 채우기 핸들을 더블 클릭해서 다른 셀에도 채운 후 등록번호가 바뀌는 시점에서 어떻게 되는지 해보니 #VALUE!에러가 발생합니다.

claude가 제시한 수식

다시 왜 오류가 발생하는지 물어보니

=IF(AND(E288<>””,OR(D288=”완충”,D288=”충전중”),D287=”운행”),A288-IFERROR(INDEX(A$2:A287,MAX((E$2:E287=E288)((D$2:D287=”완충”)+(D$2:D287=”충전중”))(ROW(E$2:E287)))-1),INDEX(A$2:A287,MATCH(E288,E$2:E287,0))),””)

라고 충전중 또는 완충이 없을 때 값을 등록번호가 일치하는 최초 행으로 바꿔줍니다.

이제 다른 셀에도 적용하니 문제없습니다.

좀 손은 가지만, 그리고, 엑셀에 대한 기본 지식이 있어야 제시한 수식을 이해하고 수정 요청을 할 수 있지만 대단합니다.

다. 집계표 만들기

필터를 없애고,집계표 왼쪽에 등록번호가 없으므로 추가합니다.

S5셀에

MINIFS($H:$H,$E:$E,$I5,month($B:$B),VALUE(LEFT(S$2,LEN(S$2)-1)),$H:$H,”>0″)

라고 입력하면 month($B:$B)때문에 에러가 발생하므로 B열을 월만 표시하던가, 월이 있는 다른 열을 만들어야 합니다. B열을 =month(A2)로 해서 월로 바꾸겠습니다.

MaxIfs 수식 안에서 Month 함수를 사용 시 에러 발생

그리고, 아래와 같이 입력하면

=MINIFS($H:$H,$E:$E,$I5,$B:$B,VALUE(LEFT(S$2,LEN(S$2)-1)),$H:$H,”>0″)

S5셀의 값이 구해집니다.

이런 식으로 다른 셀에도 수식을 복사해서 붙여넣는데, min은 max 또는 average로 수정하고, 집계표의 월을 지정하는 주소도 수정해야 합니다. 복사할 때는 최소,최대,평균 3개를 한꺼번에 복사해야 합니다.

그런데 해당 월의 데이터가 없을 때 AverageIfs 수식에서 #DIV/0!에러가 발생하므로 ifError함수를 추가해서 0으로 지정합니다.

=IFERROR(AVERAGEIFS($H:$H,$E:$E,$I5,$B:$B,VALUE(LEFT(J$2,LEN(J$2)-1)),$H:$H,”>0″),0)

#DIV/0! 0으로 나누는 에러 표시

완성된 파일은 아래와 같습니다.

구구단 만들기

구구단을 만들어 보면서 for 반복문과 if 조건문, 그리고 인쇄시 오른쪽 정렬, 가운데 정렬 등 정렬, 공백 추가 등을 알아보겠습니다. 간단한 것인데도 Rust가 다른 언어와 다르다는 것을 다시 한번 더 느꼈습니다.

1. 구구단 만들기(실패)

fn main() {
    let mut i:i32;
    let mut j:i32;
    for i in 2..=9 {
        for j in 1..=9 {
            println!("{} * {} = {}",i,j,i*j) ;
        }
    }
}

위와 같이

let mut i:i32;
let mut j:i32;

라고 하니 warning: unused variable: i와 j 경고가 발생합니다.

그러나, 결과는 아래와 같이 잘 표시됩니다.

다시 말해 for 반복문에서 변수 i와 j를 사용하면 별도로 변수 선언이 필요하지 않다는 것을 알 수 있습니다.

그리고, for 반복문에서는 변수 앞에 mut를 붙이지 않아도 된다는 것을 알 수 있습니다.

오히려 mut를 붙여서 for mut i in 2..=9 {라고 하면

warning: variable does not need to be mutable라는 경고가 발생합니다.

2. 구구단 만들기(부분 성공)

let 문 2개를 지우고 실행하니

fn main() {
    for i in 2..=9 {
        for j in 1..=9 {
            println!("{} * {} = {}",i,j,i*j) ;
        }
    }
}

아래와 같이 출력결과가 잘 표시됩니다. 그러나, 2 * 1 = 2에서 결과값 2가 오른쪽 정렬이 되면 좋겠습니다.

3. 구구단 만들기(성공)

이럴 때 사용할 수 있는 것이 표시될 너비를 지정하는 것으로

{:2}라고 지정하면 됩니다.

따라서 코드는 아래와 같습니다.

fn main() {
    for i in 2..=9 {
        for j in 1..=9 {
            println!("{} * {} = {:2}",i,j,i*j) ;
        }
    }
}

출력 결과가 아래와 같이 깔끔합니다.

4. 구구단 한 줄에 3개씩 표시하기

i를 3칸씩 떨어져서 실행하고,

결과 표시를 i, i+1, i+2를 이용해 표시하는데, 중간에 공백 10칸을 넣은 후 i+1과 i+2를 오른쪽 정렬로 넣기 위해 {:>10}이라고 하면 됩니다.

오른쪽 정렬은 >, 왼쪽 정렬은 <, 가운데 정렬은 ^를 사용합니다.

완성된 코드는 아래와 같습니다.

fn main() {
    for i in (2..=9).step_by(3) {
        for j in 1..=9 {
            println!("{} * {} = {:2}{:>10} * {} = {:2}{:>10} * {} = {:2}",
                      i,j,i*j, i+1,j,(i+1)*j, i+2,j,(i+2)*j);
        }
    }
}

출력된 결과는 아래와 같습니다.

5. 구구단이 한 단이 끝난 다음 공백 2줄 추가하기

공백 2줄을 추가하기 위해

print!(“\n\n”) ;

또는

println!();
println!();

을 사용합니다.

전체 코드는 아래와 같습니다.

fn main() {
    for i in (2..=9).step_by(3) {
        for j in 1..=9 {
            println!("{} * {} = {:2}{:>10} * {} = {:2}{:>10} * {} = {:2}",
                      i,j,i*j, i+1,j,(i+1)*j, i+2,j,(i+2)*j);
        }
        print!("\n\n");
    }
}

6. 구구단 명 표시하기(실패)

구구단명을 표시하려면 구구단명을 가운데 정렬(^사용)로 표시하고, 단 이름과 단 이름사이에 공백을 10개 추가해야 한다고 생각했는데, 8로 해야 제대로 보입니다. 따라서 {:>8}로 하고 값이 없으므로 “”,를 추가했습니다.

완성된 코드는 아래와 같고

fn main() {
    for i in (2..=9).step_by(3) {
        println!("{:^10}{:8}{:^10}{:8}{:^10}",
                    i.to_string()+"단","",
                    (i+1).to_string()+"단","",
                    (i+2).to_string()+"단");
                    
        for j in 1..=9 {
            println!("{} * {} = {:2}{:>10} * {} = {:2}{:>10} * {} = {:2}",
                      i,j,i*j, i+1,j,(i+1)*j, i+2,j,(i+2)*j);
        }
        println!();
        println!();
    }
}

결과는 아래와 같습니다. 9단까지 표시돼야 하는데, 3개씩 표시하다 보니 10단까지 표시됐습니다.

7. 구구단 명 표시하기(성공)

10단이면, 다시 말해 (i+2)가 9보다 크면 인쇄하지 않고, 9이하일 때만 인쇄해야 하므로 아래와 같이 if 문을 사용해야 합니다.

fn main() {
    for i in (2..=9).step_by(3) {
        if (i+2) <= 9 {
            println!("{:^10}{:8}{:^10}{:8}{:^10}",
                        i.to_string()+"단","",
                        (i+1).to_string()+"단","",
                        (i+2).to_string()+"단");

            for j in 1..=9 {
                println!("{} * {} = {:2}{:>10} * {} = {:2}{:>10} * {} = {:2}",
                          i,j,i*j, i+1,j,(i+1)*j, i+2,j,(i+2)*j);
            }
                        
        } else {
            println!("{:^10}{:8}{:^10}",
                        i.to_string()+"단","",
                        (i+1).to_string()+"단");

            for j in 1..=9 {
                println!("{} * {} = {:2}{:>10} * {} = {:2}",
                          i,j,i*j, i+1,j,(i+1)*j);
            }
                        
        }
                        
        println!();
        println!();
    }
}

9단까지만 잘 표시됐습니다.

함수, if와 match 표현식

함수는 코드의 재사용과 구조화를 위한 기본 단위로서 매개변수와 반환값이 있을 수 있습니다. 또한 if와 match는 중요한 제어 흐름 도구로서, let과 결합하여 변수에 값을 대입하는 표현식도 됩니다. match의 경우 모든 경우를 망라하기 위해 _를 사용하는 것이 특이합니다.


🔧 함수 정의

fn main() {
greet("Rust");
}

fn greet(name: &str) {
println!("Hello, {}!", name);
}
  • fn 키워드로 함수를 정의합니다.
  • 함수는 매개변수와 반환 타입을 명시할 수 있습니다. 그러나, 매개변수나 반환 값이 있다면 반드시 형식(타입)을 지정해야 합니다. 위에서 main 함수에는 매개변수가 없고, greet에는 매개변수 name이 있으므로 형식을 &str로 지정했습니다.
  • &str은 문자열 슬라이스(문자열 참조)입니다.
  • main함수에서 greet 함수를 호출하고, greet 함수의 name 매개변수로 Rust를 전달하고 있으므로, 위 코드를 실행하면 아래 화면과 같이 Hello, Rust!라고 화면에 표시됩니다.
Run을 실행한 결과 Hello, Rust!가 화면에 출력된 화면입니다.

위 화면은 D:\rust-practice 폴더에서 cargo new day3를 실행한 다음 위 코드로 대체하고 실행한 화면입니다.

name 다음의 형식을 제거하고 실행(Run) 하면 아래와 같이 복잡한 에러 메시지가 표시되는데, name에 대한 형식을 지정하라는 의미입니다.

name 다음에 형식 지정이 없어서 지정하라는 에러 화면입니다.

위 화면에서 name 다음에 :을 입력하면 &str이 제시되므로 tab키를 눌러 제안을 수용하면 쉽게 코드를 완성할 수 있습니다.


🔁 반환값이 있는 함수

fn add(a: i32, b: i32) -> i32 {
a + b // 세미콜론 없음 → 반환값
}
  • 함수의 마지막 표현식(Expression)이 반환값입니다. 여기서는 a + b 입니다.
  • -> 다음의 i32가 반환 값의 형식을 지정하는 것입니다.
  • 세미콜론(;)이 붙으면 실행문(Statement)으로 값이 반환되지 않습니다.
  • return키워드를 사용할 수도 있지만, 마지막 줄에 return 없이 값을 놓는 것이 일반적입니다.
fn add(a: i32, b: i32) -> i32{
    return a + b
}
  • 위 함수는 출력문이 없으므로 화면에 어떠한 값도 출력하지 않습니다.
    값을 출력하려면 println! 매크로를 사용해야 합니다.
fn main() {
    let sum = add(5, 10);
    println!("5와 10의 합은: {sum}"); // 15
}

fn add(a: i32, b: i32) -> i32{
    a + b
}

위 코드는 main함수에서 add 함수에 5와 10을 전달하고 a + b의 값을 반환받아 값 15를 sum 변수에 대입한 후 println!를 이용해 “5와 10의 합은: 15″라고 화면에 출력하는 것입니다.


🔸 if 표현식

Rust에서 if는 표현식이며, 값으로 사용할 수 있습니다. 다시 말해 let 예약어를 이용해 변수에 if 표현식으로 결정되는 값을 변수에 대입할 수 있습니다.

fn main() {
let score = 85;
let grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else {
"C"
};
println!("성적: {grade}");
}
  • if는 블록의 결과를 반환합니다.
  • 각 분기의 결과는 같은 타입이어야 합니다.
  • 위 코드를 실행하면 score가 90보다 작고, 80보다 크므로 “성적: B”가 화면에 출력됩니다.

🔶 match 표현식

match는 패턴 매칭을 제공하는 강력한 제어문입니다.

fn main() {
let number = 3;

match number {
1 => println!("하나"),
2 => println!("둘"),
3 => println!("셋"),
_ => println!("기타"),
}
}
  • _는 위에 해당하지 않는 모든 경우를 의미하는 와일드카드입니다. ‘아무거나(any value)’라고 이해하면 편합니다.
  • 각 분기(arm)에는 =>로 실행 코드를 지정합니다(The => operator that separates the pattern and the code to run).
  • println!를 사용했는데도 ;을 붙이지 않는 점을 주의해야 합니다.
  • 위 코드를 실행하면 “셋”이라고 화면에 표시됩니다.
  • match는 반드시 모든 경우를 처리해야 합니다.
    다시 말해 _가 없으면 “i32 형식에 해당하는 수 중 1,2,3만 처리해서 i32의 최소값부터 0까지와 4부터 i32의 최대값은 커버하지 못했다”고 하는 non-exaustive patterns(총망라 하지 않은 패턴) 에러가 표시됩니다.

또한 아래와 같이 _를 맨 위에 놓으면 ‘모든 경우’가 되므로, number의 값이 1이거나 2 또는 3이더라도 “기타”를 출력하게 됩니다. 1,2,3이 아닌 4인 경우 “기타”를 출력하는 것은 너무나 당연합니다.

fn main() {
    let number = 3;

    match number {
        _ => println!("기타"),
        1 => println!("하나"),
        2 => println!("둘"),
        3 => println!("셋"),        
    }
}

📌 match를 값으로 사용하기

fn main() {
let day = 3;
let weekday = match day {
1 => "월요일",
2 => "화요일",
3 => "수요일",
_ => "기타",
};
println!("요일: {}", weekday);
}
  • match는 if와 마찬가지로 표현식이므로 변수에 바로 match 표현식의 결과 값을 할당할 수 있습니다.
  • 이전 예에서는 => 다음에 println!를 사용했는데, 여기서는 “화요일” 등의 반환값을 지정한 점이 다릅니다.
  • 위 코드를 실행하면 “요일: 수요일”이 출력됩니다.


🧠 요약

  • 함수는 fn으로 정의하며, 매개변수와 반환 타입 지정 가능
  • if와 match는 모두 표현식으로, 값을 반환할 수 있음
  • match는 매우 강력한 패턴 매칭 도구이며, 모든 경우를 반드시 다뤄야 함