[Rust]Summe eines Vec mit Wahrheitswerten

Alles, was nicht direkt mit Python-Problemen zu tun hat. Dies ist auch der perfekte Platz für Jobangebote.
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich verzweifle mal wieder und hoffe das ihr mir wieder helfen könnt. Ich habe einen `Vec` - Objekt mit Wahrheitswerten und will eigentlich nur zählen wie oft `true` darin steckt.

In Python würde ich

Code: Alles auswählen

sum([True, False, True])
schreiben.

Wenn ich in Rust einen `Vec<i32>` habe, dann kann ich auch schreiben:

Code: Alles auswählen

name_des_objekts.iter().sum();
Wie mache ich das aber bei `Vec<bool>`?
Hier mal der vollständige Code:

Code: Alles auswählen

use std::{
    fs::File,
    io::{prelude::*, BufReader},
    path::Path
};
extern crate itertools;
use itertools::Itertools;


fn is_safe(report: Vec<i32>) -> bool {
    let mut safe: Vec<bool> = Vec::new();
    for (x, y) in report.into_iter().tuple_windows() {
        if 1 <= x - y && x -y <= 3 {
            safe.push(true);
        } else if 1 <= y - x && y - x <= 3 {
            safe.push(true);
        } else {safe.push(false)}
    };
    safe.iter().all(|&x| x == true)
}


fn get_safe_reports(reports: Vec<Vec<i32>>) -> i32 {
    let safe_reports: Vec<_>= reports.into_iter().map(|report| is_safe(report)).collect();
    safe_reports.iter().sum()
}


fn read_lines(filepath: impl AsRef<Path>) -> Vec<String> {
    let file_content = File::open(filepath).expect("File not found");
    let buffer = BufReader::new(file_content);
    buffer.lines()
        .map(|l| l.expect("Can't read"))
        .collect()
}

fn format_input(lines: Vec<String>) -> Vec<Vec<i32>> {
    let mut reports: Vec<Vec<_>> = Vec::new();
    for line in lines {
        let line = line.split_whitespace().map(|x| x.parse().unwrap()).collect();
        reports.push(line);
    };
    reports

}



fn main() {
    let lines = read_lines("/home/dennis/AoC/2024/src/input02.txt");
    let reports = format_input(lines);
    println!("{:?}", get_safe_reports(reports));
}

Die Fehlermeldung:

Code: Alles auswählen

[dennis@dennis submarine]$ cargo run
   Compiling submarine v0.1.0 (/home/dennis/AoC/submarine)
error[E0277]: a value of type `i32` cannot be made by summing an iterator over elements of type `&bool`
    --> src/main.rs:26:25
     |
26   |     safe_reports.iter().sum()
     |                         ^^^ value of type `i32` cannot be made by summing a `std::iter::Iterator<Item=&bool>`
     |
     = help: the trait `Sum<&bool>` is not implemented for `i32`
     = help: the following other types implement trait `Sum<A>`:
               `i32` implements `Sum<&i32>`
               `i32` implements `Sum`
note: the method call chain might not have had the expected associated types
    --> src/main.rs:25:39
     |
25   |     let safe_reports: Vec<_>= reports.into_iter().map(|report| is_safe(report)).collect();
     |                               ------- ^^^^^^^^^^^ ----------------------------- `Iterator::Item` changed to `bool` here
     |                               |       |
     |                               |       `Iterator::Item` is `Vec<i32>` here
     |                               this expression has type `Vec<Vec<i32>>`
26   |     safe_reports.iter().sum()
     |                  ------ `Iterator::Item` is `&bool` here
note: required by a bound in `std::iter::Iterator::sum`
    --> /home/dennis/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/iterator.rs:3578:12
     |
3575 |     fn sum<S>(self) -> S
     |        --- required by a bound in this associated function
...
3578 |         S: Sum<Self::Item>,
     |            ^^^^^^^^^^^^^^^ required by this bound in `Iterator::sum`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `submarine` (bin "submarine") due to 1 previous error
Da steht jetzt das man den Typ i32 nicht aus der Summe vom Typ bool machen kann. Ich komme mit der Fehlermeldung allerdings nicht klar. Es ist schon so ewig her als ich das letzte mal etwas mit Rust versucht habe. Ich finde gerade gar kein Einstieg und habe auch im Internet keine Hilfe finden können.

