1
\$\begingroup\$

I have a Vec<String> of all available versions of a particular piece of software (Godot), named VERSIONS in code below, where each version can be either "major.minor" or "major.minor.patch". These are manually scraped from tuxfamily.

I also have a Vec<&str> of versions that are considered "supported", named STABLES in code below, where each version is in form of "major.minor". These are fetched from a file on GitHub.

The purpose of this snippet is to filter VERSIONS to only include latest versions for each minor release. For example, if i have VERSIONS = [2.1, 3.5, 3.5.1, 3.6] and STABLES = [3.5, 3.6], i want to disregard 2.1 (as it is not in STABLES) and 3.5 (as the latest 3.5 release is 3.5.1), ending up with VERSIONS = [3.5.1, 3.6].

I tried hard to figure out a clean and ideally fast way to do the above, but ended up with a solution that is explicit about everything it does, possibly sacrificing readability (opinionated) and speed (i did not measure it, but i think it could be faster).

// Dependencies: `versions` (for the `Versioning` struct), `ahash` (for `AHashMap`).

// A mapping of "major.minor" to a struct representing "major.minor[.patch]"
let mut latest_versions: AHashMap<&str, Versioning> = AHashMap::new();

// For each Godot version fetched from tuxfamily
for godot_version in VERSIONS {
    for stable_godot_version in &STABLES {
        // We check if it is one of the versions marked as stable in our
        // custom data set
        if godot_version.starts_with(stable_godot_version) {
            // If yes, we check if we already have a version set as
            // "latest" in our hashmap.
            let latest: Option<&Versioning> = latest_versions.get(stable_godot_version);

            match latest {
                Some(latest) => {
                    // If yes, we compare our currently-iterated-over version
                    // with the one in the hashmap, and if it is greater, we
                    // set it as the latest
                    let godot_version = Versioning::new(godot_version.as_str()).unwrap();

                    if &godot_version > latest {
                        latest_versions.insert(stable_godot_version, godot_version);
                    }
                },
                // If not, we simply set it as the latest.
                None => { latest_versions.insert(stable_godot_version, Versioning::new(godot_version.as_str()).unwrap()); },
            }
        }
    }
}

VERSIONS = latest_versions.values().into_iter().map(|v| v.to_string()).collect();

Notably, version comparison is done by overloaded operators on versions::Versioning. I still am not very comfortable with this code in my codebase, so i wonder if there's a way to improve/refactor it?

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I would start by parsing all of the version strings into some sort of Version struct. I might define it something like:

struct Version {
    major: u32,
    minor: u32,
    patch: Option<u32>
}

You already do use versions::Versioning which you could parse into, but looking at that crate it looks to be trying to support any kind of version format and consequently lacks functionality which would be more useful for a constrained version.

for godot_version in VERSIONS {
    for stable_godot_version in &STABLES {
        // We check if it is one of the versions marked as stable in our
        // custom data set
        if godot_version.starts_with(stable_godot_version) {

This is going to be somewhat inefficient because you loop over all the STABLES looking for a match. It would be more efficient if you created a set of stable versions and then checked if the corresponding major.minor for a particular godot version was in there.

What I'd do is sort all of the versions. (Actually, if you are scraping it, maybe they already come sorted). Then I'd group_by from itertools to group versions with the same major.minor version. Then you could filter by stable versions and then map to return the last item of the group.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.