首先尝试使用 Rust 进行日志文件分析

时间:2021-05-07 06:31:42

标签: rust

我经常对日志文件执行相同的分析。最初,我有一个用于 grep 和 sort 的小型 Awk 脚本。为了好玩,我把它改写成 Python:

#!/usr/bin/python3

import sys

months = { "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6,
           "Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12 }

months_r = { v:k for k,v in months.items() }

totals = {}

for line in sys.stdin:
    if "redis" in line and "Partial" in line:
        f1, f2 = line.split()[:2]
        w = (months[f1], int(f2))
        totals[w] = totals.get(w, 0) + 1

for k in sorted(totals.keys()):
    print(months_r[k[0]], k[1], totals[k])

然后转到 Go(为了避免太长,这里不再引用 Go 版本(68 行))。

我现在试图用 Rust 来表达它(到目前为止我只写了一些玩具示例)并且我很卡。一开始,我有很多错误,而且越来越好,但现在我有一个我无法修复......

有人可以帮忙说明如何在 Rust 中表达这一点吗? Python 版本很短,所以最好有一些非常地道的东西,而不是太冗长。

这是我目前为止的位置,但我无法取得任何进一步的进展。

use std::array::IntoIter;
use std::collections::HashMap;
use std::io;
use std::io::prelude::*;
use std::iter::FromIterator;

fn main() {
    let m = HashMap::<_, _>::from_iter(IntoIter::new([
        ("Jan", 1),
        ("Feb", 2),
        ("Mar", 3),
        ("Apr", 4),
        ("May", 5),
        ("Jun", 6),
        ("Jul", 7),
        ("Aug", 8),
        ("Sep", 9),
        ("Oct", 10),
        ("Nov", 11),
        ("Dec", 12),
    ]));

    let mut totals = HashMap::new();

    for l in io::stdin().lock().lines() {
        let ul = l.unwrap();
        if ul.contains("redis") && ul.contains("Partial") {
            let mut wi = ul.split_whitespace();
            let f1 = wi.next().unwrap();
            let f2 = wi.next().unwrap();
            let count = totals.entry((m.get(&f1).unwrap(), f2)).or_insert(0);
            *count += 1;
        }
    }
}

简单的提示将不胜感激,我不是要一个完整的工作解决方案,这是更多的工作(但如果有的话,我当然会欢迎)。

非常感谢!

1 个答案:

答案 0 :(得分:1)

问题是您的 f2 指的是当前行字符串 ul 拥有的数据。但是,在循环的每次迭代中都会删除 ul,并由 lines() 迭代器分配一个新的。如果您将引用 ul 的切片插入 totals 哈希图中,该切片将在循环的下一次迭代中失效,并且当您稍后尝试访问已释放的内存时,程序将崩溃或出现故障数据。

split_whitespace() 的行为是为了效率:在调用它时,您通常只需要检查字符串,返回所有内容的新分配副本将是一种浪费。取而代之的是,ul.split_whitespace() 提供廉价的切片,这些切片实际上是 ul 的视图,允许您选择是否复制返回的字符串切片。

解决方案很简单,只需使用 to_string() 从返回的切片创建一个拥有的字符串。例如,这会编译:

fn main() {
    let m = HashMap::<_, _>::from_iter(IntoIter::new([
        ("Jan", 1),
        // ...
    ]));

    let mut totals = HashMap::new();

    for l in io::stdin().lock().lines() {
        let ul = l.unwrap();
        if ul.contains("redis") && ul.contains("Partial") {
            let mut wi = ul.split_whitespace();
            let f1 = wi.next().unwrap();
            let f2 = wi.next().unwrap().to_string();
            *totals.entry((m.get(f1).unwrap(), f2)).or_insert(0) += 1;
        }
    }
}

一个更简单的选择是做你的 Python 代码所做而 Rust 翻译没有做的事情,即将 f2 解析为整数。整数可以按值复制到映射中,您不再需要分配 f2 的副本:

for l in io::stdin().lock().lines() {
    let ul = l.unwrap();
    if ul.contains("redis") && ul.contains("Partial") {
        let mut wi = ul.split_whitespace();
        let f1 = wi.next().unwrap();
        let f2 = wi.next().unwrap();
        let w = (m.get(f1).unwrap(), f2.parse::<u32>().unwrap());
        *totals.entry(w).or_insert(0) += 1;
    }
}

最后,排序和打印应该通过对原始 Python 的相当简单的翻译来实现:

let months_r: HashMap<_, _> = m.iter().map(|(k, v)| (v, k)).collect();

let mut totals_keys: Vec<_> = totals.keys().collect();
totals_keys.sort();
for k in totals_keys {
    println!(
        "{} {} {}", months_r.get(k.0).unwrap(),
        k.1, totals.get(k).unwrap()
    );
}

Playground

总共有 48 行代码 rustfmt - 比 Python 长,但仍比 Go 短。