Решение на Text Info от Георги Ангелов

Обратно към всички решения

Към профила на Георги Ангелов

Резултати

  • 15 точки от тестове
  • 0 бонус точки
  • 15 точки общо
  • 15 успешни тест(а)
  • 0 неуспешни тест(а)

Код

use std::cmp;
use std::str;
pub struct TextInfo<'a> {
string: &'a str
}
impl <'a> TextInfo<'a> {
pub fn new(string: &'a str) -> Self {
TextInfo { string: string }
}
pub fn char_count(&self) -> usize {
self.string.chars().count()
}
pub fn alphabetic_count(&self) -> usize {
self.string.matches(char::is_alphabetic).count()
}
pub fn latin_letter_count(&self) -> usize {
self.string.matches( |c| c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ).count()
}
pub fn cyrillic_letter_count(&self) -> usize {
self.string.matches( |c| c >= 'А' && c <= 'Я' || c >= 'а' && c <= 'я' ).count()
}
pub fn word_count(&self) -> usize {
self.string
.split( |c: char| !c.is_alphabetic() )
.filter( |s| !s.is_empty() )
.count()
}
pub fn sentence_count(&self) -> usize {
self.sentence_endings().count()
}
pub fn emotion(&self) -> String {
let mut exclamations = 0;
let mut questions = 0;
let mut full_stops = 0;
for c in self.sentence_endings() {
match c {
'!' => exclamations += 1,
'?' => questions += 1,
'.' => full_stops += 1,
_ => panic!(format!("Sentence ending was {}", c))
}
}
let emotion =
if exclamations > cmp::max(questions, full_stops) {
"😮"
} else if questions > cmp::max(exclamations, full_stops) {
"🤔"
} else {
"😐"
};
String::from(emotion)
}
fn sentence_endings(&self) -> SentenceIterator {
SentenceIterator::new(&self.string)
}
}
struct SentenceIterator<'a> {
chars: str::Chars<'a>,
in_sentence: bool
}
impl<'a> SentenceIterator<'a> {
fn new(string: &'a str) -> Self {
Self { chars: string.chars(), in_sentence: false }
}
}
impl<'a> Iterator for SentenceIterator<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
for c in &mut self.chars {
match c {
'?' | '!' | '.' => if self.in_sentence {
self.in_sentence = false;
return Some(c);
},
_ => if !c.is_whitespace() {
self.in_sentence = true;
}
}
}
None
}
}

Лог от изпълнението

Compiling solution v0.1.0 (file:///tmp/d20171026-5817-ngi37q/solution)
invalid expression
!2773 = !DIExpression(6, 34, 0, 6)
invalid expression
!2774 = !DIExpression(6, 34, 0, 6)
    Finished dev [unoptimized + debuginfo] target(s) in 4.8 secs
     Running target/debug/deps/solution-f5dd4e94aa395cae

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/solution_test-c3b431457e2a7a27

running 15 tests
test solution_test::test_alpha_count ... ok
test solution_test::test_alpha_count_2 ... ok
test solution_test::test_char_count ... ok
test solution_test::test_cyrillic_letter_count ... ok
test solution_test::test_emotions ... ok
test solution_test::test_emotions_repeated_punctuation ... ok
test solution_test::test_empty_string ... ok
test solution_test::test_latin_letter_count ... ok
test solution_test::test_sentence_count ... ok
test solution_test::test_sentence_count_2 ... ok
test solution_test::test_triple_dots_count ... ok
test solution_test::test_unicode_char_count ... ok
test solution_test::test_word_count ... ok
test solution_test::test_word_count_2 ... ok
test solution_test::test_word_count_3 ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests solution

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

История (4 версии и 10 коментара)

Георги качи първо решение на 18.10.2017 23:29 (преди почти 8 години)

Задачата ти е 15/15, добра работа :). Не съм учуден, разбира се, ти си от ветераните.

Ако искаш да пробваш интересна вариация на задачата -- опитай да имплементираш sentence_endings без да алокираш динамична памет във вектор. Би могъл да си направиш custom итератор за целта. Моят опит замества целия метод с това:

fn sentence_endings(&self) -> SentenceIterator {
    SentenceIterator { chars: self.string.chars(), in_sentence: false }
}

Имплементацията на SentenceIterator е това, с което можеш да си поиграеш. Разбира се, може и по различен начин да пробваш, просто давам идея в случай, че ти е скучно :). Имай предвид, че ще трябва мъничко да поръсиш тук и там lifetimes, за да може Chars тип да се запази като поле в SentenceIterator. Ако не ти се рови в момента (или не искаш да се spoil-ваш :)), може да почакаш да стигнем дотам в лекциите.

Мерси, ще си поиграя с итераторите :)

В интерес на истината, като писах sentence_endings много ми се искаше да yield-на нещо, но доколкото разбирам няма coroutine-и в rust. Колко идиоматично е в Rust да се прави итератор vs да се връща вектор? Бих предположил, че първото се среща по-често, но пък е повече усилие за имплементиране, поне на пръв поглед.

