[Python] Automatisiertes vergleichen/ID ändern/speichern

Vielen Dank euch beiden. Mit den Maps* habe ich das nicht so recht verstanden, habe damit noch nie gearbeitet, aber lese mal etwas nach.

Wenn Ihr wollt habe ich hier ein Beispieldatensatz mit 4x2 csv-Dateien (5MB gepackt): https://cloud8.eu/s/ezEeANBZgQedzk9
Wenn eine von den vier IDs irgendwann neben einer anderen ID gestanden ist, dann sollen alle (zusammenhängende) eben die selbe neue ID bekommen.
Diese eine neue ID soll dann in eine xlsx-Datei geschrieben werden (da komprimiert), mit jeweils alle Dateien (in diesen fall vier) am Stück, als Arbeitsblatt. Das letztere ist auch kein Problem.
Natürlich haben die Originaldateien noch viel mehr Spalten, dass sollte aber hier nicht relevant sein.

Das System nur zur Info: Windows 10 x64, Python 3.7 (in Jupyter Nootebooks), i9-10900k, 4x8GB DDR4-ECC RAM, irgendeine m.2 SSD. Also das System ist ganz ok.
 
Zuletzt bearbeitet:
Noch ein Hinweis zu Pandas. Ich bin kein großer Pandas Fan aber man iteriert nicht einfach so mit einer for Schleife über einen Pandas DF. Ne Übersicht der Alternativen, welche extrem viel performanter sind (Vektorisierung, iteritems(), iterrow() etc.), findest du in jedem Pandas Tutorial und bei Stackoverflow.

Dein Problem kann man aber meiner Meinung nach komplett ohne Pandas lösen.

Ich kann Dir Deinen Code nicht komplett schreiben. Da würde ich zwar wahrscheinlich in 30-60 Minuten packen aber dafür würde ich dann auch eine 50-100 Euro Rechnung schreiben. Nimm das Problem als Herausforderung mal besser programmieren zu lernen und dich mit Effizienz von Algorithmen auseinanderzusetzen. Dir war scheinbar gar nicht klar welcher Teil wie viel Zeit frisst und wie man das ganze massiv beschleunigen kann. Ich hatte bei meinen ersten Python Tests auch immer nen Timer eingebaut und die Performance verschiedener Algorithem miteinander verglichen. Wenn ich was komplett neues Testen würde, würde ich das genauso gegen den Status Quo benchmarken. Du musst immer überlegen wie lange welcher Teil vom Code braucht und ob das nicht evtl. alles viel schneller geht wenn man gleich das ganze Programm ändern. Daher Prozessdiagram! Am Reißbett kannst Du auch die Möglichkeiten Skizzieren für Deinen Algorithmus und theoretisch überlegen welche Variante sinnvoller ist (Zeitaufwand für Erstellung vs. Effizienz bei der Ausführung)
 
Zuletzt bearbeitet:
Crys schrieb:
Wenn Ihr wollt habe ich hier ein Beispieldatensatz mit 4x2 csv-Dateien (5MB gepackt): https://cloud8.eu/s/ezEeANBZgQedzk9
Fehlen da jetzt nicht komplett die Zahlen?

Crys schrieb:
Wenn eine von den vier IDs irgendwann neben einer anderen ID gestanden ist, dann sollen alle (zusammenhängende) eben die selbe neue ID bekommen.
Irgendwann oder nur in Typ 2- oder Typ 1-Dateien (wie in Tabelle 2)?
Sind Typ1&2 von Nummer eins, zwei, drei, ... als paare zu betrachten oder müssen grundsätzlich alle miteinander ausgwertet werden?

Und keine ID = gleiche ID (Tabelle 2 oder auch TestDatei_Typ1_Nr1.csv)?
 
Zuletzt bearbeitet:
KitKat::new() schrieb:
Fehlen da jetzt nicht komplett die Zahlen?
Ja, da kommen noch 20-16T Spalten mit Daten, die habe ich weg gelassen. Sind ja auch nicht von Relevanz!?

KitKat::new() schrieb:
Irgendwann oder nur in Typ 2- oder Typ 1-Dateien (wie in Tabelle 2)?
Sind Typ1&2 von Nummer eins, zwei, drei, ... als paare zu betrachten oder müssen grundsätzlich alle miteinander ausgwertet werden?

Und keine ID = gleiche ID (Tabelle 2 oder auch TestDatei_Typ1_Nr1.csv)?
Das verstehe ich nicht.

TypX: ist immer der Output eines Programmes. TypX-Dateien sehen immer gleich aus.

Jede ID in einer Zeile steht ja für eine newID. Also ist es egal ob in einer Zeile eine oder zwei IDs stehen. Diese eine oder beiden oldID stehen ja nur für eine einzige newID.

Jede ID ist erstmal nur für sich eine ID. Wenn eine weitere ID in der selben Zeile steht gehören diese zusammen. Wenn in einer anderen Zeile eine der ersten IDs steht, dann gehört diese weitere (dritte) ID auch zu der Familie, hat also die selbe newID, und so weiter ...
 
Crys schrieb:
Sind ja auch nicht von Relevanz!?
Ich dachte, die sollen addiert werden?

Crys schrieb:
TypX: ist immer der Output eines Programmes. TypX-Dateien sehen immer gleich aus.
Die Frage ist, ob die Ausgaben verschiedener Programme unabhängig sind. In anderen Worten: Benötigt man für die Auswertung von Typ1Nr1 eine Typ 2 Datei?

Ansonsten habe ich es mal probiert umzusetzen (in Rust, mit Platzhaltern für die zu aggregierenden Werte und mit der Annahme, dass Typ 1 Dateien abhängig von Typ 2 sind):

Rust:
use std::cell::Cell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;

use csv::Error;
use itoa;
use maplit::hashset;

