Episode 11: Restricting the Dataset
Julien Truffaut
2 December 2025
In the previous episode, I implemented a nested loop exploring all possible gear combinations. While it technically works, it would take an astronomical amount of time to complete. Let’s start by looking at the scale of the problem.
My dataset currently contains:
- 323 amulets
- 352 belts
- 364 boots
- 300 cloaks
- 367 hats
- 370 rings
- 145 shields
- 696 weapons
If we try every possible build, that’s 6 × 10²² combinations
To put this in perspective:
Even at 1,000,000 builds per second, the search would take over 2 billion years. Safe to say I won’t be playing Dofus by then.
Before exploring smarter algorithms, we should drastically shrink the dataset. A very effective first step: remove all gears outside the player’s level range (e.g. for a level 150 build, ignore all items above 150). This alone can cut the search space by several orders of magnitude.
Gathering User Requirements
Let’s add CLI parameters to set a minimum and maximum gear level. As seen in the dofus_db crate, we can easily do this with Clap:
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(long = "min-level")]
min_level: Option<u32>,
#[arg(long = "max-level")]
max_level: Option<u32>,
} And in main.rs:
fn main() -> Result<()> {
let args = Args::parse();
let mut amulets = import_gears(&[GearType::Amulet])?;
amulets.retain(|g| is_level_in_range(g.level, args.min_level, args.max_level));
// repeat for other gear types...
} Using this helper function:
fn is_level_in_range(
level: u32,
min: Option<u32>,
max: Option<u32>
) -> bool {
min.map_or(true, |m| level >= m) &&
max.map_or(true, |m| level <= m)
} Then, we can call the CLI with min and max level arguments:
cargo run -p dofus-opti-build --
--min-level 190
--max-level 200 Even this small improvement has a huge impact: The number of builds between levels 190–200 is around 2 × 10¹³. At one million builds per second, that’s “only” 7.5 months — still too long, but massively better than 2 billion years.
Adding Validation
We should validate CLI input because mistakes here can easily lead to empty or nonsensical results. Let’s implement two simple rules:
- Levels must be between 1 and 200
- min_level ≤ max_level
The first rule is easily supported by Clap:
#[arg(long = "min-level", value_parser = clap::value_parser!(u32).range(1..=200))]
min_level: Option<u32>, Incorrect input yields:
cargo run -p dofus-opti-build --
--min-level 220
error: invalid value '220' for '--min-level <MIN_LEVEL>': 220 is not in 1..=200 Great!
The second rule requires custom validation because we need to validate two fields together:
use clap::{CommandFactory, Parser};
impl Args {
fn validate(&self) -> Result<(), clap::Error> {
if let (Some(min), Some(max)) = (self.min_level, self.max_level) {
if min > max {
return Err(Args::command().error(
clap::error::ErrorKind::ValueValidation,
"min-level must be ≤ max-level",
));
}
}
Ok(())
}
} Now invalid ranges report a clean error:
cargo run -p dofus-opti-build --
--min-level 180
--max-level 150
Error: error: min-level must be ≤ max-level Alternatively, we could have defined a struct with a min and max level and define the validation there:
struct LevelRange {
min: Option<u32>,
max: Option<u32>,
}
impl LevelRange {
fn new(min: Option<u32>, max: Option<u32>) -> Result<LevelRange, String> {
// validation logic
}
}
impl FromStr for LevelRange {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// parsing logic
}
}
struct Args {
#[arg(long = "level")]
level_range: Option<LevelRange>,
} I prefer this approach as it makes easier to test the parsing and validation logic and it allows some cool syntax:
cargo run -p dofus-opti-build --
--level 150..180
cargo run -p dofus-opti-build --
--level 200 You can see my implementation here.
Build Targets
Different gears support different playstyles: some boost elemental damage, others improve tankiness, AP/MP mechanics, and so on. If a player is looking for a strength build, we can safely eliminate all items that don’t meaningfully contribute to it.
Let’s start by allowing requirements such as:
cargo run -p dofus-opti-build --
--require "Vitality >= 3000"
--require "Strength >= 500" First, let’s define the model:
struct MinRequirement {
id: RequirementId,
desired_value: i32,
}
enum RequirementId {
Strength,
Vitality,
} We can define how to parse a MinRequirement with FromStr:
impl FromStr for MinRequirement {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<_> = s.split_whitespace().collect();
if parts.len() != 3 {
return Err(format!("Invalid requirement: {}", s));
}
let id: RequirementId = parts[0].parse()?;
if parts[1] != ">=" {
return Err(format!("Unsupported operator: {}", parts[1]));
}
let desired_value = parts[2]
.parse()
.map_err(|_| "Invalid number".to_string())?;
Ok(MinRequirement { id, desired_value })
}
} And in the CLI:
#[arg(short, long = "require", num_args(1..), action = clap::ArgAction::Append)]
requirements: Vec<MinRequirement>, Now, running:
cargo run -p dofus-opti-build --
--require "Vitality >= 3000"
--require "Strength >= 800" produces:
[
MinRequirement { id: Vitality, desired_value: 3000 },
MinRequirement { id: Strength, desired_value: 800 }
] Perfect!
To actually use these requirements, we’ll need to compute the combined effects of all gears in a build — but that’s for the next episode.
Conclusion
I continue to be impressed by how ergonomic and powerful Clap is. Writing a robust CLI is very straightforward.
Today, we saw that the naïve search algorithm is hopeless when applied to the full dataset — but if we aggressively prune it (level filtering, build archetypes, etc.), the search space becomes drastically smaller.
In the next episodes, we’ll explore additional ways to reduce the dataset and begin experimenting with more promising search strategies.