Георги качи решение на 19.10.2017 22:15 (преди почти 8 години)

use std::cmp;
pub struct TextInfo {
string: String
}
impl TextInfo {
pub fn new(string: &str) -> Self {
TextInfo { string: String::from(string) }
}
pub fn char_count(&self) -> usize {
self.string.chars().count()
}
pub fn alphabetic_count(&self) -> usize {
- self.count_chars( |c| c.is_alphabetic() )
+ self.string.matches(char::is_alphabetic).count()
}
pub fn latin_letter_count(&self) -> usize {
- self.count_chars( |c| c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' )
+ self.string.matches( |c| c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ).count()
}
pub fn cyrillic_letter_count(&self) -> usize {
- self.count_chars( |c| c >= 'А' && c <= 'Я' || c >= 'а' && c <= 'я' )
- }
-
- fn count_chars(&self, filter: fn(char) -> bool) -> usize {
- self.string.chars()
- .filter( |c| filter(*c) )
- .count()
+ self.string.matches( |c| c >= 'А' && c <= 'Я' || c >= 'а' && c <= 'я' ).count()
}
pub fn word_count(&self) -> usize {
self.string
.split( |c: char| !c.is_alphabetic() ).into_iter()

В този случай не би трябвало да е нужен into_iter, защото split връща итератор. Предполагам си забравил да го махнеш, но интересно е поведението - ако извикаш into_iter на итератор е noop. Пробвах с няколкo различни итераторa и нямаше разлика. Не съм видял да е част от спецификацията, нито да е имплементиран за всеки итератор. Сега питах в Discord канала на Rust, ще пиша като разбера защо се случва това.

Ок, оказа се че има имплементация за удобство impl<I: Iterator> IntoIterator for I { type Item = I::Item; type IntoIter = I; fn into_iter(self) -> I { self } } Ако метод приема тип който имплементира IntoIterator може да се подават примерно Vec(collection) или чист итератор. Основен пример е https://doc.rust-lang.org/stable/std/iter/index.html#for-loops-and-intoiterator.

.filter( |s| !s.is_empty() )
.count()
}
pub fn sentence_count(&self) -> usize {
self.sentence_endings().len()
}
pub fn emotion(&self) -> String {
let mut exclamations = 0;
let mut questions = 0;
let mut full_stops = 0;
for c in self.sentence_endings().into_iter() {
match c {
'!' => exclamations += 1,
'?' => questions += 1,
'.' => full_stops += 1,
_ => panic!(format!("Sentence ending was {}", c))
}
}
let emotion =
if exclamations > cmp::max(questions, full_stops) {
"😮"
} else if questions > cmp::max(exclamations, full_stops) {
"🤔"
} else {
"😐"
};
String::from(emotion)
}
fn sentence_endings(&self) -> Vec<char> {
let mut in_sentence = false;
let mut endings = Vec::new();
for c in self.string.chars() {
match c {
'?' | '!' | '.' => if in_sentence {
endings.push(c);
in_sentence = false;
},
_ => if !c.is_whitespace() {
in_sentence = true;
}
}
}
endings
}
}

Итератори се правят доста, може да видиш, че доста от &str методите връщат различни типове: Chars, Lines, SplitWhitespace. Всичките са различни типове итератори, които не алокират памет на heap-а, а просто размятат char-ове или slice-ове.

Като цяло, идиоматичното за Rust (като low-level език писан от C/C++ програмисти) е да се избягва копиране на неща, освен ако не е 100% необходимо. Дори и за String типа, заръчахме да се ползва за структури, за да си държат ownership. Но е напълно възможно, и предпочитано, структурите да си държат slices. В случая особено -- един TextInfo обект няма смисъл да живее без оригиналния низ, от който е създаден. Така че, ако го вържем с него използвайки експлицитни lifetimes, по-удачно би било да запазим &str в TextInfo -- просто reference към някаква част от низ, която да можем да анализираме.

Но наистина е повече усилие, да :). За сметка на това, веднъж като се направи някакъв смислен итератор, върху него могат да се chain-нат доста филтри и подобни неща, и може да от него да се изградят други структури с различна форма.

Георги качи решение на 20.10.2017 21:57 (преди почти 8 години)

