Malerwerkst.at

Santa's Path

Introduction

R.


Rust

Santa's Path

Posted by R. on .
Featured

Rust

Santa's Path

Posted by R. on .

Bevor wir mit Tag 5 weitermachen, habe ich mir eine kleine Zwischenaufgabe überlegt:
Am Tag 3 sollte Santa Geschenke ausliefern und bekam dafür Richtungsanweisungen eines (leicht verwirrten) Elfen.

  • "^" ein Schritt nach Norden (+Y)
  • "v" ein Schritt nach Süden (-Y)
  • ">" ein Schritt nach Osten (+X)
  • "<" ein Schritt nach Westen (-X)

Unsere Lösung zu Teil 1 sagte uns, dass insgesamt 2572 Häuser mit Geschenken belieferten wurden. Aber welchen Weg musste Santa dafür zurücklegen und wie sah der aus?

Mit anderen Worten, sollen wir eine Bilddatei erstellen, die Santas Weg ("v>vvv>v<<<^^^^^<<^<...") darstellt. Jeder Wegpunkt könnte dabei durch einen weißen Pixel markiert werden.

Die Pixel-Positionen haben wir ja in unserem HashSet "houses" bereits vorliegen und müssten diese nur noch in eine Bilddatei schreiben. Allerdings weisen diese Posititionstupel auch negative Werte, z.B. (-30, 11) oder (-25, -16) oder (1, -42), auf, die quasi über unseren Leinwandrand hinausragen würden und daher vorher "normalisiert" werden müssten.

Wir suchen zunächst also den kleinsten X und Y Wert.

    let mut x:Vec<i32> = Vec::new();
    let mut y:Vec<i32> = Vec::new();

    for h in houses {
        x.push(h.0);
        y.push(h.1);
    }
    let minx = x.iter().min().unwrap().abs();
    let miny = y.iter().min().unwrap().abs();

    println!("Min X: {} / Min Y: {}",-minx,-miny);

Diese gefunden (absoluten) Werte addieren wir nun auf alle Positionswerte und eliminieren somit alle negativen, halten dabei aber die relativen Abstände bei.

    for e in x.iter_mut() { *e += minx; }
    for e in y.iter_mut() { *e += miny; }

Jetzt haben wir fast alles zusammen, was wir brauchen - es fehlt allerdings noch die maximale Bildausdehnung, die sich aber einfach aus den jeweils größten Werten für X und Y ableiten lässt:

    let maxx = x.iter().max().unwrap();
    let maxy = y.iter().max().unwrap();

Wie entsteht aus diesen Rohdaten nun aber ein Bild? Wie kann ich allgemein in Rust ein Bild erzeugen? Ganz einfach, ich bediene mich eines Library-crates; in diesem Fall dem "image"-crate der Piston Entwickler.

Update: Hätte ich gewusst, wieviel Arbeit im Vorfeld nötig ist, um durch die "image API" durchzusteigen - die Doku ist IMHO in der Beziehung recht dürftig -, hätte ich mich für eine andere lib / ein anderes crate entschieden...

In unserer Cargo.toml Datei wird daher folgender Abschnitt eingefügt / ergänzt:

[dependencies]
image = "0.10.3"  

Der Code könnte komplettiert dann ungefähr so aussehen:

extern crate image;  
extern crate resize;

use std::fs::File;  
use std::io::Read;  
use std::collections::HashSet;

use image::{ImageBuffer, RgbImage, Rgb};

fn get_data(fname: &str) -> String {  
    let mut file = match File::open(fname) {
        Err(e) => panic!("file error: {}",e),
        Ok(file) => file,
    };

    let mut data = String::new();

    match file.read_to_string(&mut data) {
        Err(e) => panic!("read error: {}", e),
        Ok(_) => {},
    }
    data
}


fn main() {  
    let mut houses = HashSet::new();
    let mut pos=(0,0);

    let data=get_data("input.txt");

    for ch in data.chars() {
        match ch {
            '^' => pos.1+=1,
            '>' => pos.0+=1,
            'v' => pos.1-=1,
            '<' => pos.0-=1,
            _ => {},
        }
        houses.insert(pos);
    }

    let mut xpos:Vec<i32> = Vec::new();
    let mut ypos:Vec<i32> = Vec::new();

    for h in houses {
        xpos.push(h.0);
        ypos.push(h.1);
    }

    let minx = xpos.iter().min().unwrap().abs();
    let miny = ypos.iter().min().unwrap().abs();

    for e in xpos.iter_mut() { *e += minx; }
    for e in ypos.iter_mut() { *e += miny; }

    let maxx = xpos.iter().max().unwrap() + 1;
    let maxy = ypos.iter().max().unwrap() + 1;

    let mut image: RgbImage = ImageBuffer::new((maxx as u32), (maxy as u32));

    for (&x,&y) in xpos.iter().zip(ypos.iter()) {
    image.put_pixel((x as u32),(y as u32), Rgb([240,240,240]));
    }    

    image.save("output.png").unwrap();
}

Das Ergebnis, und damit Santa's Path, wird in "output.png" geschrieben und sieht beim vorgegebenen Input so aus:

Etwas zu klein, aber immerhin schon mal was! Auf welchen Wegen wir daraus ein größeres Bild erzeugen können (ohne Santa Extra-Wege laufen zu lassen!), wird ein einem der nächsten Artikel beleuchtet werden.

Wie immer bin ich auf Anregungen, Kritik und Verbesserungsmöglichkeiten gespannt!

R.

View Comments...