Rust에서 문자열의 첫 글자를 대문자로 바꾸는 이유는 무엇입니까?


82

의 첫 글자를 대문자로 쓰고 싶습니다 &str. 간단한 문제이고 간단한 해결책을 원합니다. 직감은 나에게 다음과 같은 것을하라고 말한다.

let mut s = "foobar";
s[0] = s[0].to_uppercase();

그러나 &strs는 이와 같이 인덱싱 할 수 없습니다. 내가 할 수 있었던 유일한 방법은 지나치게 복잡해 보입니다. 나는 변환 &str작성, 반복자,로 I 인덱스를 생성하는 벡터의 벡터, 대문자로 첫 번째 항목을 반복자를 변환, 반복자에 Option내가 나에게 대문자가 첫 번째 문자를주고 풀다 어느. 그런 다음 벡터를 반복자 String로 변환하여으로 변환하고 &str.

let s1 = "foobar";
let mut v: Vec<char> = s1.chars().collect();
v[0] = v[0].to_uppercase().nth(0).unwrap();
let s2: String = v.into_iter().collect();
let s3 = &s2;

이것보다 더 쉬운 방법이 있습니까? 그렇다면 무엇입니까? 그렇지 않다면 Rust가 이런 방식으로 디자인 된 이유는 무엇입니까?

비슷한 질문


46
이것은 단순한 문제 입니다. 아니, 아닙니다. ß독일어로 해석되는 경우 대문자로 표기하십시오 . 힌트 : 단일 문자가 아닙니다. 문제 진술조차도 복잡 할 수 있습니다. 예를 들어 성의 첫 문자를 대문자로 사용하는 것은 부적절합니다 von Hagen. 이것은 수천 년 동안 서로 다른 관행을 가진 서로 다른 문화를 가진 글로벌 세계에서 사는 것의 한 측면이며 우리는 모든 것을 8 비트와 2 줄의 코드로 뭉치려고 노력하고 있습니다.
Shepmaster

3
당신이 제기하는 것은 데이터 유형 문제가 아니라 문자 인코딩 문제로 보입니다. char :: to_uppercase가 이미 유니 코드를 제대로 처리한다고 가정합니다. 내 질문은 왜 모든 데이터 유형 변환이 필요한가요? 인덱싱은 다중 바이트, 유니 코드 문자 (단일 바이트 문자가 아닌 ascii 만 가정 함)를 반환 할 수 있으며 to_uppercase는 해당 언어에서 사용할 수있는 경우 어떤 언어로든 대문자를 반환 할 수 있습니다.
marshallm

3
@marshallm char::to_uppercase참으로이 문제를 처리하지만, 당신은 첫 번째 코드 포인트 (취함으로써 노력을 버리고 nth(0)대신 총액하는 모든 코드 포인트)

문자 인코딩은 소프트웨어 : 유니 코드에 대한 Joel이 지적한 것처럼 간단한 프로세스가 아닙니다 .
Nathan

@Shepmaster, 일반적으로 당신이 맞습니다. 이것은 영어 (프로그래밍 언어 및 데이터 형식의 사실상 표준 기반)의 간단한 문제입니다. 네, "대문자 화"가 개념도 아닌 스크립트와 매우 복잡한 스크립트가 있습니다.
폴 드레이퍼

답변:


101

왜 그렇게 복잡합니까?

한 줄씩 나눠 보자

let s1 = "foobar";

UTF-8로 인코딩 된 리터럴 문자열을 만들었습니다 . UTF-8을 사용하면 유니 코드 의 1,114,112 코드 포인트 를 매우 간결한 방식으로 인코딩 할 수 있습니다 . 1963 년에 생성 된 표준 인 ASCII 로 대부분의 문자를 입력하는 세계 지역에서 왔을 때입니다 . UTF-8은 가변 길이입니다. 이는 단일 코드 포인트가 1에서 4 바이트까지 걸릴 수 있음을 의미합니다 . 더 짧은 인코딩은 ASCII 용으로 예약되어 있지만 많은 Kanji는 UTF-8에서 3 바이트를 사용 합니다.

let mut v: Vec<char> = s1.chars().collect();

이것은 char행위자 의 벡터를 만듭니다 . 문자는 코드 포인트에 직접 매핑되는 32 비트 숫자입니다. ASCII 전용 텍스트로 시작했다면 메모리 요구 사항이 4 배가되었습니다. 만약 우리가 아스트랄 계 의 캐릭터들을 가지고 있다면, 우리는 그다지 많이 사용하지 않았을 것입니다.

v[0] = v[0].to_uppercase().nth(0).unwrap();

이것은 첫 번째 코드 포인트를 잡고 대문자 변형으로 변환하도록 요청합니다. 유감스럽게도 영어로 자란 우리에게는 "소문자"를 "큰 문자"에 대한 간단한 일대일 매핑이 항상있는 것은 아닙니다 . 참고 : 과거에는 한 상자의 글자가 다른 글자 상자보다 위에 있었기 때문에 대문자와 소문자라고 부릅니다 .

