백준 1002(ft. rust)

Hyeseong·2023년 5월 10일
0

rust bakjun algorithms

목록 보기
2/2

문제

두 정수 A와 B를 입력받은 다음, A×B를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 A와 B가 주어진다. (0 < A, B < 10)

출력

첫째 줄에 A×B를 출력한다.

예제 입력1

1 2

예제 출력1

2

예제 입력2

3 4

예제 출력2

12

Case-1

use std::io;

fn main() {
    println!("Please enter two numbers: ");
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();

    let numbers: Vec<i32> = input
        .split_whitespace()
        .map(|s| s.parse().unwrap())
        .collect();

    let a:i32 = numbers[0];
    let b:i32 = numbers[1];

    println!("{}", a * b);
}

Case-2

use std::io;

fn main() {
    println!("Please enter two numbers: ");
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();

    let mut numbers = input.split_whitespace();
    let a: i32 = numbers.next().unwrap().parse().unwrap();
    let b: i32 = numbers.next().unwrap().parse().unwrap();

    println!("{}", a * b);
}

case-1 코드와 유사하지만, 입력을 처리하는 방식에 약간의 차이가 있습니다. 이 버전에서는 split_whitespace()분리한 숫자들을 numbers 이터레이터저장한 뒤, next() 메서드를 사용하여 각 숫자를 차례대로 가져와a와 b에 할당합니다. 이후 곱셈 결과를 출력합니다.

설명

  1. next() 메서드 :
  • 컬렉션의 다음 요소를 차례대로 가져옴
  • Option<T> 타입을 반환
  • T는 이터레이터가 생성하는 항목의 타입
  • 이터레이터에 더 이상 요소가 없을 경우, next() 메서드는 None을 반환
  • 그렇지 않으면, Some(T)를 반환합니다. 이를 통해 이터레이터가 더 이상 요소를 가지고 있지 않음을 알 수 있습니다.
  • 결국, Option<T> 타입을 반환 -> None or Some(T) 일수 있음.
  1. unwrap메서드 :
    2.1. Option 타입:

    • Some(T): 내부에 값 T를 포함합니다.
    • None: 값이 없음을 나타냅니다.

    2.2. Result 타입:

    • Ok(T): 성공적인 결과를 포함하는 값 T를 가집니다.
    • Err(E): 에러를 나타내는 값 E를 포함합니다.

    2.3. unwrap() 메서드:

    • Option 또는 Result 타입에서 사용할 수 있습니다.
    • Option의 경우:
      - Some(T)일 때: 내부의 값 T를 반환합니다.
      - None일 때: 패닉을 일으키고 프로그램이 종료됩니다.
    • Result의 경우:
      - Ok(T)일 때: 내부의 값 T를 반환합니다.
      - Err(E)일 때: 패닉을 일으키고 프로그램이 종료됩니다.

    2.4. 안전한 에러 처리:

    • unwrap() 메서드보다는 match 문이나 if let 구문을 사용하여 에러 처리를 명시적으로 하는 것이 좋습니다.
    • 이를 통해 프로그램의 안정성을 높일 수 있습니다.

Case - 3

use std::io;

fn main() {
    println!("Please enter two numbers: ");
    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();

    let numbers: Vec<i32> = input
        .split_whitespace()
        .map(|s| s.parse().unwrap())
        .collect();

    let (a, b) = (numbers[0], numbers[1]);

    println!("{}", a * b);
}

설명

  1. destructuring or pattern matching

    • tuple 내의 요소를 각각 변수 a와 b에 할당
  2. destructuring을 쓸수 있는 데이터 타입.
    2.1. 튜플: 여러 개의 다른 타입의 값을 묶어 하나로 만든 데이터 구조
    2.2. 배열: 동일한 타입의 여러 개의 값을 묶어 하나로 만든 데이터 구조
    2.3. Structs: 용자 정의 데이터 타입으로, 여러 개의 다른 타입의 값을 묶어 하나로 만든 데이터 구조
    2.4. Enums: 여러 개의 다른 타입의 값을 묶어 하나로 만든 데이터 구조로, 각각의 값은 Enum의 다른 variant에 해당합니다.

  3. destructuring 예시 코드
    3.1. 튜플 destructuring