use std::cmp;
+use std::str;
-pub struct TextInfo {
- string: String
+pub struct TextInfo<'a> {
+ string: &'a str
}
-impl TextInfo {
- pub fn new(string: &str) -> Self {
- TextInfo { string: String::from(string) }
+impl <'a> TextInfo<'a> {
+ pub fn new(string: &'a str) -> Self {
+ TextInfo { string: string }
}
pub fn char_count(&self) -> usize {
self.string.chars().count()
}
pub fn alphabetic_count(&self) -> usize {
self.string.matches(char::is_alphabetic).count()
}
pub fn latin_letter_count(&self) -> usize {
self.string.matches( |c| c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ).count()
}
pub fn cyrillic_letter_count(&self) -> usize {
self.string.matches( |c| c >= 'А' && c <= 'Я' || c >= 'а' && c <= 'я' ).count()
}
pub fn word_count(&self) -> usize {
self.string
.split( |c: char| !c.is_alphabetic() ).into_iter()
.filter( |s| !s.is_empty() )
.count()
}
pub fn sentence_count(&self) -> usize {
- self.sentence_endings().len()
+ self.sentence_endings().count()
}
pub fn emotion(&self) -> String {
let mut exclamations = 0;
let mut questions = 0;
let mut full_stops = 0;
- for c in self.sentence_endings().into_iter() {
+ for c in self.sentence_endings() {
match c {
'!' => exclamations += 1,
'?' => questions += 1,
'.' => full_stops += 1,
_ => panic!(format!("Sentence ending was {}", c))
}
}
let emotion =
if exclamations > cmp::max(questions, full_stops) {
"😮"
} else if questions > cmp::max(exclamations, full_stops) {
"🤔"
} else {
"😐"
};
String::from(emotion)
}
- fn sentence_endings(&self) -> Vec<char> {
- let mut in_sentence = false;
- let mut endings = Vec::new();
+ fn sentence_endings(&self) -> SentenceIterator {
+ SentenceIterator::new(&self.string)
+ }
+}
- for c in self.string.chars() {
+struct SentenceIterator<'a> {
+ chars: str::Chars<'a>,
+ in_sentence: bool
+}
+
+impl<'a> SentenceIterator<'a> {
+ fn new(string: &'a str) -> Self {
+ Self { chars: string.chars(), in_sentence: false }
+ }
+}
+
+impl<'a> Iterator for SentenceIterator<'a> {
+ type Item = char;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ for c in &mut self.chars {
match c {
- '?' | '!' | '.' => if in_sentence {
- endings.push(c);
- in_sentence = false;
+ '?' | '!' | '.' => if self.in_sentence {
+ self.in_sentence = false;
+ return Some(c);
},
_ => if !c.is_whitespace() {
- in_sentence = true;
+ self.in_sentence = true;
}
}
}
- endings
+ None
}
}

Георги качи решение на 20.10.2017 22:06 (преди почти 8 години)

use std::cmp;
use std::str;
pub struct TextInfo<'a> {
string: &'a str
}
impl <'a> TextInfo<'a> {
pub fn new(string: &'a str) -> Self {
TextInfo { string: string }
}
pub fn char_count(&self) -> usize {
self.string.chars().count()
}
pub fn alphabetic_count(&self) -> usize {
self.string.matches(char::is_alphabetic).count()
}
pub fn latin_letter_count(&self) -> usize {
self.string.matches( |c| c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' ).count()
}
pub fn cyrillic_letter_count(&self) -> usize {
self.string.matches( |c| c >= 'А' && c <= 'Я' || c >= 'а' && c <= 'я' ).count()
}
pub fn word_count(&self) -> usize {
self.string
- .split( |c: char| !c.is_alphabetic() ).into_iter()
+ .split( |c: char| !c.is_alphabetic() )
.filter( |s| !s.is_empty() )
.count()
}
pub fn sentence_count(&self) -> usize {
self.sentence_endings().count()
}
pub fn emotion(&self) -> String {
let mut exclamations = 0;
let mut questions = 0;
let mut full_stops = 0;
for c in self.sentence_endings() {
match c {
'!' => exclamations += 1,
'?' => questions += 1,
'.' => full_stops += 1,
_ => panic!(format!("Sentence ending was {}", c))
}
}
let emotion =
if exclamations > cmp::max(questions, full_stops) {
"😮"
} else if questions > cmp::max(exclamations, full_stops) {
"🤔"
} else {
"😐"
};
String::from(emotion)
}
fn sentence_endings(&self) -> SentenceIterator {
SentenceIterator::new(&self.string)
}
}
struct SentenceIterator<'a> {
chars: str::Chars<'a>,
in_sentence: bool
}
impl<'a> SentenceIterator<'a> {
fn new(string: &'a str) -> Self {
Self { chars: string.chars(), in_sentence: false }
}
}
impl<'a> Iterator for SentenceIterator<'a> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
for c in &mut self.chars {
match c {
'?' | '!' | '.' => if self.in_sentence {
self.in_sentence = false;
return Some(c);
},
_ => if !c.is_whitespace() {
self.in_sentence = true;
}
}
}
None
}
}

Изглежда супер :). Единствената разлика с моето е, че използвах while let Some(c) = self.chars.next(), но то е същото, в случая.

Иначе да, безсмислено е да се оптимизира нещо толкова тривиално, но според мен е въпрос на shift in thinking. И аз съм рубист и съм свикнал да размятам наоколо тонове копия на списъци и обекти, но културата на Rust е доста по-low-level, и според мен community-то би се мръщило на каквато и да е алокация, която е ненужна. Доста библиотеки вероятно ще са написани по този начин (включително std, както виждаш), доста примери вероятно ще изглеждат така. Та затова си заслужава да се напасне човек на mindset-а.

Когато си в Рим, правиш като римляните, когато пишеш Хаскел, бичиш point-free-style, etc. :)