이 코드는 코드 포인트에 해당하는 대문자 변형이 없을 때 패닉 상태가됩니다. 실제로 존재하는지 확실하지 않습니다. 또한 코드 포인트에 German과 같이 여러 문자가있는 대문자 변형이있는 경우 의미 상 실패 할 수 있습니다 ß. ß는 The Real World에서 실제로 대문자로 표기되지 않을 수 있습니다. 이것은 제가 항상 기억하고 검색 할 수있는 유일한 예입니다. 2017년 6월 29일로, 사실, 독일어 맞춤법의 공식 규칙이 이렇게 업데이트되었는지 모두 "ẞ"과 "SS"는 유효한 총액은 !

let s2: String = v.into_iter().collect();

여기서는 문자를 다시 UTF-8로 변환하고 런타임에 메모리를 차지하지 않도록 원래 변수가 상수 메모리에 저장되었으므로이를 저장할 새 할당이 필요합니다.

let s3 = &s2;

그리고 이제 우리는 그것을 참조합니다 String.

간단한 문제

불행히도 이것은 사실이 아닙니다. 아마도 우리는 세상을 에스페란토 로 바꾸려고 노력해야 할까요?

나는 char::to_uppercase이미 유니 코드를 제대로 처리한다고 생각한다.

네, 확실히 그러길 바랍니다. 불행히도 모든 경우에 유니 코드만으로는 충분하지 않습니다. 대문자 ( İ )와 소문자 ( i ) 버전 모두에 점이 있는 터키어 I 를 지적 해 주신 huon에게 감사드립니다 . 즉 더 없다,있다 편지의 적절한 총액 ; 소스 텍스트 의 로케일 에 따라 다릅니다 .i

모든 데이터 유형 변환이 필요한 이유는 무엇입니까?

작업중인 데이터 유형은 정확성과 성능이 걱정 될 때 중요하기 때문입니다. A char는 32 비트이고 문자열은 UTF-8로 인코딩됩니다. 그들은 다른 것들입니다.

인덱싱은 멀티 바이트 유니 코드 문자를 반환 할 수 있습니다.

여기에 일치하지 않는 용어가있을 수 있습니다. A char 멀티 바이트 유니 코드 문자입니다.

바이트 단위로 이동하면 문자열을 슬라이싱 할 수 있지만 문자 경계에 있지 않으면 표준 라이브러리가 패닉 상태가됩니다.

문자를 얻기 위해 문자열 인덱싱이 구현되지 않은 이유 중 하나는 많은 사람들이 문자열을 ASCII 문자 배열로 오용하기 때문입니다. 문자 를 설정 하기 위해 문자열을 인덱싱하는 것은 결코 효율적일 수 없습니다. 1-4 바이트를 1-4 바이트 값으로 대체 할 수 있어야 나머지 문자열이 상당히 많이 바운스됩니다.

to_uppercase 대문자를 반환 할 수 있습니다.

위에서 언급 ß했듯이은 대문자로 입력하면 두 문자 가되는 단일 문자입니다 .

솔루션

ASCII 문자 만 대문자로하는 trentcl의 답변 도 참조하십시오 .

실물

코드를 작성해야한다면 다음과 같이 보일 것입니다.

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().chain(c).collect(),
    }
}

fn main() {
    println!("{}", some_kind_of_uppercase_first_letter("joe"));
    println!("{}", some_kind_of_uppercase_first_letter("jill"));
    println!("{}", some_kind_of_uppercase_first_letter("von Hagen"));
    println!("{}", some_kind_of_uppercase_first_letter("ß"));
}

그러나 나는 아마도 crates.io 에서 대문자 또는 유니 코드 를 검색하고 나보다 똑똑한 사람이 처리하도록 할 것입니다.

향상

"나보다 똑똑한 사람"에 대해 Veedrac 은 첫 번째 자본 코드 포인트에 액세스 한 후 반복기를 다시 슬라이스로 변환하는 것이 아마도 더 효율적 이라고 지적합니다 . 이것은 memcpy나머지 바이트를 허용합니다.

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}

34
많은 생각을 한 후 이러한 디자인 선택을 더 잘 이해합니다. 표준 라이브러리는 가능한 가장 다재다능하고 성능이 뛰어나며 안전한 절충안을 선택해야합니다. 그렇지 않으면 개발자가 애플리케이션, 아키텍처 또는 로케일에 적합하지 않을 수있는 절충안을 만들도록합니다. 또는 모호성과 오해로 이어질 수 있습니다. 다른 절충안을 선호하는 경우 타사 라이브러리를 선택하거나 직접 작성할 수 있습니다.
marshallm

13
@marshallm 정말 듣기 좋습니다! 저는 Rust를 처음 접하는 많은 사람들이 Rust 디자이너가 내린 결정을 오해하고 단순히 너무 복잡해서 아무런 이득이 없다고 씁니다. 여기에서 질문하고 답함으로써 그러한 디자인에 들어가고 더 나은 프로그래머가되기를 바라는 배려에 대한 감사를 얻었습니다. 열린 마음을 유지하고 더 많은 것을 배우고 자하는 것은 프로그래머로서의 큰 특징입니다.
Shepmaster jul.