Was ich noch zugeben muss, `safe_reports` hat den Typ `Vec<_>` weil es in einer vorherigen Fehlermeldung als Vorschlag kam, ich konnte noch nicht rausfinden wieso. Meine Vermutung liegt zur Zeit darin, dass da ein `map`-Objekt zurück kommt. Aber so ganz weiß ich das nicht. Das wäre sehr hilfreich wenn ihr mir das auch erklären könnte.


Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Antwort, die ich innerhalb von 3 Sekunden bei Google gefunden habe, lautet: https://stackoverflow.com/a/69847395

Ob sie für dich funktioniert, müsstest du ausprobieren.
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

ach man, hatte mich so auf das `sum` fokusiert. Dankeschön, funktioniert.

Code: Alles auswählen

use std::{
    fs::File, io::{prelude::*, BufReader}, path::Path
};
extern crate itertools;
use itertools::Itertools;


fn is_safe(report: Vec<i32>) -> bool {
    let mut safe_up: Vec<bool> = Vec::new();
    let mut safe_down: Vec<bool> = Vec::new();
    for (x, y) in report.into_iter().tuple_windows() {
        if 1 <= x - y && x -y <= 3 {
            safe_up.push(true);
        } else {safe_up.push(false)}
        if 1 <= y - x && y - x <= 3 {
            safe_down.push(true);
        } else {safe_down.push(false)}
    };
    safe_up.iter().all(|x| *x) || safe_down.iter().all(|x| *x)
}


fn get_safe_reports(reports: Vec<Vec<i32>>) -> u16 {
    let safe_reports: Vec<_>= reports.into_iter()
        .map(|report| is_safe(report))
        .collect();
    safe_reports.into_iter().filter(|b| *b).count().try_into().unwrap()
}


fn skip_index(iterable: Vec<i32>, index: usize) -> Vec<i32> {
    iterable.into_iter()
        .enumerate()
        .filter(|&(i, _)| i != index)
        .map(|(_, v)| v)
        .collect()
}


fn get_tolerated_safe_reports(reports: Vec<Vec<i32>>) -> u16 {
    let mut safe_reports: u16 = 0;
    for report in reports {
        let mut safe = 0;
        if is_safe(report.clone()) == true {
            safe = 1;
        } else {
            for index in 0..report.len() {
                let optimized_report = skip_index(report.clone(), index);
                if is_safe(optimized_report) == true {
                    safe = 1;
                    break;
                };
            };
        };
        safe_reports += safe;
        };
    safe_reports
    }


fn read_lines(filepath: impl AsRef<Path>) -> Vec<String> {
    let file_content = File::open(filepath).expect("File not found");
    let buffer = BufReader::new(file_content);
    buffer.lines()
        .map(|l| l.expect("Can't read"))
        .collect()
}


fn format_input(lines: Vec<String>) -> Vec<Vec<i32>> {
    let mut reports: Vec<Vec<_>> = Vec::new();
    for line in lines {
        let line = line.split_whitespace().map(|x| x.parse().unwrap()).collect();
        reports.push(line);
    };
    reports

}


fn main() {
    let lines = read_lines("/home/dennis/PycharmProjects/AdventofCode/2024/Day02/input02.txt");
    let reports = format_input(lines);
    println!("{:?}", get_safe_reports(reports.clone()));
    println!("{:?}", get_tolerated_safe_reports(reports));
}

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14131
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Der Grund ist wahrscheinlich, dass man in Rust keine Wahrheitswerte addieren kann. Das geht in Python ja hauptsächlich deswegen, weil es `bool` nicht von Anfang an gab, sondern da wie beispielsweise in C (vor ``_Bool``) oder BASIC einfach ``int`` für genommen wurde, mit C-Semantik: 0 ist „unwahr“, alle anderen Werte sind „wahr“. Es gab dann Leute die im Code damit gerechnet haben, wie man das auch von C oder BASIC kennt. Als dann `bool` eingeführt wurde, musste das von `int` erben und `True`/`False` die ganzahligen Werte 1 und 0 sein, damit alter Code der das so erwartete, weiterhin funktioniert. Also zum Beispiel dass man die addieren/aufsummieren kann.

