Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# PrimeKit Challenges

[![Chat with Repo](https://badge.forgithub.com/shortthirdman-org/PrimeKit-Challenges?badge=chat)](https://uithub.com/shortthirdman-org/PrimeKit-Challenges)
[![LLM Context](https://badge.forgithub.com/shortthirdman-org/PrimeKit-Challenges?badge=context)](https://uithub.com/shortthirdman-org/PrimeKit-Challenges)

PrimeKit Coding Challenges

> A collection of Java-based solutions designed to tackle typical coding problems asked in software development interviews.
Expand Down
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,10 @@
<rules>
<banDuplicatePomDependencyVersions />
<requireMavenVersion>
<version>[3.8.6,4.0.0)</version>
<version>[3.8.8,4.0.0)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[15,23)</version>
<version>[15,26)</version>
</requireJavaVersion>
</rules>
</configuration>
Expand Down Expand Up @@ -274,6 +274,7 @@
<version>3.5.0</version>
<configuration>
<threadCount>10</threadCount>
<trimStackTrace>true</trimStackTrace>
<printSummary>true</printSummary>
<reportFormat>plain</reportFormat>
<testFailureIgnore>true</testFailureIgnore>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.shortthirdman.primekit.coderbyte;

import com.shortthirdman.primekit.core.agents.Action;
import com.shortthirdman.primekit.core.agents.Plan;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.PriorityQueue;

/**
* A planner that uses the A* search algorithm to find an optimal sequence of actions
* (a Plan) to transition from an initial state to a desired goal state.
* @author shortthirdman
* @since 1.2.0
*/
public class GoalOrientedActionPlanner {

/**
* Calculates the optimal plan to reach the goal state from the initial state
* using the provided set of available actions.
* <p>
* This method implements the A* search algorithm, exploring states based on
* accumulated cost (g-score) and a heuristic estimating the remaining distance
* to the goal (f-score).
*
* @param initialState the starting state map containing key-value condition pairs
* @param goal the target state map containing required key-value condition pairs
* @param availableActions the list of actions the planner can utilize
* @return an {@link Optional} containing the optimal {@link Plan} if a valid sequence
* is found, or an empty {@link Optional} if the goal is unreachable
*/
public Optional<Plan> plan(
Map<String, Object> initialState,
Map<String, Object> goal,
List<Action> availableActions) {
var open = new PriorityQueue<>(Comparator.comparingDouble(Node::f));
var closed = new HashSet<Map<String, Object>>();

open.add(new Node(initialState, List.of(), 0, heuristic(initialState, goal)));

while (!open.isEmpty()) {
Node current = open.poll();

if (satisfies(current.state(), goal)) {
return Optional.of(new Plan(current.actions(), current.g()));
}

if (!closed.add(current.state())) continue;

for (Action action : availableActions) {
if (!canApply(action, current.state())) continue;

Map<String, Object> next = apply(action, current.state());
List<Action> chain = new ArrayList<>(current.actions());
chain.add(action);

double g = current.g() + action.cost();
double f = g + heuristic(next, goal);

open.add(new Node(next, chain, g, f));
}
}

return Optional.empty();
}

/**
* Evaluates whether an action can be performed in the given state by verifying
* that all preconditions of the action are met.
*
* @param action the action to evaluate
* @param state the current state to check against
* @return {@code true} if all preconditions are satisfied, {@code false} otherwise
*/
private boolean canApply(Action action, Map<String, Object> state) {
return action.preconditions().entrySet().stream()
.allMatch(e -> Objects.equals(state.get(e.getKey()), e.getValue()));
}

/**
* Applies the effects of an action to a given state, generating a new resulting state.
* <p>
* This method does not mutate the original state; instead, it returns a newly allocated
* map containing the merged state and effects.
*
* @param action the action whose effects are to be applied
* @param state the initial state before the action is executed
* @return a new state map reflecting the applied effects
*/
private Map<String, Object> apply(Action action, Map<String, Object> state) {
Map<String, Object> result = new HashMap<>(state);
result.putAll(action.effects());
return result;
}

/**
* Checks if the given state satisfies all conditions defined in the goal.
*
* @param state the current state to evaluate
* @param goal the target conditions that must be met
* @return {@code true} if the state contains all goal conditions with matching values,
* {@code false} otherwise
*/
private boolean satisfies(Map<String, Object> state, Map<String, Object> goal) {
return goal.entrySet().stream()
.allMatch(e -> Objects.equals(state.get(e.getKey()), e.getValue()));
}

/**
* Calculates a heuristic value estimating the cost to reach the goal from the current state.
* <p>
* The current implementation uses the number of unsatisfied goal conditions as the heuristic.
* This acts as an admissible heuristic assuming each action costs at least 1.0 and satisfies
* at most one goal condition.
*
* @param state the current state
* @param goal the target state
* @return the count of goal conditions that differ from the current state
*/
private double heuristic(Map<String, Object> state, Map<String, Object> goal) {
return goal.entrySet().stream()
.filter(e -> !Objects.equals(state.get(e.getKey()), e.getValue()))
.count();
}

/**
* Represents a discrete node in the A* search tree.
*
* @param state the current world state at this node
* @param actions the sequence of actions taken to reach this state
* @param g the exact cumulative cost from the initial state to this node (g-score)
* @param f the estimated total cost from the initial state to the goal through this node (f-score = g + heuristic)
*/
private record Node(
Map<String, Object> state,
List<Action> actions,
double g,
double f
) {}
}
17 changes: 17 additions & 0 deletions src/main/java/com/shortthirdman/primekit/core/agents/Action.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.shortthirdman.primekit.core.agents;

import java.util.Map;

/**
* Represents an action that can be executed to transition between states.
*
* @param name the unique identifier or descriptive name of the action
* @param preconditions the state requirements that must be met before this action can be executed
* @param effects the state changes resulting from the execution of this action
* @param cost the numerical cost associated with performing this action
*/
public record Action(String name,
Map<String, Object> preconditions,
Map<String, Object> effects,
double cost) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.shortthirdman.primekit.core.agents;

/**
* @param serviceDegraded whether service is degraded
* @param recentDeployment whether deployment is recent
* @param serviceHealthy whether service is healthy
* @param oncallPaged whether service is on-call paged
* @param restartAttempted whether service has been attempted restart
* @param severity the level of {@link Severity} of the incident
*/
public record IncidentWorldState(boolean serviceDegraded,
boolean recentDeployment,
boolean serviceHealthy,
boolean oncallPaged,
boolean restartAttempted,
Severity severity) {

/**
* Initialize incident with whether service is healthy
* @param healthy flag to set if service is healthy or not
* @return the initialized {@link IncidentWorldState}
*/
public IncidentWorldState withServiceHealthy(boolean healthy) {
return new IncidentWorldState(serviceDegraded, recentDeployment, healthy,
oncallPaged, restartAttempted, severity);
}
}
13 changes: 13 additions & 0 deletions src/main/java/com/shortthirdman/primekit/core/agents/Plan.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.shortthirdman.primekit.core.agents;

import java.util.List;

/**
* Represents a calculated sequence of actions to achieve a specific goal,
* along with the total accumulated cost.
*
* @param actions the ordered list of actions to be executed
* @param totalCost the sum of the costs of all actions in the plan
*/
public record Plan(List<Action> actions, double totalCost) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.shortthirdman.primekit.core.agents;

public enum Severity {

HIGH,
MEDIUM,
LOW,
NONE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.shortthirdman.primekit.coderbyte;

import com.shortthirdman.primekit.core.agents.Action;
import com.shortthirdman.primekit.core.agents.Plan;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

class GoalOrientedActionPlannerTest {

private static GoalOrientedActionPlanner planner;
private static List<Action> actions;
private static Map<String, Object> goal;
private static Map<String, Object> initialState;

@BeforeAll
static void setUp() {
planner = new GoalOrientedActionPlanner();
initialState = Map.of(
"service.degraded", true,
"deployment.recent", true,
"service.healthy", false,
"oncall.paged", false
);

goal = Map.of("service.healthy", true);

actions = List.of(
new Action("restartService",
Map.of("service.degraded", true),
Map.of("service.degraded", false, "service.healthy", true),
1.0),

new Action("rollbackDeployment",
Map.of("service.degraded", true, "deployment.recent", true),
Map.of("service.degraded", false, "service.healthy", true,
"deployment.recent", false),
5.0),

new Action("pageOncall",
Map.of("service.degraded", true),
Map.of("oncall.paged", true),
10.0)
);
}

@AfterAll
static void tearDown() {
planner = null;
goal = Map.of();
initialState = Map.of();
actions = List.of();
}

@Test
void plan() {
Plan actualPlan = planner.plan(initialState, goal, actions).orElseThrow();

var expectedAction = new Action("restartService",
Map.of("service.degraded", true),
Map.of("service.degraded", false, "service.healthy", true),
1.0);
Plan expectedPlan = new Plan(List.of(expectedAction), 1.0);

Assertions.assertThat(actualPlan).isNotNull();
Assertions.assertThat(actualPlan).isEqualTo(expectedPlan);
Assertions.assertThat(actualPlan).hasNoNullFieldsOrProperties();
}
}
Loading