fn main() {
    let tuple = (1, "hello", 3.14);
    let (a, b, c) = tuple;

    println!("{}, {}, {}", a, b, c); // 출력: 1, hello, 3.14
}

3.2. 배열 destructuring

fn main() {
    let array = [1, 2, 3];
    let [a, b, c] = array;

    println!("{}, {}, {}", a, b, c); // 출력: 1, 2, 3
}

3.3. Struct destructuring

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 3, y: 4 };
    let Point { x, y } = point;

    println!("{}, {}", x, y); // 출력: 3, 4
}

3.4. Enum destructuring

enum Animal {
    Dog { name: String, age: i32 },
    Cat { name: String, age: i32 },
}

fn main() {
    let animal = Animal::Dog {
        name: String::from("Buddy"),
        age: 3,
    };

    match animal {
        Animal::Dog { name, age } => println!("Dog named {} is {} years old", name, age),
        Animal::Cat { name, age } => println!("Cat named {} is {} years old", name, age),
    }
}
  1. enum, 열거형?
    열거형(enum)은 Rust에서 다양한 값을 묶어서 하나의 타입으로 표현할 수 있는 사용자 정의 데이터 타입입니다. 열거형은 여러 개의 variant로 구성되며, 각 variant는 열거형의 이름을 사용하여 접근할 수 있습니다. 각 variant는 다른 타입과 값을 가질 수 있으며, 그 값들은 괄호 안에 표시됩니다.

4.1. 구조

enum EnumName {
    Variant1(Type1),
    Variant2(Type2),
    Variant3(Type3),
    // ...
}
  • 예. 여러 종류의 도형을 표현하는 열거형
enum Shape {
    Circle(f64),          // 원
    Rectangle(f64, f64),  // 사각형
    Square(f64),          // 정사각형
}
  • 이 열거형에서 Shape는 열거형의 이름이며, Circle, Rectangle, Square는 각각의 variant입니다. 각 variant는 다른 타입의 값을 가집니다. 원은 반지름을 나타내는 f64 타입의 값을 가지며, 사각형은 가로와 세로 길이를 나타내는 두 개의 f64 타입 값을 가집니다. 정사각형은 한 변의 길이를 나타내는 f64 타입 값을 가집니다.

  • 예. 아래는 Shape 열거형의 Rectangle variant를 사용하는 코드 예시입니다. Shape 열거형을 선언하고, 함수를 사용하여 사각형의 면적을 계산하는 예제입니다.

enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
    Square(f64),
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
        Shape::Rectangle(width, height) => width * height,
        Shape::Square(side) => side * side,
    }
}

fn main() {
    let rectangle = Shape::Rectangle(4.0, 6.0);
    let rectangle_area = area(&rectangle);
    println!("The area of the rectangle is: {:.2}", rectangle_area);
}
  • 이 예제에서 Shape 열거형을 선언한 후, area 함수를 사용하여 도형의 면적을 계산합니다. main 함수에서 Shape::Rectangle variant를 사용하여 사각형을 생성하고, 그 면적을 계산한 뒤 출력합니다.

Case - 4

use std::io;
use std::str::FromStr;

fn main() {
    println!("Please enter two numbers: ");
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read line");

    let mut numbers = input.split_whitespace().map(|s| {
        i32::from_str(s).unwrap_or_else(|_| {
            eprintln!("Error: Invalid input");
            std::process::exit(1);
        })
    });

    let a = numbers.next().expect("Missing number");
    let b = numbers.next().expect("Missing number");

    println!("{}", a * b);
}

설명

  1. 이 코드는 parse() 대신 from_str()을 사용하여 에러 메시지를 표시하고 프로그램을 종료하는 처리를 추가했습니다. 또한, 입력 문자열을 공백으로 분리하고 각각의 숫자를 추출하는 과정을 조금 더 명확하게 표현하였습니다.

Case-5

fn main() {
    let (a, b): (i32, i32);
    std::io::stdin().read_line(&mut String::new()).map(|s| s.trim().parse().unwrap()).collect();
    println!("{}", a * b);
}