Code: Alles auswählen

In [14]: issubclass(bool, int)
Out[14]: True

In [15]: isinstance(True, bool)
Out[15]: True

In [16]: isinstance(True, int)
Out[16]: True

In [17]: True == 1
Out[17]: True

In [18]: True + 1
Out[18]: 2

In [19]: False == 0
Out[19]: True

In [20]: False - 1
Out[20]: -1
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Achso, ich bin natürlich davon ausgegangen, dass die Wahrheitswerte grundsätzlich immer 0 oder 1 sind, sprachenunabhängig. Dass das anders sein könnte, darüber habe ich noch nie nachgedacht.


Danke und Grüße
Dennis

Achja, auch wenn da Problem gelöst ist, wenn jemand Kritik loswerden will, immer gerne.
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
__blackjack__
User
Beiträge: 14131
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Also ich habe ganz am Anfang gelernt, dass Wahrheitswerte 0 oder -1 sind. CBM/Microsoft BASIC halt. Es gibt da nicht nur keinen Typ für Wahrheitswerte, sondern auch nur bitweises NOT, AND, und OR. Damit das auch für Wahrheitswerte funktioniert, muss wenn 0 „unwahr“ ist, -1 „wahr“ sein, weil NOT alle Bits negiert, also aus 0b0000000000000000 dann 0b1111111111111111 wird und das als vorzeichenbehaftete ganze Zahl im Zweierkomplement eine -1 repräsentiert.

Wenn man saubere Typen haben will und einen Wahrheitstyp mit zwei sinnvollen Werten, dann sollten darauf eigentlich auch nur Operationen definiert sein, bei denen wieder einer dieser beiden Werte heraus kommt, und nicht der Wertebereich verlassen wird.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Das heißt dann, das Sprachen die 0 und 1 verwenden unter der Haube keine bitweise Vergleiche macht?

Code: Alles auswählen

>>>0 == ~-1
True
>>>False == ~True
False
>>>~True
-2
Hier gibt es wohl kein Bit-Operator:

Code: Alles auswählen

10 PRINT NOT TRUE
20 PRINT FALSE

RUN
-1
0

READY.
Rust:

Code: Alles auswählen

fn main() {
    println!(
        "true: {:?}\n
        false: {:?}\n
        ~true: {:?}", 
        true as i32, false as i32, !true as i32
    );
}

Code: Alles auswählen

[dennis@dennis test]$ cargo run
   Compiling test v0.1.0 (/home/dennis/test)
true: 1
false: 0
~true: 0
Damit es einheitlich bleibt, habe ich in die Ausgabe `~` geschrieben auch wenn Rust `!` verwendet. Dann müsste man damit doch rechnen können? Oder liegt da noch eine "Schicht" dazwischen die mir halt 0 und 1 macht, wenn ich das explizit umwandle? Weil ohne `as i32` wird nur `true` oder `false` ausgegeben.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Vermutlich wirst du die Wahrheitswerte zum Rechnen explizit in einen Integer Datentyp von Rust casten müssen. Ich finde count() an der Stelle aber sowieso besser.
Benutzeravatar
__blackjack__
User
Beiträge: 14131
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Dennis89: Was genau meinst Du mit bitweisen Vergleichen?

Python verwendet für die Werte für die Namen `True` und `False` Objekte die zwar vom Typ `bool` sind, aber gleichzeitig auch die `int`-Werte 0 und 1.

Bei dem BASIC-Beispiel ist die Falle wohl das bei TRUE und FALSE jeweils die ersten beiden Buchstaben TR und FA als Variablennamen verwendet werden, und da beide Variablen undefiniert sind, haben die beide den Wert 0. Sind also beide „unwahr“ weshalb NOT TRUE den Wert für „wahr“ ergibt, was nicht wirklich intuitiv ist.

Code: Alles auswählen

10 T=1=1:F=1<>1
20 PRINT T; NOT T
30 PRINT F; NOT F
RUN
-1  0
 0 -1

