Design and implement an interface for
submitting and managing workflows. A workflow consists of one or more tasks and can run either
sequentially or
in parallel. The system must also support
passing data between workflows, where the output of one workflow becomes the input of another connected workflow. Each task works only on
List<String>: it takes a list of strings as input, applies its configured simple string-list operations in order, and returns another
List<String> as output.
Task Definition Format
Available tasks are provided to the constructor as task-definition strings. Each task definition uses the format:
taskId-functionality1,functionality2,...
Example:
validate-removeEmptyStrings,trimEachAndRemoveEmpty refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically
Required Class
WorkflowManager(List<String> availableTaskDefinitions)
- Each entry in
availableTaskDefinitions defines one task id and its ordered functionalities.
- All constructor inputs are guaranteed valid.
- The following is a valid example:
WorkflowManager(List.of( "validate-removeEmptyStrings,trimEachAndRemoveEmpty", "clean-lowercase,sortByLength", "refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically", "email-uppercase", "sms-reverseList" ))
Supported Functionalities
removeEmptyStrings
- Removes strings that are exactly empty or contain only spaces.
List.of("cat", "", "dog", " ") -> List.of("cat", "dog")
trimEachAndRemoveEmpty
- Trims every string, then removes strings that become empty after trimming.
List.of(" cat ", " ", "dog") -> List.of("cat", "dog")
removeDuplicateStringsIgnoreCaseKeepFirst
- Keeps only the first occurrence of each string ignoring case.
List.of("cat", "dog", "Cat", "DOG") -> List.of("cat", "dog")
sortStringsLexicographically
- Sorts the list in lexicographical order.
List.of("banana", "apple", "cat") -> List.of("apple", "banana", "cat")
sortByLength
- Sorts strings by length in ascending order. If lengths are equal, keep their existing relative order.
List.of("elephant", "cat", "dog") -> List.of("cat", "dog", "elephant")
reverseList
- Reverses the order of the list.
List.of("a", "b", "c") -> List.of("c", "b", "a")
lowercase
- Converts every string in the list to lowercase.
List.of("Cat", "DOG") -> List.of("cat", "dog")
uppercase
- Converts every string in the list to uppercase.
List.of("Cat", "dog") -> List.of("CAT", "DOG")
Execution Rules
- Inside one task, functionalities are applied in the exact order given in its task definition.
- In a sequential workflow, the output of one task becomes the input of the next task.
- In a parallel workflow, every task receives the same original input list of that workflow.
- For a parallel workflow, task outputs are merged by concatenating the output lists in the same task order used in the workflow definition.
- If workflow
A is connected to workflow B, then the final output of A becomes the input of B.
- Each workflow may have at most one direct downstream and at most one direct upstream workflow.
- If a workflow chain is executed,
executeWorkflow returns the final output of the last workflow in that chain.
Top Methods
boolean submitWorkflow(String workflowId, List<String> taskIds, boolean runInParallel)
- Creates a new workflow with the given task ids and execution mode.
workflowId must be unique.
- Each task id in
taskIds must exist in availableTaskDefinitions.
1 ≤ taskIds.size()
- Return
true if the workflow is created successfully, otherwise return false.
boolean connectWorkflows(String sourceWorkflowId, String targetWorkflowId)
- Connects one workflow to another.
- The final output of
sourceWorkflowId becomes the input of targetWorkflowId.
- Both workflows must already exist.
sourceWorkflowId and targetWorkflowId must be different.
- The connection must not create a cycle.
sourceWorkflowId must not already have a direct downstream workflow.
targetWorkflowId must not already have a direct upstream workflow.
- Therefore, workflows can form only a linear chain. Branching and merging are not supported.
- Return
true if the connection is added successfully, otherwise return false.
List<String> executeWorkflow(String workflowId, List<String> inputData)
- Executes the given workflow using the provided input list.
workflowId must exist.
- Every task in the workflow must apply its internal functionalities in order.
- If the workflow is sequential, task outputs flow one by one through the workflow.
- If the workflow is parallel, all tasks use the same input list and their output lists are concatenated in task order.
- If a downstream workflow is connected, its execution starts automatically with the current workflow's final output.
- Return the final
List<String> produced by the last workflow in the chain.
String getWorkflowDetails(String workflowId)
- Returns a string representation of the workflow configuration.
- The response must include workflow id, execution mode, task order, and directly connected downstream workflow if present.
- If the workflow does not exist, return
"WORKFLOW_NOT_FOUND".
- The returned string must exactly follow this format:
workflowId=<workflowId>, mode=<SEQUENTIAL|PARALLEL>, tasks=<task1->task2->...>, downstream=<downstreamWorkflowId or empty string>
Output Format Example
- workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream=WF-NOTIFY
- workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream
Constraints
1 ≤ availableTaskDefinitions.size() ≤ 10^4
1 ≤ taskIds.size() ≤ 50 for one workflow
0 ≤ inputData.size() ≤ 10^4
- Each task id and workflow id is a non-empty string.
- Task ids are unique across
availableTaskDefinitions.
- Each task definition contains at least one functionality.
- A workflow cannot be submitted more than once with the same id.
- Self-connections are invalid.
- Cyclic workflow connections are invalid.
- All operations should be handled in memory.
Example 1
WorkflowManager(
availableTaskDefinitions=List.of(
"validate-removeEmptyStrings,trimEachAndRemoveEmpty",
"clean-lowercase,sortByLength",
"refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically",
"email-uppercase",
"sms-reverseList"
)
)
submitWorkflow(workflowId="WF-MAIN", taskIds=List.of("validate", "clean", "refine"), runInParallel=false) -> true
getWorkflowDetails(workflowId="WF-MAIN") -> "workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream="
executeWorkflow(workflowId="WF-MAIN", inputData=List.of(" Cat ", "", "dog", "cat", " ")) -> List.of("cat", "dog")
Explanation:
- validate: first remove exact empty strings / space-only strings, then trim each remaining string and remove any that become empty. So List.of(" Cat ", "", "dog", "cat", " ") -> List.of("Cat", "dog", "cat").
- clean: lowercase then stable sort by length. So List.of("Cat", "dog", "cat") -> List.of("cat", "dog", "cat").
- refine: remove duplicates ignoring case while keeping the first occurrence, then sort lexicographically. So List.of("cat", "dog", "cat") -> List.of("cat", "dog").
Example 2
WorkflowManager(
availableTaskDefinitions=List.of(
"validate-removeEmptyStrings,trimEachAndRemoveEmpty",
"clean-lowercase,sortByLength",
"refine-removeDuplicateStringsIgnoreCaseKeepFirst,sortStringsLexicographically",
"email-uppercase",
"sms-reverseList"
)
)
submitWorkflow(workflowId="WF-MAIN", taskIds=List.of("validate", "clean", "refine"), runInParallel=false) -> true
submitWorkflow(workflowId="WF-NOTIFY", taskIds=List.of("email", "sms"), runInParallel=true) -> true
connectWorkflows(sourceWorkflowId="WF-MAIN", targetWorkflowId="WF-NOTIFY") -> true
getWorkflowDetails(workflowId="WF-MAIN") -> "workflowId=WF-MAIN, mode=SEQUENTIAL, tasks=validate->clean->refine, downstream=WF-NOTIFY"
getWorkflowDetails(workflowId="WF-NOTIFY") -> "workflowId=WF-NOTIFY, mode=PARALLEL, tasks=email->sms, downstream="
executeWorkflow(workflowId="WF-MAIN", inputData=List.of(" Cat ", "", "dog", "cat", " ")) -> List.of("CAT", "DOG", "dog", "cat")
Explanation:
- WF-MAIN executes first and produces List.of("cat", "dog").
- Because WF-MAIN is connected to WF-NOTIFY, that output becomes the input of WF-NOTIFY.
- WF-NOTIFY is a parallel workflow, so both tasks receive the same input List.of("cat", "dog").
- email: uppercase, so List.of("cat", "dog") -> List.of("CAT", "DOG").
- sms: reverse list, so List.of("cat", "dog") -> List.of("dog", "cat").
- The parallel outputs are concatenated in task order: first email, then sms. Final result: List.of("CAT", "DOG", "dog", "cat").