설명

  1. 이 코드는 공백을 기준으로 두 정수를 입력받고, 곱셈 결과를 출력합니다. 하지만 이렇게 코드를 짧게 작성하는 것이 항상 좋은 것은 아니며, 이해하기 어렵고 유지보수하기 힘들 수 있습니다. 가독성과 에러 처리를 고려한 코드를 작성하는 것이 더 바람직합니다.

Case-6

use std::io;

fn main() {
    println!("Please enter 2 numbers separated by a space:");

    let input = read_input();
    let parse_input(&input);

    if let Some((a, b)) = numbers {
        println!("the product of the 2 numbers is: {}", a * b);
    } else {
        eprintln!("Error: Invalid input");
    }

}

fn read_input()-> String {
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("Failed to read line from stdin");
    input
}

fn parse_input(input: &str) {
    let mut numbers = input.split_whitespace().map(|s| i32::from_str(s));

    match (numbers.next(), numbers.next()) {
        (Some(Ok(a)), Some(Ok(b))) => Some((a, b)),
        _ => None,
    }
}

설명

  1. let input = read_input();: read_input() 함수를 호출하여 사용자로부터 입력을 받아 변수 input에 저장합니다.

  2. let parse_input(&input);: parse_input() 함수를 호출하고 input 변수를 인자로 전달합니다. parse_input() 함수는 숫자 두 개를 반환합니다.

  3. if let Some((a, b)) = numbers { ... }: parse_input() 함수에서 반환한 숫자들이 유효한지 확인하고, 유효한 경우 두 숫자를 곱한 결과를 출력합니다.

  4. else { eprintln!("Error: Invalid input"); }: parse_input() 함수에서 반환한 숫자들이 유효하지 않은 경우, 에러 메시지를 출력합니다.

  5. fn read_input()-> String { ... }: 사용자로부터 입력을 받기 위한 함수입니다. io::stdin().read_line() 메소드를 사용하여 콘솔에서 입력을 받습니다.

  6. fn parse_input(input: &str) { ... }: 사용자 입력 문자열을 받아 숫자를 추출하고, 추출한 숫자를 반환하는 함수입니다.
    input.split_whitespace().map(...) 메소드를 사용하여 입력 문자열에서 공백으로 구분된 숫자들을 추출합니다. 추출한 숫자를 정수로 변환한 후, (a, b) 형태의 튜플로 반환합니다.

  7. parse_input:

    7.1. 매개변수 :

    • input: &str :
      매개변수 input의 원형 타입은 String입니다. 하지만 &String과 같은 문자열 레퍼런스는 문자열슬라이스(&str)과 호환됨. 따라서 &String&str로 변환할 때는 별도의 변환 과정이 필요하지 않습니다. 이를 더 일반화해서 말하면, Rust에서는 "Deref coercion"이라는 기능을 제공하는데, 이를 통해 컴파일러가 암시적으로 타입을 변환해줍니다.

      • Deref coercion을 한글로는 "역참조 강제 변환" 또는 "간접 참조 강제 변환"으로 번역할 수 있습니다. "Deref"는 "역참조"를 의미하는데, 이는 포인터나 레퍼런스 타입의 값을 직접 참조할 때 사용하는 연산자인 *와 유사한 개념입니다. 따라서 "Deref coercion"는 암시적으로 역참조 연산을 수행하여 타입을 변환하는 것을 의미합니다.
    • input: &String도 가능

      • Q. parse_input 메서드의 매개변수 타입으로 input: &String 도 가능하다. 그렇다면 &str, &String 어느것을 쓰는것이 더 효율적?

      • A. 실제 메모리 상에서는 &str이 더 효율적입니다. 이는 &String을 사용하면 추가적인 메모리 할당과 복사가 발생하기 때문입니다. 따라서 가능하다면, 함수의 매개변수나 구조체의 필드 등에서 문자열을 참조하는 경우에는 &str을 사용하는 것이 좋습니다.

  • 7.2. 반환 타입 :
    • Option<(i32, i32)>
  1. (numbers.next(), numbers.next()) 튜플
  2. _ 와일드카드 패턴 :
    • 모든 값과 매치된다는 것
    • 패턴 매칭에서 해당 패턴과 일치하는 값이 없는 경우를 처리
    • 보통 match 표현식의 마지막 분기에 사용되며, 일치하는 패턴이 없는 경우 디폴트 값을 지정할 때 유용
  3. map메서드 클로저
  • 클로저는 특정한 코드 블록을 의미`
  • 특별한 문법을 사용하여 표현
    • |s| i32::from_str(s) 에서 |s| 는 클로저의 인자를 정의하고, i32::from_str(s) 는 클로저의 반환값을 정의합니다.
  • 클로저는 함수처럼 값을 반환할 수 있고, 이 반환값은 map 함수가 호출된 컨텍스트에서 사용될 수 있음.
  • 따라서 map(|s| i32::from_str(s)) 는 각각의 문자열에 대해 i32::from_str 함수를 호출하여 결과 값을 반환하는 클로저를 생성하고, 이 클로저를 각각의 문자열에 적용하여 새로운 Iterator 를 생성합니다.

힙 할당 타입

  • 알아야 하는 이유: 자동으로 형 변환이 가능하기 때문

    • Rust에서 Deref coercion은 Deref 트레이트를 구현하는 타입에서 사용됩니다.

    • Deref 트레이트는 * 연산자의 동작을 오버로드하는데 사용됩니다. 이를 통해 해당 타입에 대한 참조가 아닌 해당 타입 자체에 대한 작업을 수행할 수 있습니다.

    • Deref coercionDeref 트레이트를 구현하는 모든 타입에서 사용할 수 있습니다. 일반적으로 Box, Vec, String, Rc, Arc와 같은 힙 할당 타입에서 많이 사용됩니다. 또한, 참조자(&) 타입에 대해서도 사용할 수 있습니다.

Box

let boxed_num: Box<i32> = Box::new(42);
let num_ref: &i32 = &boxed_num;
assert_eq!(*num_ref, 42);

Vec

let vec_of_strings = vec!["hello", "world"];
let slice_of_strings: &[&str] = &vec_of_strings;
assert_eq!(slice_of_strings[0], "hello");

String

let s = String::from("hello");
let s_ref: &str = &s;
assert_eq!(s_ref, "hello");

Rc

use std::rc::Rc;
let rc_num: Rc<i32> = Rc::new(42);
let num_ref: &i32 = &rc_num;
assert_eq!(*num_ref, 42);

Arc

use std::sync::Arc;
let arc_num: Arc<i32> = Arc::new(42);
let num_ref: &i32 = &arc_num;
assert_eq!(*num_ref, 42);

heap 할당 타입 & 참조자(&) 타입

  1. 힙 할당 타입은 메모리 할당이 동적으로 이루어지는 타입을 의미합니다. 러스트에서는 힙에 메모리를 할당하는 타입으로 Box, Vec, String, Rc, Arc 등이 있습니다. 이러한 타입은 값이 스택이 아닌 힙에 저장되기 때문에 포인터로 스택에 참조됩니다.

  2. 참조자(&) 타입다른 값을 참조하는 데 사용되는 타입으로, 해당 값을 소유하지 않습니다. 대신 참조된 값의 메모리 위치를 가리키는 포인터 역할을 합니다. 이러한 타입은 불변 참조자(&T)가변 참조자(&mut T)가 있습니다. 불변 참조자는 값을 읽을 수만 있으며 변경할 수 없고, 가변 참조자는 값을 읽고 변경할 수 있습니다. 참조자는 스택에 저장되며, 스택에서 값을 참조하기 때문에 값이 소유한 메모리 공간의 크기에 영향을 받지 않습니다.

2.1. Q. 참조자 타입을 사용 할 수 없는 타입은 뭐야? 혹은 사용 할 수 없는 경우가 있어?

2.1. A. 참조자(&)는 소유권을 빌릴 수 있는 타입에서만 사용할 수 있습니다.
2.1.1. 소유권을 가지지 않는 타입에서는 참조자를 사용할 수 없습니다.

  • 기본 타입인 숫자 타입들(i32, u8, f64 등), 불(bool) 타입, 복합 타입 중 하나 이상의 필드가 소유권을 가지는 타입(Box, Vec, String` 등)이 포함됩니다.
profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글