READY.
Das NOT fasst die Zahl als 16-Bit-Ganzzahl auf und dreht einfach alle Bits um. Aus jeder 0 wird eine 1 und aus jeder 1 eine 0. Also das was `~` in Python macht. Das kann man auch in Python für Zahlen als Wahrheitswerte verwenden, aber das klappt in beiden Sprachen nur wenn der jeweilige Wert auch tatsächlich 0 bzw. -1 ist. Nicht wenn er beispielsweise 1 ist, denn die bitweise Negation davon ist -2. Beide Werte sind in beiden Sprachen im Wahrheitskontext, also beispielsweise als IF-Bedingung „wahr“. ``not`` in Python dagegen wandelt den Operanden erst in `True` oder `False` um und liefert dann das Gegenteil davon.

BASIC's NOT, AND, OR entsprechen `~`, `&`, `|` in Python. Python's `not`, `and`, `or` gibt es in BASIC nicht. Wobei das nicht generell für jeden BASIC-Dialekt gilt. Es gibt auch welche wo NOT, AND, OR eher `not`, `and`, `or` in Python entsprechen. Meistens ohne „Kurzschlussauswertung“. Und dann gibt es entweder bitweise Verknüpfungen als extra Schlüsselworte, oder auch gar nicht.

Rust hat Bitoperatoren für Wahrheitswerte so implementiert, das sie die als 1-Bit-Wert betrachten und das Byte das zum Speichern verwendet wird nur die Werte 0 und 1 annehmen kann. Ein ~x ist dann also in der Umsetzung eigentlich ein `((x as u8) ^ 1) as bool` (keine Ahnung ob die Syntax so funktioniert). Also letztlich das gleiche was bei `!` passiert.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
narpfel
User
Beiträge: 696
Registriert: Freitag 20. Oktober 2017, 16:10

__blackjack__ hat geschrieben: Freitag 5. September 2025, 15:01 Rust hat Bitoperatoren für Wahrheitswerte so implementiert, das sie die als 1-Bit-Wert betrachten und das Byte das zum Speichern verwendet wird nur die Werte 0 und 1 annehmen kann. Ein ~x ist dann also in der Umsetzung eigentlich ein `((x as u8) ^ 1) as bool` (keine Ahnung ob die Syntax so funktioniert). Also letztlich das gleiche was bei `!` passiert.
Das würde ich so nicht ausdrücken. Als Implementierungsdetail stimmt das zwar, aber konzeptionell ist `bool` in Rust keine Zahl, sondern (fast) ein `enum`:

Code: Alles auswählen

enum Bool {
    False,
    True,
}

impl std::ops::Not for Bool {
    type Output = Self;

    fn not(self) -> Self {
        match self {
            Self::False => Self::True,
            Self::True => Self::False,
        }
    }
}
@Dennis89: Anmerkungen zu deinem Code: Zuerst mal solltest du `cargo fmt` benutzen; die Einrückung und Klammersetzung ist an einigen Stellen schon arg... gewöhnungsbedürftig. Dann noch `cargo clippy`, um zumindest Sachen zu finden, die ein Computer gut erkennen kann.

`is_safe` nimmt einen `Vec<i32>` by move, aber die Funktion braucht gar keinen geownten `Vec`. Wenn du eine Referenz übergibst, sparst du dir später im Code eine unnötige Kopie (`is_safe(report.clone())`).

Dann baust du zwei `Vec<bool>` auf, nur um am Ende zu prüfen, ob alle Werte `true` sind. Das brauchst du nicht; ein `bool` pro Richtung, das speichert, ob es safe ist, ist einfacher:

Code: Alles auswählen

fn is_safe(report: &[i32]) -> bool {
    let mut safe_up = true;
    let mut safe_down = true;
    for (x, y) in report.iter().tuple_windows() {
        safe_up &= 1 <= x - y && x - y <= 3;
        safe_down &= 1 <= y - x && y - x <= 3;
    }
    safe_up || safe_down
}
Meine Lösung von Dezember iteriert mehrfach über den Report, ist aber (meiner Meinung nach) einfacher verständlich, weil die Problembeschreibung direkter abgebildet wird:

Code: Alles auswählen

