Blackboard
The Blackboard pattern provides a central repository (the Blackboard) of information that multiple, independent Knowledge Sources can access, modify, and react to. A Controller selects which Knowledge Sources are relevant at any given time based on the current state of the Blackboard, and applies their expertise to solve a complex problem. This allows for a flexible and extensible system when the problem-solving strategy isn’t known in advance or changes frequently.
It’s particularly useful in domains like artificial intelligence, speech recognition, and expert systems where a variety of specialized components need to collaborate to achieve a single goal. The Blackboard decouples the problem-solving logic from the individual knowledge sources, making it easier to add, remove, or modify components without disrupting the entire system.
Usage
The Blackboard pattern is commonly used in:
- AI and Expert Systems: For tasks like image recognition, natural language processing, and automated reasoning, where different sources of knowledge (e.g., edge detection, grammar rules, inference engines) contribute to a final solution.
- Speech Recognition Systems: Different modules for acoustic modeling, phoneme recognition, and language processing contribute to recognizing spoken words.
- Complex Data Processing Pipelines: Where multiple stages of data transformation and analysis need to be applied reactively depending on the data’s contents.
- Robotics: Coordinating actions of different robot components based on sensor input and environmental conditions.
- Game AI: Managing the behavior of multiple game entities, allowing them to react and interact with each other in complex ways.
Examples
- GraalVM’s Truffle Framework: Truffle uses a Blackboard pattern to represent the abstract syntax tree (AST) of code being executed. Different language implementations (knowledge sources) contribute to analyzing and optimizing this AST, and a central interpreter (controller) manages the execution. The AST effectively is the Blackboard.
- OpenCV: OpenCV’s image processing pipeline utilizes a Blackboard approach, although not explicitly named as such. An image (the Blackboard) is passed through a series of filters and algorithms (Knowledge Sources). The output of one filter becomes the input for the next, with a central process orchestrating the pipeline and determining which algorithms to apply based on the image data. For example, object detection might involve edge detection, then shape analysis, then feature matching – each a knowledge source contributing to the overall “understanding” of the image.
Specimens
15 implementationsThe Blackboard pattern is a computational architecture for solving problems that involve multiple, independent knowledge sources. A shared data structure, the “blackboard,” holds the problem state. Knowledge sources (independent modules) observe the blackboard and, when their conditions are met, execute to modify the state. This allows for flexible problem-solving without tight coupling between components.
This Dart implementation uses a simple Blackboard class to hold the problem state (a String in this case). KnowledgeSource classes have a condition function to check if they can act, and an action function to modify the blackboard. A BlackboardSystem orchestrates the process, repeatedly applying applicable knowledge sources until a solution is reached. The use of functions as arguments and the separation of condition/action logic are idiomatic for Dart’s functional and OOP capabilities, providing a clean and testable structure.
// blackboard.dart
class Blackboard {
String state = '';
}
abstract class KnowledgeSource {
bool condition(Blackboard blackboard);
void action(Blackboard blackboard);
}
class Initializer implements KnowledgeSource {
@override
bool condition(Blackboard blackboard) => blackboard.state.isEmpty;
@override
void action(Blackboard blackboard) {
blackboard.state = 'initial data';
print('Initializer applied: blackboard.state = ${blackboard.state}');
}
}
class Processor1 implements KnowledgeSource {
@override
bool condition(Blackboard blackboard) => blackboard.state == 'initial data';
@override
void action(Blackboard blackboard) {
blackboard.state = 'processed by 1';
print('Processor 1 applied: blackboard.state = ${blackboard.state}');
}
}
class Processor2 implements KnowledgeSource {
@override
bool condition(Blackboard blackboard) => blackboard.state == 'processed by 1';
@override
void action(Blackboard blackboard) {
blackboard.state = 'processed by 2';
print('Processor 2 applied: blackboard.state = ${blackboard.state}');
}
}
class BlackboardSystem {
final Blackboard blackboard;
final List<KnowledgeSource> knowledgeSources;
BlackboardSystem(this.blackboard, this.knowledgeSources);
void run() {
while (true) {
bool applied = false;
for (var source in knowledgeSources) {
if (source.condition(blackboard)) {
source.action(blackboard);
applied = true;
break; // Apply only one source per cycle
}
}
if (!applied) {
print('No sources applied, system halted.');
break;
}
if (blackboard.state == 'processed by 2') {
print('Final State: ${blackboard.state}');
break;
}
}
}
}
void main() {
var blackboard = Blackboard();
var system = BlackboardSystem(blackboard, [
Initializer(),
Processor1(),
Processor2(),
]);
system.run();
}
The Blackboard pattern is a computational architecture for solving problems with no pre-defined flow of control. Multiple independent knowledge sources (in this case, functions) observe a shared data structure (the “blackboard”) and contribute to solving a problem based on the data’s current state. This avoids tight coupling and allows for flexible, reactive problem-solving.
The Scala code defines a Blackboard class managing the shared data and triggering updates. KnowledgeSource traits declare functions that react to blackboard changes. ProblemSolver orchestrates the process. The implementation uses Scala’s functional programming strengths (traits, immutable data) and leverages type safety for data manipulation on the blackboard. The use of Observable simplifies the event-driven nature of the pattern, making it easily scalable and maintainable.
import scala.collection.mutable
import rx.lang.scala.Observable
trait BlackboardData {
def currentStatus: String
}
trait KnowledgeSource {
def react(data: BlackboardData): Unit
}
class Blackboard(initialData: BlackboardData) {
private val observers = mutable.List[KnowledgeSource]()
private var data: BlackboardData = initialData
def register(ks: KnowledgeSource): Unit = observers += ks
def unregister(ks: KnowledgeSource): Unit = observers -= ks
def updateData(newData: BlackboardData): Unit = {
data = newData
observers.foreach(_.react(data))
}
def getData: BlackboardData = data
}
class ProblemSolver(blackboard: Blackboard, knowledgeSources: KnowledgeSource*) {
knowledgeSources.foreach(blackboard.register)
def solve(): Unit = {
// Initial state triggers reactions
blackboard.updateData(blackboard.getData)
}
def shutdown(): Unit = {
knowledgeSources.foreach(blackboard.unregister)
}
}
case class MyData(currentStatus: String) extends BlackboardData
object Example extends App {
val initialData = MyData("Initial State")
val blackboard = new Blackboard(initialData)
val source1 = new KnowledgeSource {
override def react(data: BlackboardData): Unit = {
if (data.currentStatus == "Initial State") {
println("Source 1: Reacting to initial state...")
blackboard.updateData(MyData("State A"))
}
}
}
val source2 = new KnowledgeSource {
override def react(data: BlackboardData): Unit = {
if (data.currentStatus == "State A") {
println("Source 2: Reacting to State A...")
blackboard.updateData(MyData("Final State"))
}
}
}
val solver = new ProblemSolver(blackboard, source1, source2)
solver.solve()
solver.shutdown()
}
The Blackboard pattern is a centralized data repository (the “blackboard”) coupled with a set of independent, specialized agents that operate on that data. Agents react to changes on the blackboard, contributing knowledge or transforming the data until a solution is reached. This decouples the data and the processing logic, making the system flexible and extensible.
This PHP implementation simulates a simple problem-solving scenario: recognizing patterns in a data stream. The Blackboard class holds the data. Agent is an abstract class defining the interface for agents. Concrete agents (DataIngestAgent, PatternRecognitionAgent) observe the blackboard for updates (simulated with a trigger) and perform their tasks. PatternRecognitionAgent determines if a defined pattern exists in the ingested data and posts its findings back to the blackboard. The structure uses interfaces and abstract classes typical of PHP OOP.
<?php
// Blackboard Pattern in PHP
/**
* Represents the Blackboard - a central data repository.
*/
class Blackboard
{
private $data = [];
private $observers = [];
public function getData(): array
{
return $this->data;
}
public function setData(array $newData): void
{
$this->data = $newData;
$this->notifyObservers();
}
public function addObserver(Observer $observer): void
{
$this->observers[] = $observer;
}
private function notifyObservers(): void
{
foreach ($this->observers as $observer) {
$observer->update($this->data);
}
}
}
/**
* Defines the interface for Agents.
*/
interface Observer
{
public function update(array $data): void;
}
/**
* Base class for Agents.
*/
abstract class Agent implements Observer
{
protected Blackboard $blackboard;
public function __construct(Blackboard $blackboard)
{
$this->blackboard = $blackboard;
$this->blackboard->addObserver($this);
}
}
/**
* Ingests data into the Blackboard.
*/
class DataIngestAgent extends Agent
{
private $dataToIngest;
public function __construct(Blackboard $blackboard, array $dataToIngest)
{
parent::__construct($blackboard);
$this->dataToIngest = $dataToIngest;
}
public function update(array $data): void
{
// In this simplified example, the agent only runs once.
$this->blackboard->setData($this->dataToIngest);
}
}
/**
* Recognizes a pattern in the Blackboard data.
*/
class PatternRecognitionAgent extends Agent
{
private $patternToRecognize;
public function __construct(Blackboard $blackboard, array $patternToRecognize)
{
parent::__construct($blackboard);
$this->patternToRecognize = $patternToRecognize;
}
public function update(array $data): void
{
if ($this->patternExists($data, $this->patternToRecognize)) {
$this->blackboard->setData(['pattern_found' => true, 'data' => $data]);
} else {
$this->blackboard->setData(['pattern_found' => false, 'data' => $data]);
}
}
private function patternExists(array $data, array $pattern): bool
{
// Simple pattern matching example
return array_intersect($pattern, $data) === $pattern;
}
}
// Example Usage
$blackboard = new Blackboard();
$data = [1, 2, 3, 4, 5];
$pattern = [2, 4];
$ingestAgent = new DataIngestAgent($blackboard, $data);
$recognitionAgent = new PatternRecognitionAgent($blackboard, $pattern);
// Simulate data ingestion (agent runs on construction in this simplified example)
// The pattern recognition agent then reacts.
$result = $blackboard->getData();
print_r($result);
?>
The Blackboard pattern is a computational architecture for solving problems that can’t be solved with a deterministic algorithm. It consists of several knowledge sources (independent modules) that contribute to solving a problem by observing a shared data structure – the “blackboard”. These sources react to changes on the blackboard, adding or modifying information.
This Ruby implementation simulates a simple Blackboard for recognizing patterns in data. Blackboard holds the current data. KnowledgeSource classes define rules to modify the blackboard. DataProcessor adds initial data and triggers the process. The process method in DataProcessor iterates, applying knowledge sources until a solution is found or a maximum iteration count is reached. This approach is idiomatic Ruby due to its emphasis on modularity and using objects to encapsulate behavior. The use of a hash for the blackboard aligns with Ruby’s flexible data structures.
# blackboard.rb
class Blackboard
attr_accessor :data
def initialize
@data = {}
end
end
class KnowledgeSource
def initialize(blackboard)
@blackboard = blackboard
end
def apply
raise NotImplementedError, "Subclasses must implement the 'apply' method"
end
end
class PatternRecognizer < KnowledgeSource
def initialize(blackboard)
super
end
def apply
if @blackboard.data[:numbers] && @blackboard.data[:numbers].is_a?(Array)
if @blackboard.data[:numbers].include?(1) && @blackboard.data[:numbers].include?(2) && @blackboard.data[:numbers].include?(3)
@blackboard.data[:pattern_found] = "Sequence 1, 2, 3 detected!"
return true # Signal solution found
end
end
false
end
end
class NumberExtractor < KnowledgeSource
def initialize(blackboard)
super
end
def apply
input_string = @blackboard.data[:input]
if input_string
numbers = input_string.scan(/\d+/).map(&:to_i)
@blackboard.data[:numbers] = numbers
return true
end
false
end
end
class DataProcessor
def initialize(blackboard)
@blackboard = blackboard
@knowledge_sources = []
end
def add_knowledge_source(source)
@knowledge_sources << source
end
def process(input_data, max_iterations = 10)
@blackboard.data[:input] = input_data
iteration = 0
while iteration < max_iterations
changed = false
@knowledge_sources.each do |source|
if source.apply
changed = true
end
end
break if !changed
iteration += 1
end
@blackboard.data
end
end
# Example Usage
blackboard = Blackboard.new
processor = DataProcessor.new(blackboard)
processor.add_knowledge_source(NumberExtractor.new(blackboard))
processor.add_knowledge_source(PatternRecognizer.new(blackboard))
result = processor.process("This string contains the numbers 1, 2, and 3.")
puts result # Output: {:input=>"This string contains the numbers 1, 2, and 3.", :numbers=>[1, 2, 3], :pattern_found=>"Sequence 1, 2, 3 detected!"}
result2 = processor.process("The numbers are 4, 5, 6")
puts result2 # Output: {:input=>"The numbers are 4, 5, 6", :numbers=>[4, 5, 6]}
The Blackboard pattern is a computational architecture for solving problems that involve multiple, independent knowledge sources. A central “blackboard” data structure holds the problem state, and “knowledge sources” observe the blackboard, triggering actions when their conditions are met. This allows for flexible and reactive problem-solving without tight coupling between components.
This Swift implementation uses a Blackboard class to hold the problem data (a String in this example) and a protocol KnowledgeSource with a conditionMet(on: Blackboard) method. Concrete KnowledgeSources register with the blackboard and are notified when the data changes. They then decide if their expertise applies and modify the blackboard accordingly. This approach leverages Swift’s protocol-oriented programming and closures for event handling, fitting the language’s functional and flexible nature.
// Define the Blackboard data structure
class Blackboard {
var data: String = ""
var knowledgeSources: [KnowledgeSource] = []
func addKnowledgeSource(_ source: KnowledgeSource) {
knowledgeSources.append(source)
}
func setData(_ newData: String) {
data = newData
notifyKnowledgeSources()
}
private func notifyKnowledgeSources() {
knowledgeSources.forEach { $0.conditionMet(on: self) }
}
}
// Define the KnowledgeSource protocol
protocol KnowledgeSource {
func conditionMet(on blackboard: Blackboard)
}
// Example Knowledge Sources
class CapitalizeKnowledgeSource: KnowledgeSource {
func conditionMet(on blackboard: Blackboard) {
if !blackboard.data.isEmpty {
blackboard.data = blackboard.data.capitalized
}
}
}
class AddExclamationKnowledgeSource: KnowledgeSource {
func conditionMet(on blackboard: Blackboard) {
if !blackboard.data.isEmpty && !blackboard.data.last!.isExclamationPoint {
blackboard.data += "!"
}
}
}
// Usage
let blackboard = Blackboard()
let capitalizeSource = CapitalizeKnowledgeSource()
let exclamationSource = AddExclamationKnowledgeSource()
blackboard.addKnowledgeSource(capitalizeSource)
blackboard.addKnowledgeSource(exclamationSource)
blackboard.setData("hello world")
print(blackboard.data) // Output: Hello world!
blackboard.setData("swift is fun")
print(blackboard.data) // Output: Swift is fun!
The Blackboard pattern is a computational architecture for solving complex problems by allowing multiple knowledge sources (agents) to interact through a shared data repository (the blackboard). Agents independently examine the blackboard and contribute information when they detect relevant data. This promotes loose coupling and allows agents to be added or removed without impacting others.
This Kotlin example creates a simplified Blackboard system for recognizing “patterns” in strings. The Blackboard holds the data (input string) and knowledge (recognized patterns). Agents define condition (when to act) and action (what to contribute). The run function simulates the processing loop, where agents check and update the blackboard until a termination condition is met, or a maximum number of iterations is reached. Kotlin’s functional approach with lambda expressions cleanly defines the agent’s logic, and data classes promote immutability and clarity, fitting the language’s style.
data class Blackboard(var data: String, var knowledge: MutableList<String> = mutableListOf())
interface Agent {
val condition: (Blackboard) -> Boolean
val action: (Blackboard) -> Unit
}
fun runBlackboard(initialData: String, agents: List<Agent>, maxIterations: Int = 10): Blackboard {
val blackboard = Blackboard(initialData)
var iteration = 0
while (iteration < maxIterations && blackboard.knowledge.isEmpty()) {
for (agent in agents) {
if (agent.condition(blackboard)) {
agent.action(blackboard)
}
}
iteration++
}
return blackboard
}
fun main() {
val blackboard = runBlackboard("hello world") {
val agents = listOf(
object : Agent {
override val condition: (Blackboard) -> Boolean = { it.data.contains("hello") }
override val action: (Blackboard) -> Unit = { it.knowledge.add("Greeting detected") }
},
object : Agent {
override val condition: (Blackboard) -> Boolean = { it.data.contains("world") }
override val action: (Blackboard) -> Unit = { it.knowledge.add("World detected") }
}
)
runBlackboard("hello world", agents)
}
println("Data: ${blackboard.data}")
println("Knowledge: ${blackboard.knowledge}")
}
The Blackboard pattern is a computational problem-solving technique where multiple independent knowledge sources (agents) collaborate to solve a complex problem. A shared data structure, called the blackboard, holds the evolving problem state. Agents examine the blackboard, and if their knowledge applies, they modify it. This process continues until a solution is found.
The Rust example below uses channels for communication between agents and a Mutex to protect the blackboard data. Each agent is a thread that attempts to process the blackboard content. The blackboard itself is a Vec<String>, and agents look for specific prefixes indicating what data they are meant to handle. This approach leverages Rust’s ownership and concurrency features for a safe and efficient implementation, emphasizing immutability and message passing.
use std::sync::{Arc, Mutex};
use std::thread;
use std::sync::mpsc;
fn main() {
let blackboard = Arc::new(Mutex::new(vec!["Initial data".to_string()]));
let (tx, rx) = mpsc::channel();
// Agent 1: Adds a prefix to identify its data
let board_clone1 = Arc::clone(&blackboard);
let tx1 = tx.clone();
thread::spawn(move || {
loop {
let mut board = board_clone1.lock().unwrap();
if board.first().unwrap().starts_with("Initial") {
board.push("Agent1_Processed:".to_string() + board.first().unwrap());
tx1.send(()).unwrap();
}
drop(board); // Explicitly drop the lock
thread::sleep(std::time::Duration::from_millis(100));
}
});
// Agent 2: Appends data to the existing entry
let board_clone2 = Arc::clone(&blackboard);
let tx2 = tx.clone();
thread::spawn(move || {
loop {
let mut board = board_clone2.lock().unwrap();
if board.last().unwrap().starts_with("Agent1_Processed:") {
board.push("Agent2_Appended".to_string() + board.last().unwrap());
tx2.send(()).unwrap();
}
drop(board);
thread::sleep(std::time::Duration::from_millis(100));
}
});
// Main thread: Monitors for completion
let mut completion_count = 0;
while completion_count < 2 {
rx.recv().unwrap();
completion_count += 1;
}
println!("Blackboard contents: {:?}", blackboard.lock().unwrap());
}
The Blackboard pattern provides a shared memory (the “blackboard”) that different knowledge sources (independent components) can read and write to achieve a common goal. Each knowledge source monitors the blackboard for information it can use or modify, triggering its associated action when conditions are met. This promotes loose coupling and allows for dynamic modification of problem-solving behavior.
Here, we model a simple spell checker. The Blackboard holds the current state of the text. KnowledgeSource interface defines the behavior of components which examine and modify the blackboard. SpellCheckSource and CapitalizationSource represent distinct checks, reacting to changes on the blackboard. Go’s concurrency model using channels facilitates the asynchronous interaction between knowledge sources and the blackboard. The use of interfaces promotes flexibility and testability.
package main
import "fmt"
// Blackboard represents the shared data.
type Blackboard struct {
text string
changes chan string
}
// NewBlackboard creates a new Blackboard instance.
func NewBlackboard() *Blackboard {
return &Blackboard{
text: "",
changes: make(chan string),
}
}
// GetText retrieves the current text on the blackboard.
func (b *Blackboard) GetText() string {
return b.text
}
// SetText updates the text on the blackboard and signals changes.
func (b *Blackboard) SetText(text string) {
b.text = text
b.changes <- text // Signal that the text has changed
}
// KnowledgeSource defines the interface for components that work with the blackboard.
type KnowledgeSource interface {
ReactToChange(text string)
}
// SpellCheckSource checks for spelling errors. (Simplified example)
type SpellCheckSource struct {
dictionary map[string]bool
}
// NewSpellCheckSource creates a new SpellCheckSource.
func NewSpellCheckSource(dictionary map[string]bool) *SpellCheckSource {
return &SpellCheckSource{dictionary: dictionary}
}
// ReactToChange checks the given text for spelling errors.
func (s *SpellCheckSource) ReactToChange(text string) {
words := splitTextIntoWords(text)
for _, word := range words {
if !s.dictionary[word] {
fmt.Printf("Possible spelling error: %s\n", word)
}
}
}
// CapitalizationSource checks for capitalization issues. (Simplified example)
type CapitalizationSource struct {}
// NewCapitalizationSource creates a new CapitalizationSource
func NewCapitalizationSource() *CapitalizationSource {
return &CapitalizationSource{}
}
// ReactToChange checks if the first letter of each sentence is capitalized.
func (c *CapitalizationSource) ReactToChange(text string) {
sentences := splitTextIntoSentences(text)
for _, sentence := range sentences {
if len(sentence) > 0 && !isCapitalized(sentence[0]) {
fmt.Printf("Sentence should be capitalized: %s\n", sentence)
}
}
}
func splitTextIntoWords(text string) []string {
// Very simple split - improve for production.
words := []string{}
currentWord := ""
for _, r := range text {
if r == ' ' {
if currentWord != "" {
words = append(words, currentWord)
currentWord = ""
}
} else {
currentWord += string(r)
}
}
if currentWord != "" {
words = append(words, currentWord)
}
return words
}
func splitTextIntoSentences(text string) []string {
// Very simple split - improve for production.
sentences := []string{}
currentSentence := ""
for _, r := range text {
if r == '.' || r == '?' || r == '!' {
currentSentence += string(r)
sentences = append(sentences, currentSentence)
currentSentence = ""
} else {
currentSentence += string(r)
}
}
return sentences
}
func isCapitalized(r rune) bool {
return r >= 'A' && r <= 'Z'
}
func main() {
blackboard := NewBlackboard()
spellCheck := NewSpellCheckSource(map[string]bool{"hello": true, "world": true, "go": true, "langue": false})
capitalizationCheck := NewCapitalizationSource()
// Register knowledge sources
go func() {
for text := range blackboard.changes {
spellCheck.ReactToChange(text)
capitalizationCheck.ReactToChange(text)
}
}()
blackboard.SetText("hello world. go langue")
blackboard.SetText("this is a test")
// Keep the program running to process changes.
fmt.Scanln()
}
The Blackboard pattern is a computational architecture for solving complex problems by providing a shared data structure (the “blackboard”) that multiple independent knowledge sources (KS) can access and modify. KSs react to changes on the blackboard, contributing to the solution without direct control over each other. This promotes flexibility and concurrency.
This C implementation demonstrates a basic Blackboard with an integer blackboard, a generator KS adding data, and a processor KS subtracting from it. Synchronization is handled with a mutex to prevent race conditions. The blackboard_t structure represents the blackboard and the KSs operate on it through defined function pointers. This structure and function pointer usage aligns well with C’s low-level control and callback mechanisms.
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// Define the Blackboard data
typedef struct {
int data;
pthread_mutex_t mutex;
} blackboard_t;
// Knowledge Source function pointer type
typedef void (*ks_func_t)(blackboard_t* blackboard);
// Knowledge Source: Generator - adds to the blackboard
void generator_ks(blackboard_t* blackboard) {
pthread_mutex_lock(&blackboard->mutex);
blackboard->data += 10;
printf("Generator: Added 10, Blackboard data = %d\n", blackboard->data);
pthread_mutex_unlock(&blackboard->mutex);
}
// Knowledge Source: Processor - subtracts from the blackboard
void processor_ks(blackboard_t* blackboard) {
pthread_mutex_lock(&blackboard->mutex);
blackboard->data -= 5;
printf("Processor: Subtracted 5, Blackboard data = %d\n", blackboard->data);
pthread_mutex_unlock(&blackboard->mutex);
}
int main() {
blackboard_t blackboard = {0, PTHREAD_MUTEX_INITIALIZER};
pthread_t gen_thread, proc_thread;
// Create and start Knowledge Source threads
pthread_create(&gen_thread, NULL, (void *(*)(void *))generator_ks, &blackboard);
pthread_create(&proc_thread, NULL, (void *(*)(void *))processor_ks, &blackboard);
// Let the threads run for a bit
sleep(2);
// Clean up
pthread_join(gen_thread, NULL);
pthread_join(proc_thread, NULL);
pthread_mutex_destroy(&blackboard.mutex);
return 0;
}
The Blackboard pattern is a computational problem-solving technique where multiple independent knowledge sources (KSs) contribute to solving a problem via a shared data structure, the Blackboard. KSs ‘watch’ the Blackboard for changes that match their expertise, and when a match occurs, execute to modify the data accordingly. This allows for flexible, collaborative problem-solving without tight coupling between the KSs. The code demonstrates this with a simplified example of image processing: edge detection and shape analysis operate on pixel data in a shared Blackboard structure. C++’s flexibility allows representing KSs as classes with dedicated functions watching for conditions in the Blackboard.
#include <iostream>
#include <vector>
#include <algorithm>
#include <mutex>
// The Blackboard - shared data
class Blackboard {
public:
std::vector<std::vector<int>> data;
std::mutex mutex;
Blackboard(int rows, int cols) : data(rows, std::vector<int>(cols, 0)) {}
void update_data(int row, int col, int value) {
std::lock_guard<std::mutex> lock(mutex);
data[row][col] = value;
}
int get_data(int row, int col) const {
std::lock_guard<std::mutex> lock(mutex);
return data[row][col];
}
};
// Knowledge Source Interface
class KnowledgeSource {
public:
virtual void execute(Blackboard& blackboard) = 0;
virtual bool should_execute(Blackboard& blackboard) = 0;
};
// Knowledge Source: Edge Detection
class EdgeDetector : public KnowledgeSource {
public:
void execute(Blackboard& blackboard) override {
for (size_t i = 1; i < blackboard.data.size() - 1; ++i) {
for (size_t j = 1; j < blackboard.data[0].size() - 1; ++j) {
// Simple edge detection (Sobel-like)
int gx = blackboard.get_data(i - 1, j) - blackboard.get_data(i + 1, j);
int gy = blackboard.get_data(i, j - 1) - blackboard.get_data(i, j + 1);
int gradient_magnitude = std::abs(gx) + std::abs(gy);
if (gradient_magnitude > 10) {
blackboard.update_data(i, j, 255); // Mark edge
}
}
}
}
bool should_execute(Blackboard& blackboard) override {
// Execute if no edges have been detected yet
for (auto& row : blackboard.data) {
if (std::any_of(row.begin(), row.end(), [](int pixel){ return pixel == 255; })) {
return false;
}
}
return true;
}
};
// Knowledge Source: Shape Analysis (Placeholder)
class ShapeAnalyzer : public KnowledgeSource {
public:
void execute(Blackboard& blackboard) override {
// In a real implementation, this would analyze shapes based on edge data.
std::cout << "Shape analysis running..." << std::endl;
}
bool should_execute(Blackboard& blackboard) override {
// Execute if edges are present
for (auto& row : blackboard.data) {
if (std::any_of(row.begin(), row.end(), [](int pixel){ return pixel == 255; })) {
return true;
}
}
return false;
}
};
int main() {
Blackboard blackboard(5, 5);
// Initialize blackboard with some data
blackboard.update_data(1, 1, 100);
blackboard.update_data(1, 2, 150);
blackboard.update_data(2, 1, 120);
blackboard.update_data(2, 2, 200);
// Create knowledge sources
EdgeDetector edge_detector;
ShapeAnalyzer shape_analyzer;
// Execution loop
std::vector<KnowledgeSource*> knowledge_sources = {&edge_detector, &shape_analyzer};
for (auto& ks : knowledge_sources) {
if (ks->should_execute(blackboard)) {
ks->execute(blackboard);
}
}
// Print the blackboard data
for (const auto& row : blackboard.data) {
for (int pixel : row) {
std::cout << pixel << " ";
}
std::cout << std::endl;
}
return 0;
}
The Blackboard pattern is a computational architecture for solving problems that don’t have a pre-defined solution sequence. It consists of several knowledge sources (KSs) that independently examine a shared data structure (the blackboard) and contribute to the solution when they have relevant information. A control component manages the KS execution order. This implementation uses a simple string blackboard and KSs that perform basic string manipulations. It’s idiomatic C# due to its use of interfaces for KSs, allowing for loose coupling and extensibility, and delegates for the control component to manage KS execution.
// Blackboard.cs
using System;
using System.Collections.Generic;
// Define the Blackboard interface
public interface IBlackboard
{
string Data { get; set; }
}
// Concrete Blackboard implementation
public class StringBlackboard : IBlackboard
{
public string Data { get; set; } = string.Empty;
}
// Define the Knowledge Source interface
public interface IKnowledgeSource
{
void Execute(IBlackboard blackboard);
}
// Concrete Knowledge Sources
public class UppercaseKS : IKnowledgeSource
{
public void Execute(IBlackboard blackboard)
{
if (!string.IsNullOrEmpty(blackboard.Data))
{
blackboard.Data = blackboard.Data.ToUpper();
}
}
}
public class ReverseKS : IKnowledgeSource
{
public void Execute(IBlackboard blackboard)
{
if (!string.IsNullOrEmpty(blackboard.Data))
{
blackboard.Data = new string(blackboard.Data.ToCharArray().Reverse().ToArray());
}
}
}
public class TrimKS : IKnowledgeSource
{
public void Execute(IBlackboard blackboard)
{
if (!string.IsNullOrEmpty(blackboard.Data))
{
blackboard.Data = blackboard.Data.Trim();
}
}
}
// Control component
public class BlackboardController
{
private readonly List<IKnowledgeSource> _knowledgeSources;
public BlackboardController(List<IKnowledgeSource> knowledgeSources)
{
_knowledgeSources = knowledgeSources;
}
public void Run(IBlackboard blackboard)
{
foreach (var ks in _knowledgeSources)
{
ks.Execute(blackboard);
}
}
}
// Usage Example
public class Program
{
public static void Main(string[] args)
{
StringBlackboard blackboard = new StringBlackboard();
blackboard.Data = " hello world ";
List<IKnowledgeSource> knowledgeSources = new List<IKnowledgeSource>()
{
new UppercaseKS(),
new TrimKS(),
new ReverseKS()
};
BlackboardController controller = new BlackboardController(knowledgeSources);
controller.Run(blackboard);
Console.WriteLine(blackboard.Data); // Output: DLROW OLLEH
}
}
The Blackboard pattern is a computational architecture where multiple independent knowledge sources (agents) collaborate to solve a complex problem. A central data structure, the “blackboard,” holds the problem state, and agents react to changes on the blackboard, modifying it to move closer to a solution. This facilitates a flexible and adaptable system, well-suited for problems lacking a clear algorithmic solution.
This TypeScript implementation simulates a simple Blackboard with agents that add information to it. Blackboard class manages the shared data (data). Agent classes each define a run method that examines the blackboard and potentially manipulates the data. Agents are loosely coupled and added to the blackboard instance, triggering their actions when the solve method is called. Using classes and interfaces aligns with TypeScript’s OOP capabilities and promotes type safety and organization.
interface BlackboardData {
[key: string]: any;
}
class Blackboard {
private data: BlackboardData = {};
private agents: Agent[] = [];
constructor() {}
getData(): BlackboardData {
return this.data;
}
setData(key: string, value: any) {
this.data[key] = value;
this.notifyAgents();
}
addAgent(agent: Agent) {
this.agents.push(agent);
}
solve(): void {
this.agents.forEach(agent => agent.run(this.data, this));
}
private notifyAgents(): void {
this.agents.forEach(agent => agent.run(this.data, this));
}
}
interface Agent {
run(data: BlackboardData, blackboard: Blackboard): void;
}
class InputAgent implements Agent {
run(data: BlackboardData, blackboard: Blackboard): void {
if (!data.input) {
console.log("Input Agent: Providing initial input.");
blackboard.setData("input", "Initial Data");
}
}
}
class ProcessingAgent implements Agent {
run(data: BlackboardData, blackboard: Blackboard): void {
if (data.input && !data.processed) {
console.log("Processing Agent: Processing input.");
blackboard.setData("processed", data.input.toUpperCase());
}
}
}
class OutputAgent implements Agent {
run(data: BlackboardData, blackboard: Blackboard): void {
if (data.processed) {
console.log("Output Agent: Displaying result.");
console.log("Result:", data.processed);
}
}
}
// Example Usage:
const blackboard = new Blackboard();
blackboard.addAgent(new InputAgent());
blackboard.addAgent(new ProcessingAgent());
blackboard.addAgent(new OutputAgent());
blackboard.solve();
The Blackboard pattern is a computational problem-solving approach where multiple independent knowledge sources (agents) operate on a shared data structure (the blackboard) to collectively solve a complex problem. Each agent recognizes patterns in the blackboard’s state and contributes relevant information, triggering other agents in a cycle until a solution is found. This promotes loose coupling and allows for dynamic problem-solving.
The JavaScript implementation uses a central Blackboard object holding the evolving solution. Agent classes define apply methods which modify the blackboard based on their assigned criteria. A Dispatcher manages the agents and orchestrates the process, iteratively applying agents until a solved flag is set on the blackboard. This leverages JavaScript’s flexible object model and favors composition over inheritance, fitting its typical style. Asynchronous operations could easily be integrated within the agents.
// blackboard.js
class Blackboard {
constructor(initialData) {
this.data = initialData;
this.solved = false;
}
setData(newData) {
this.data = newData;
}
getData() {
return this.data;
}
isSolved() {
return this.solved;
}
setSolved() {
this.solved = true;
}
}
// agent.js
class Agent {
constructor(name, condition, apply) {
this.name = name;
this.condition = condition;
this.apply = apply;
}
shouldApply(blackboard) {
return this.condition(blackboard.getData());
}
apply(blackboard) {
this.apply(blackboard.getData());
}
}
// dispatcher.js
class Dispatcher {
constructor(blackboard, agents) {
this.blackboard = blackboard;
this.agents = agents;
}
run() {
while (!this.blackboard.isSolved()) {
for (const agent of this.agents) {
if (agent.shouldApply(this.blackboard)) {
agent.apply(this.blackboard);
break; // Apply only one agent per cycle
}
}
}
}
}
// Example Usage:
// problem: Determine if a number is prime
const initialData = 17;
const blackboard = new Blackboard(initialData);
const checkDivisibility = new Agent(
"DivisibilityChecker",
(data) => data > 1,
(data) => {
for (let i = 2; i <= Math.sqrt(data); i++) {
if (data % i === 0) {
blackboard.setData(`Not prime, divisible by ${i}`);
return;
}
}
blackboard.setData("Prime");
}
);
const setSolved = new Agent(
"SolutionSetter",
(data) => data === "Prime" || data === "Not prime, divisible by",
(data) => {
blackboard.setSolved();
}
);
const dispatcher = new Dispatcher(blackboard, [checkDivisibility, setSolved]);
dispatcher.run();
console.log(blackboard.getData()); // Output: Prime
console.log(blackboard.isSolved()); // Output: true
The Blackboard pattern is a shared data repository (the “blackboard”) combined with a set of independent “knowledge sources” that observe the blackboard and contribute to solving a problem when they have relevant information. It’s useful for complex problems with no clear algorithmic solution, allowing loosely coupled components to collaborate.
This Python implementation uses a dictionary as the blackboard and functions as knowledge sources. Each source checks conditions on the blackboard and updates it if applicable. The run_blackboard function simulates the problem-solving process, iteratively applying knowledge sources until a solution is reached (or a maximum number of iterations is exceeded). This design uses Python’s flexibility and functional capabilities for easy addition and modification of knowledge sources without central control.
class Blackboard:
def __init__(self):
self.data = {}
def set(self, key, value):
self.data[key] = value
def get(self, key):
return self.data.get(key)
def knowledge_source_1(blackboard):
if not blackboard.get("input_data"):
blackboard.set("input_data", "initial data")
return True
return False
def knowledge_source_2(blackboard):
input_data = blackboard.get("input_data")
if input_data and not blackboard.get("processed_data"):
blackboard.set("processed_data", input_data.upper())
return True
return False
def knowledge_source_3(blackboard):
processed_data = blackboard.get("processed_data")
if processed_data and not blackboard.get("solution"):
blackboard.set("solution", f"Final result: {processed_data}")
return True
return False
def run_blackboard(blackboard, knowledge_sources, max_iterations=10):
for _ in range(max_iterations):
any_changed = False
for source in knowledge_sources:
if source(blackboard):
any_changed = True
if not any_changed:
break
return blackboard.get("solution")
if __name__ == "__main__":
blackboard = Blackboard()
knowledge_sources = [knowledge_source_1, knowledge_source_2, knowledge_source_3]
solution = run_blackboard(blackboard, knowledge_sources)
print(solution)
The Blackboard pattern is a communication and coordination mechanism for independent knowledge sources (agents) to collaboratively solve a complex problem. A central data structure, the “blackboard,” holds the problem state, and agents react to changes on the board, contributing their expertise. This code demonstrates a simplified Blackboard for a string processing pipeline, with agents for uppercasing, trimming, and replacing substrings. Each agent observes the blackboard and applies its transformation when triggered by the appropriate condition. The implementation uses interfaces for agent interaction with the blackboard, keeping the components loosely coupled, a common practice in Java for extensibility and maintainability.
// Blackboard.java
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
class Blackboard extends Observable {
private String data;
public Blackboard(String initialData) {
this.data = initialData;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
setChanged();
notifyObservers();
}
}
// Agent.java
interface Agent {
void observe(Blackboard blackboard);
void process();
}
// UppercaseAgent.java
class UppercaseAgent implements Agent {
private Blackboard blackboard;
@Override
public void observe(Blackboard blackboard) {
this.blackboard = blackboard;
}
@Override
public void process() {
String currentData = blackboard.getData();
blackboard.setData(currentData.toUpperCase());
}
}
// TrimAgent.java
class TrimAgent implements Agent {
private Blackboard blackboard;
@Override
public void observe(Blackboard blackboard) {
this.blackboard = blackboard;
}
@Override
public void process() {
String currentData = blackboard.getData();
blackboard.setData(currentData.trim());
}
}
// ReplaceAgent.java
class ReplaceAgent implements Agent {
private Blackboard blackboard;
private String target;
private String replacement;
public ReplaceAgent(String target, String replacement) {
this.target = target;
this.replacement = replacement;
}
@Override
public void observe(Blackboard blackboard) {
this.blackboard = blackboard;
}
@Override
public void process() {
String currentData = blackboard.getData();
blackboard.setData(currentData.replace(target, replacement));
}
}
// Main.java
public class Main {
public static void main(String[] args) {
Blackboard blackboard = new Blackboard(" hello world ");
UppercaseAgent uppercaseAgent = new UppercaseAgent();
TrimAgent trimAgent = new TrimAgent();
ReplaceAgent replaceAgent = new ReplaceAgent("WORLD", "Java");
uppercaseAgent.observe(blackboard);
trimAgent.observe(blackboard);
replaceAgent.observe(blackboard);
// Simulate agents processing in a specific order
uppercaseAgent.process();
trimAgent.process();
replaceAgent.process();
System.out.println(blackboard.getData()); // Output: HELLO JAVA
}
}