type ID = Rc<[u8]>;

fn main() -> Result<(), Error> {
    let t0 = std::time::Instant::now();
    let mut bubbles = HashMap::new();
    let input_files = [
        "TestDatei_Typ1_Nr1.csv",
        "TestDatei_Typ1_Nr1.csv",
        "TestDatei_Typ1_Nr2.csv",
        "TestDatei_Typ1_Nr3.csv",
        "TestDatei_Typ1_Nr4.csv",
        "TestDatei_Typ2_Nr1.csv",
        "TestDatei_Typ2_Nr2.csv",
        "TestDatei_Typ2_Nr3.csv",
        "TestDatei_Typ2_Nr4.csv",
        ];

    let mut intermediate_representations = Vec::with_capacity(input_files.len());
    for f in input_files {
        let intermediate_representation = read_file(&mut bubbles, f)?;
        intermediate_representations.push(intermediate_representation);
    }
    println!("read complete: {}", t0.elapsed().as_secs_f64());

    let output_files = input_files.iter().map(|file_name| format!("New{}",file_name));
    intermediate_representations
        .into_iter()
        .zip(output_files)
        .try_for_each::<_, Result<(), Error>>(|(entries, f)| {
            write_file(&bubbles, entries, f.as_str())?;
            Ok(())
        })?;
    println!("write complete: {}", t0.elapsed().as_secs_f64());
    Ok(())
}

fn read_file(
    bubbles: &mut HashMap<ID, Rc<Cell<HashSet<ID>>>>,
    file_name: &str,
) -> Result<HashMap<ID, Cell<Option<i64>>>, Error> {
    let mut entries = HashMap::new();
    let mut reader = csv::ReaderBuilder::new()
        .delimiter(b';')
        .from_path(file_name)?;
    let mut record = csv::ByteRecord::new();
    reader.read_byte_record(&mut record)?;
    while reader.read_byte_record(&mut record)? {
        let first_id: Rc<[u8]> = Rc::from(&record[0]);
        if &record[1] != b"" {
            memorize_connection(bubbles, first_id.clone(), &record[1]);
        }
        entries.insert(
            first_id,
            Cell::new(Some(0.into())), //TODO: placeholder; decode fields from data
        );
    }
    Ok(entries)
}

fn memorize_connection(hash_map: &mut HashMap<ID, Rc<Cell<HashSet<ID>>>>, id1_rc: ID, id2: &[u8]) {
    let id1_opt = hash_map.get(&id1_rc).cloned();
    let id2_opt = hash_map.get(id2).cloned();
    match (id1_opt, id2_opt) {
        (Some(h1), Some(h2)) => {
            let mut h1_local = h1.take();
            let h2_local = h2.take();
            if h1_local != h2_local {
                let second_set_local = hash_map.remove(id2).unwrap().take();
                for key in second_set_local.iter() {
                    hash_map.insert(key.clone(), h1.clone());
                }
                h1_local.extend(second_set_local);
            };
            h1.set(h1_local)
        }
        (Some(h1), None) => add_unknown_key_to_bubble(hash_map, Rc::from(id2), &h1),
        (None, Some(h2)) => add_unknown_key_to_bubble(hash_map, id1_rc, &h2),
        (None, None) => {
            let id2_rc: Rc<[u8]> = Rc::from(id2);
            let new_set = Rc::new(Cell::new(hashset! {id1_rc.clone(), id2_rc.clone()}));
            hash_map.insert(id1_rc, new_set.clone());
            hash_map.insert(id2_rc, new_set);
        }
    }
}

fn add_unknown_key_to_bubble(
    bubbles: &mut HashMap<ID, Rc<Cell<HashSet<ID>>>>,
    unknown_id: ID,
    hash_set_found: &Rc<Cell<HashSet<ID>>>,
) {
    let mut local_hashset = hash_set_found.take();
    local_hashset.insert(unknown_id.clone());
    hash_set_found.set(local_hashset);
    bubbles.insert(unknown_id, hash_set_found.clone());
}

fn write_file(
    bubbles: &HashMap<ID, Rc<Cell<HashSet<ID>>>>,
    entries: HashMap<ID, Cell<Option<i64>>>,
    file_name: &str,
) -> Result<(), Error> {
    let mut writer = csv::Writer::from_path(file_name)?;
    for (key, value) in entries.iter() {
        match value.get() {
            Some(value) => {
                let sum = extract_aggregation(bubbles, &entries, key, value);
                //TODO: generate and write new ID, write other fields
                writer.write_field(key)?;
                let mut buffer = itoa::Buffer::new();
                writer.write_field(buffer.format(sum))?;
                writer.write_record(None::<&[u8]>)?;
            }
            None => {}
        }
    }
    writer.flush()?;
    Ok(())
}

fn extract_aggregation(
    bubbles: &HashMap<ID, Rc<Cell<HashSet<ID>>>>,
    entries: &HashMap<ID, Cell<Option<i64>>>,
    key: &ID,
    value: i64,
) -> i64 {
    let bubble_cell = match bubbles.get(key){
        None => return value,
        Some(bubble_cell) => bubble_cell
    };
    let bubble = bubble_cell.take();
    let sum = bubble
        .iter()
        .filter_map(|id| entries.get(id))
        .map(|cell| cell.replace(None))
        .filter_map(|perhaps_existing_value| perhaps_existing_value)
        .sum();
    bubble_cell.set(bubble); // keep it reusable
    sum
}
(Das Zeug über den Spaltennamen habe ich vorher entfernt)

In den Bubbles werden alle IDs gesammelt, welche verbunden sind.
Läuft via cargo run --release (nach Kompilation) innerhalb von einer Sekunde durch.
 

Anhänge

Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Crys

Ähnliche Themen

Zurück
Oben