fn is_safe(report: &[i32]) -> bool {
    (report.is_sorted() || report.is_sorted_by_key(Reverse))
        && report
            .iter()
            .tuple_windows()
            .all(|(&x, &y)| (1..=3).contains(&x.abs_diff(y)))
}
In `get_safe_reports` erstellst du einen `Vec<bool>`, nur um danach dann über den `Vec` zu iterieren. So ist es einfacher:

Code: Alles auswählen

fn get_safe_reports(reports: &[Vec<i32>]) -> usize {
    reports.iter().filter(|report| is_safe(report)).count()
}
Ich würde da als Rückgabetyp auch `usize` nehmen, weil `usize` in Rust einfach der natürliche Typ für Anzahlen und Größen ist.

In `skip_index` sollte `iterable` wieder als Slice übergeben werden, weil die Funktion keine Ownership braucht.

`get_tolerated_safe_reports` kann man ähnlich wie `get_safe_reports` mit `filter` und `count` schreiben.

`read_lines` kann man einfacher schreiben mit `std::fs::read_to_string` und dann `str::lines`.

`format_input` ist der falsche Name für die Funktion. Die formatiert ja nicht den Input, sondern sie parst ihn.

`extern crate` braucht man in Rust seit 2018 generell nicht mehr, das solltest du entfernen.

Ich komme dann am Ende ungefähr auf das hier (https://godbolt.org/z/87hE9ej5q):

Code: Alles auswählen

use itertools::Itertools;
use std::cmp::Reverse;
use std::fs::read_to_string;

fn is_safe(report: &[i32]) -> bool {
    (report.is_sorted() || report.is_sorted_by_key(Reverse))
        && report
            .iter()
            .tuple_windows()
            .all(|(&x, &y)| (1..=3).contains(&x.abs_diff(y)))
}

fn get_safe_reports(reports: &[Vec<i32>]) -> usize {
    reports.iter().filter(|report| is_safe(report)).count()
}

fn skip_index(iterable: &[i32], index: usize) -> Vec<i32> {
    iterable
        .iter()
        .enumerate()
        .filter(|&(i, _)| i != index)
        .map(|(_, v)| *v)
        .collect()
}

fn get_tolerated_safe_reports(reports: &[Vec<i32>]) -> usize {
    reports
        .iter()
        .filter(|report| {
            is_safe(report) || (0..report.len()).any(|index| is_safe(&skip_index(report, index)))
        })
        .count()
}

fn parse_input<'a>(lines: impl Iterator<Item = &'a str>) -> Vec<Vec<i32>> {
    lines
        .map(|line| {
            line.split_whitespace()
                .map(|word| word.parse().unwrap())
                .collect()
        })
        .collect()
}

fn main() {
    let input = read_to_string("input.txt").unwrap();
    let reports = parse_input(input.lines());
    println!("{:?}", get_safe_reports(&reports));
    println!("{:?}", get_tolerated_safe_reports(&reports));
}
Und dann könnte man noch über den Namen `get_safe_reports` nachdenken, das gibt dir ja nicht die safen Reports, sondern zählt sie. Also eher `count_safe_reports`.

Dann ist noch das `iter`/`filter`/`count` in `get_safe_reports` und `get_tolerated_safe_reports` dupliziert; man könnte da eine einzelne Funktion schreiben und der ein Lambda übergeben, das die Reports überprüft.
Benutzeravatar
__blackjack__
User
Beiträge: 14131
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@narpfel: Was genau ist denn das Problem an der Beschreibung? Die Dokumentation sagt, dass `bool` ein Byte belegt und die Bitmuster 0x01 und 0x00 für `true` und `false` stehen. Und es gibt die Bitoperatoren die sich so verhalten, als würden sie auf genau einem Bit operieren. Es steht auch da, dass `bool` nach `u8` casten kein Problem ist, umgekehrt aber schon, eben weil dann illegale Bitmuster entstehen können. Das ist alles so formuliert als wenn man sich darauf verlassen könnte, also nicht wirklich Implementierungsdetail, also das die nicht morgen andere Speichergrössen oder Bitmuster nehmen können.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
narpfel
User
Beiträge: 696
Registriert: Freitag 20. Oktober 2017, 16:10