6
"터키어 나는" 정렬 이상이 특정 질문에 더 직접적으로 관련이 로케일 의존의 예입니다.
huon

6
to_uppercase와 to_lowercase는 있지만 to_titlecase는 없습니다. IIRC, 일부 유니 코드 문자에는 실제로 특수한 제목 케이스 변형이 있습니다.
Tim

6
그건 그렇고, 단일 코드 포인트조차도 변환하기에 적합한 단위가 아닐 수 있습니다. 첫 번째 문자가 대문자 일 때 특수 처리를 받아야하는 자소 클러스터이면 어떻게됩니까? (너무 분해 움라우트 그냥 대문자 기본 문자 당신이 경우 그 보편적으로 해당하는 경우,하지만 나도 몰라 작동하는지 발생합니다.)
세바스찬 REDL

23

이것보다 더 쉬운 방법이 있습니까? 그렇다면 무엇입니까? 그렇지 않다면 Rust가 이런 방식으로 디자인 된 이유는 무엇입니까?

글쎄, 예 그리고 아니오. 귀하의 코드는 다른 답변에서 지적했듯이 정확하지 않으며 བོད་ སྐད་ ལ་와 같은 것을 제공하면 당황합니다. 따라서 Rust의 표준 라이브러리로 이것을하는 것은 처음에 생각했던 것보다 훨씬 더 어렵습니다.

그러나 Rust는 코드 재사용을 장려하고 라이브러리를 쉽게 가져올 수 있도록 설계되었습니다. 따라서 문자열을 대문자로 표기하는 관용적 방법은 실제로 매우 맛있습니다.

extern crate inflector;
use inflector::Inflector;

let capitalized = "some string".to_title_case();

4
사용자의 질문은 그가 원하는 것처럼 들립니다 .to_sentence_case().
Christopher Oezbek 19-06-15

1
슬프게도 이름을 지정하는 데 도움이되지 않습니다 ... 이것은 멋진 라이브러리이고 전에는 본 적이 없지만 이름은 기억하기 어렵고 실제 굴절과 거의 관련이없는 기능이 있습니다. 당신의 모범이 되십시오.
사사 해

11

입력을 ASCII 전용 문자열로 제한 할 수 있다면 특별히 복잡하지 않습니다.

녹 1.23 이후, strmake_ascii_uppercase방법을 (오래된 녹 버전에서, 그것은을 통해 사용할 수있었습니다 AsciiExt특성). 이것은 상대적으로 쉽게 ASCII 전용 문자열 조각을 대문자로 바꿀 수 있음을 의미합니다.

fn make_ascii_titlecase(s: &mut str) {
    if let Some(r) = s.get_mut(0..1) {
        r.make_ascii_uppercase();
    }
}

이 켜집니다 "taylor""Taylor"있지만 켜지지 않습니다 "édouard""Édouard". ( 놀이터 )

주의해서 사용하십시오.


2
Rust 초보자를 도와주세요. 왜 r변경 가능한가요? 나는 그것이 s가변적 이라는 것을 알았다 str. Ohhhh ok : 내 질문에 대한 답이 있습니다 : get_mut(여기에 범위가 있음) 명시 적으로 returns Option<&mut>.
Steven Lu

0

이것이 제가이 문제를 해결 한 방법입니다. 대문자로 변환하기 전에 self가 ASCII가 아닌지 확인해야했습니다.

trait TitleCase {
    fn title(&self) -> String;
}

impl TitleCase for &str {
    fn title(&self) -> String {
        if !self.is_ascii() || self.is_empty() {
            return String::from(*self);
        }
        let (head, tail) = self.split_at(1);
        head.to_uppercase() + tail
    }
}

pub fn main() {
    println!("{}", "bruno".title());
    println!("{}", "b".title());
    println!("{}", "🦀".title());
    println!("{}", "ß".title());
    println!("{}", "".title());
    println!("{}", "བོད་སྐད་ལ".title());
}

산출

Bruno
B
🦀
ß

བོད་སྐད་ལ 

-1

@Shepmaster의 개선 된 버전보다 약간 느리지 만 더 관용적 인 버전이 있습니다 .

fn capitalize_first(s: &str) -> String {
    let mut chars = s.chars();
    chars
        .next()
        .map(|first_letter| first_letter.to_uppercase())
        .into_iter()
        .flatten()
        .chain(chars)
        .collect()
}

-1

나는 이렇게했다 :

fn str_cap(s: &str) -> String {
  format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..])
}

ASCII 문자열이 아닌 경우 :

fn str_cap(s: &str) -> String {
  format!("{}{}", s.chars().next().unwrap().to_uppercase(), 
  s.chars().skip(1).collect::<String>())
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.