@__blackjack__: Die Beschreibung vermischt IMHO zu sehr die Repräsentation mit der abstrakten Definition. Die Referenz definiert die Operationen auf `bool` mit Wahrheitstabellen und nicht mit Operationen auf der Repräsentation: https://doc.rust-lang.org/stable/refere ... .bool.expr

Das ist anders als in C, wo die Definition von `!` so aussieht:
https://open-std.org/JTC1/SC22/WG14/www/docs/n3220.pdf hat geschrieben: The result of the logical negation operator ! is 0 if the value of its operand compares unequal to
0, 1 if the value of its operand compares equal to 0. The result has type int. The expression !E is
equivalent to (0==E).
(Abschnitt 6.5.4.3 (Seite 82))
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Danke für eure Antworten und das Code-Review!

Schön dass gleich aufgefallen ist, wo die Aufgabenstellung her kommt.
Die zwei cargo-Funktionen kannte ich noch gar nicht, werde ich in Zukunft benutzen. Das mit Ownershipp habe ich stark vernachlässigt, das muss ich mir abgewöhnen, ist ja schon ein essenzielles Konzept der Sprache.

Meine Python-Lösung iteriert auch zwei mal, zwar aus anderen Gründen, aber das hat mir nicht gefallen und ich war sehr froh, dass ich dafür hier eine andere Lösung gefunden habe. Wenn man deine Lösung betrachtet, kann ich dir dennoch zustimmen.

`skip_index` schreibt du so:

Code: Alles auswählen

fn skip_index(iterable: &[i32], index: usize) -> Vec<i32> {
    iterable
        .iter()
        .enumerate()
        .filter(|&(i, _)| i != index)
        .map(|(_, v)| *v)
        .collect()
}
und ich habe das nach dem ich deine Anmerkung dazu gelesen habe, so geschrieben:

Code: Alles auswählen

fn skip_index(iterable: &[i32], index: usize) -> Vec<i32> {
    iterable
        .into_iter()
        .enumerate()
        .filter(|&(i, _)| i != index)
        .map(|(_, &v)| v)
        .collect()
}
Wann verwendet man `iter()` und wann `into_iter()`?
Bei `filter` und `map` habe ich das `&` einmal "für alles" und einmal explizit für den Wert, denn ich auch nur benötige. Welche Variante sollte man verwenden? Und wieso nimmst du die Referenz in `map` wieder weg? Das Konzept "Dereference" im Allgemeinen leuchtet mir noch nicht ganz an.

Die weiteren Anmerkungen und Namen habe ich angepasst. Den Hinweis über das doppelte `iter/filter/count` muss ich noch abarbeiten, das wird vermutlich erst heute Abend was.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 696
Registriert: Freitag 20. Oktober 2017, 16:10

Dennis89 hat geschrieben: Samstag 6. September 2025, 11:53 Wann verwendet man `iter()` und wann `into_iter()`?
`into_iter` nimmt Ownership von dem Ding, über das Iteriert werden soll; `iter` borrowt den Wert. Sieht man an der an der Signatur von den Funktionen: https://doc.rust-lang.org/nightly/std/v ... .into_iter und https://doc.rust-lang.org/nightly/std/p ... ethod.iter. Bei `&[T]` hast du dann zusätzlich noch, dass du aus einer Referenz nicht rausmoven kannst. Also muss der Iterator, den du von `into_iter` bekommst, `&T` liefern; macht also das gleiche wie `iter`. Clippy sagt dir an der Stelle dann auch, dass man bei Slices `into_iter` nicht benutzen sollte: https://godbolt.org/z/fa48n4vbn

Bei `Vec<T>` hast du einen Unterschied.
Bei `filter` und `map` habe ich das `&` einmal "für alles" und einmal explizit für den Wert, denn ich auch nur benötige. Welche Variante sollte man verwenden? Und wieso nimmst du die Referenz in `map` wieder weg?
Vielleicht hilft es, mal alle Typen explizit hinzuschreiben? Und dann mit der Doku vergleichen, warum da wo welche Referenzen herkommen?

Code: Alles auswählen

fn skip_index(iterable: &[i32], index: usize) -> Vec<i32> {
    let it: impl Iterator<Item = &i32> = iterable.iter();
    let it: impl Iterator<Item = (usize, &i32)> = it.enumerate();
    let it: impl Iterator<Item = (usize, &i32)> = it.filter(|&(i, _): &(usize, &i32)| i != index);
    let it: impl Iterator<Item = i32> = it.map(|(_, &v): (usize, &i32)| v);
    let vec: Vec<i32> = it.collect();
    vec
}
(Das funktioniert so nur mit nightly Rust, weil `impl Trait` als Typangabe von lokalen Variablen noch nicht stable ist, aber ist IMHO hier eine gute Demonstration, welche Typen man wo hat. https://godbolt.org/z/4h7xncn1a)

Der Unterschied von meiner Version zu deiner Version vom Lambda in `map` ist das hier (https://godbolt.org/z/sjbPT6sYY):

Code: Alles auswählen

pub fn main() {
    let x = 42;
    let ref_x = &x;
    let &deref_via_pattern = ref_x;
    let deref_via_operator = *ref_x;
    dbg!(deref_via_pattern);
    dbg!(deref_via_operator);
}
Du kannst eine Referenz auf zwei Arten dereferenzieren: mit `*` und mit einem Referenz-Pattern.
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

danke für die Erklärungen.

Auf den Punkt mit `&` und `*` muss ich allerdings noch einmal eingehen. Ich schreibe erst mal was ich denke verstanden zu haben. `&` setzt mir eine Referenz auf den ursprünglichen Wert. Sprich im folgenden Beispiel besitzt `y` nicht den gleichen Wert wie `x` sondern zeigt nur auf den Wert.
Eventuell so vergleichbar, `x` ist ein Mensch und bildet sich eine Meinung und `y` ist sehr faul und sagt einfach er hat die gleiche Meinung wie `x`.

Code: Alles auswählen

fn main() {
    let x = 5;
    let y = &x;
}


Ich kann `y` aber nicht mit anderen Datentypen vergleichen, da die Referenz ein eigener Typ ist. Wenn ich `x` mit `y` vergleichen will, dann muss ich die Referenz wieder weg nehmen mit `*y`. Gehört `y` jetzt der Wert 5?
Das machen wir ja auch hier:

Code: Alles auswählen

.map(|(_, v)| *v)
`v` wird gegen Wahrheitswerte geprüft und da der Referenztyp nicht dem eigentlichen Wert entspricht, müssen wir die Referenz weg nehmen um den Wert an sich zu prüfen.
Wenn ich aber

Code: Alles auswählen

.map(|(_, &v)| v)
schreibe, dann ist `v` vom Typ `&i32` und der Vergleich geht trotzdem. Da habe ich irgendwas noch nicht verstanden.

Und folgendes leuchtet mir auch nicht ein:

Code: Alles auswählen

let it: impl Iterator<Item = (usize, &i32)> = it.filter(|&(i, _): &(usize, &i32)| i != index);
Wieso ist `i` hier nicht vom Type `&usize`? Wäre für mich eine logische Schlussfolgerung, wenn ich das `&` vor einen Klammerausdruck setze. Vergleichsweise mit Mathe, da würde man den Inhalt der Klammer jeweils mit `&` verrechnen.



Den Unterschied von den lambda-Funktionen muss ich mir in Ruhe noch einmal anschauen. Würde erst die oben genannten Grundlagen verstehen.


Danke und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 696
Registriert: Freitag 20. Oktober 2017, 16:10

Dennis89 hat geschrieben: Sonntag 7. September 2025, 09:31 Eventuell so vergleichbar, `x` ist ein Mensch und bildet sich eine Meinung und `y` ist sehr faul und sagt einfach er hat die gleiche Meinung wie `x`.
Mmh, naja. Was Vergleiche angeht, hinkt der schon ziemlich. Ich hab aber leider auch nicht so richtig eine schöne Analogie, mit der ich Referenzen erklären könnte, also verweise ich auf das Buch: https://doc.rust-lang.org/stable/book/c ... owing.html
Ich kann `y` aber nicht mit anderen Datentypen vergleichen, da die Referenz ein eigener Typ ist.
Du meinst wahrscheinlich das richtige, aber die Formulierung so ist falsch: Du kannst `y` nicht mit Werten von einem anderen Typ vergleichen. `y` mit einem Datentyp zu vergleichen würde ungefähr so aussehen: `y == String`. Und so generell stimmt das nicht: Der Datentyp bestimmt, welche Operationen definiert sind und welche nicht. Z. B. gibt es eine Implementierung von `==` für `String` und `&str`, aber nicht für `u32` und `&u32`.
Wenn ich `x` mit `y` vergleichen will, dann muss ich die Referenz wieder weg nehmen mit `*y`. Gehört `y` jetzt der Wert 5?
Nein, `*y` ist eine Kopie von dem Wert, auf den `y` verweist (also `x`).

(Etwas genauere Erklärung: `*y` ist ein Ausdruck, der aus der Referenz `y` eine sog. place expression (weiterführende Lektüre: https://steveklabnik.com/writing/thinki ... s-in-rust/) macht. Da `i32` `Copy` ist, kann `*y` an Stellen verwendet werden, die eine value expression vom Typ `i32` erwarten, und der Wert ist dann eine Kopie von dem Wert, auf den `y` verweist. (Wenn der Typ nicht `Copy` ist, wird bei der Konvertierung von place nach value gemovet; und man kann nicht aus Referenzen moven, also würde `let z = *y;` nicht gehen, wenn `y` z. B. den Typ `&String` hätte.))
Wenn ich aber

Code: Alles auswählen

.map(|(_, &v)| v)
schreibe, dann ist `v` vom Typ `&i32` und der Vergleich geht trotzdem. Da habe ich irgendwas noch nicht verstanden.
Nein, `&v` ist vom Typ `&i32`. Durch das Referenz-Pattern wird die Referenz quasi „weggematcht“ und `v` hat den Typ `i32` und ist eine Kopie von dem Wert, auf den `v` verweist. Das ist analog z. B. hierzu:

Code: Alles auswählen

struct Number(u64);

fn f(number: Number) {
    let Number(n) = number;
    println!("{n}");
}
Hier hat `n` den Typ `u64`, nicht `Number`.
Wieso ist `i` hier nicht vom Type `&usize`? Wäre für mich eine logische Schlussfolgerung, wenn ich das `&` vor einen Klammerausdruck setze. Vergleichsweise mit Mathe, da würde man den Inhalt der Klammer jeweils mit `&` verrechnen.
Die Klammern sind nur Syntax für ein Tuple. Wäre das auch eine logische Schlussfolgerung für dich, wenn da statt einem Tuple ein `struct Pair { first: usize, second: &i32 }` stehen würde? Also in etwa so?

Code: Alles auswählen

    it.filter(|&Pair { first: i, second: _ }: &Pair| i != index);
Beispiel: https://godbolt.org/z/jK4n9b6PT

Kleiner Tipp noch, wenn du rausfinden willst, was für einen Typ ein Ausdruck hat: Der Compiler sagt dir den Typen, wenn du einen Typfehler erzeugst. Normalerweise macht man das so: `let _name: () = expression;` Man versucht also, den Wert einer Variablen vom Typ `()` zuzuweisen. Beispiel mit dem ursprünglichen Code, Zeile 33: https://godbolt.org/z/3sfW94vGj
Benutzeravatar
snafu
User
Beiträge: 6881
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Also wenn man in etwa verstanden hat, was in C ein Zeiger und eine Dereferenzierung ist, kann man das für Rust alles nochmal neu lernen. Liege ich da richtig? :D
narpfel
User
Beiträge: 696
Registriert: Freitag 20. Oktober 2017, 16:10

@snafu: Nicht wirklich. In Rust kommt halt noch Pattern Matching dazu, und place und value expressions heißen in C lvalues und rvalues. Ansonsten ist da nicht so wirklich ein großer Unterschied.
Benutzeravatar
Dennis89
User
Beiträge: 1592
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Morgen,

vielen Dank für die ausführliche Erklärung. Parallel bzw. nach absenden meines vorherigen Posts, habe ich mich auch noch mal durch die Doku gelesen und in Verbindung mit deiner Antwort, meine ich, ich habe es verstanden. Zumindest habe ich aktuell keine offenen Fragen zu dem Thema mehr.

Ich hoffe das ich etwas regelmäßiger an der Sprache dran bleiben kann um das gelernte öfters umzusetzen.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten