mirror of
https://github.com/hsokolowski/iTree.git
synced 2026-06-11 06:14:20 -04:00
Replacing nodes with new calculations
This commit is contained in:
560
public/algorithms/abc.js
Normal file
560
public/algorithms/abc.js
Normal file
@@ -0,0 +1,560 @@
|
||||
window.counter = 0;
|
||||
//let list_obj=[];
|
||||
window.list_obj = [];
|
||||
//let counter=0;
|
||||
|
||||
export var dt = (function () {
|
||||
//debugger;
|
||||
/**
|
||||
* Creates an instance of DecisionTree
|
||||
*
|
||||
* @constructor
|
||||
* @param builder - contains training set and
|
||||
* some configuration parameters
|
||||
*/
|
||||
function DecisionTree(builder) {
|
||||
this.root = buildDecisionTree({
|
||||
trainingSet: builder.trainingSet,
|
||||
ignoredAttributes: arrayToHashSet(builder.ignoredAttributes),
|
||||
//ignoredAttributes: builder.ignoredAttributes,
|
||||
categoryAttr: builder.categoryAttr || 'category',
|
||||
minItemsCount: builder.minItemsCount, //|| 4,
|
||||
entropyThrehold: builder.entropyThrehold, // || 0.1,
|
||||
maxTreeDepth: builder.maxTreeDepth, // || 30
|
||||
});
|
||||
//console.log(builder.ignoredAttributes)
|
||||
}
|
||||
|
||||
DecisionTree.prototype.predict = function (item) {
|
||||
return predict(this.root, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an instance of RandomForest
|
||||
* with specific number of trees
|
||||
*
|
||||
* @constructor
|
||||
* @param builder - contains training set and some
|
||||
* configuration parameters for
|
||||
* building decision trees
|
||||
*/
|
||||
function RandomForest(builder, treesNumber) {
|
||||
this.trees = buildRandomForest(builder, treesNumber);
|
||||
}
|
||||
|
||||
RandomForest.prototype.predict = function (item) {
|
||||
return predictRandomForest(this.trees, item);
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforming array to object with such attributes
|
||||
* as elements of array (afterwards it can be used as HashSet)
|
||||
*/
|
||||
function arrayToHashSet(array) {
|
||||
var hashSet = {};
|
||||
if (array) {
|
||||
for (var i in array) {
|
||||
var attr = array[i];
|
||||
hashSet[attr] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hashSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculating how many objects have the same
|
||||
* values of specific attribute.
|
||||
*
|
||||
* @param items - array of objects
|
||||
*
|
||||
* @param attr - variable with name of attribute,
|
||||
* which embedded in each object
|
||||
*/
|
||||
function countUniqueValues(items, attr) {
|
||||
var counter = {};
|
||||
|
||||
// detecting different values of attribute
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
// items[i][attr] - value of attribute
|
||||
counter[items[i][attr]] = 0;
|
||||
}
|
||||
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
for (var j = items.length - 1; j >= 0; j--) {
|
||||
counter[items[j][attr]] += 1;
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculating entropy of array of objects
|
||||
* by specific attribute.
|
||||
*
|
||||
* @param items - array of objects
|
||||
*
|
||||
* @param attr - variable with name of attribute,
|
||||
* which embedded in each object
|
||||
*/
|
||||
function entropy(items, attr) {
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
var counter = countUniqueValues(items, attr);
|
||||
|
||||
var entropy = 0;
|
||||
var p;
|
||||
for (var i in counter) {
|
||||
p = counter[i] / items.length;
|
||||
entropy += -p * Math.log(p);
|
||||
}
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splitting array of objects by value of specific attribute,
|
||||
* using specific predicate and pivot.
|
||||
*
|
||||
* Items which matched by predicate will be copied to
|
||||
* the new array called 'match', and the rest of the items
|
||||
* will be copied to array with name 'notMatch'
|
||||
*
|
||||
* @param items - array of objects
|
||||
*
|
||||
* @param attr - variable with name of attribute,
|
||||
* which embedded in each object
|
||||
*
|
||||
* @param predicate - function(x, y)
|
||||
* which returns 'true' or 'false'
|
||||
*
|
||||
* @param pivot - used as the second argument when
|
||||
* calling predicate function:
|
||||
* e.g. predicate(item[attr], pivot)
|
||||
*/
|
||||
function split(items, attr, predicate, pivot) {
|
||||
var match = [];
|
||||
var notMatch = [];
|
||||
|
||||
var item, attrValue;
|
||||
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
item = items[i];
|
||||
attrValue = item[attr];
|
||||
|
||||
if (predicate(attrValue, pivot)) {
|
||||
match.push(item);
|
||||
} else {
|
||||
notMatch.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
match: match,
|
||||
notMatch: notMatch,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finding value of specific attribute which is most frequent
|
||||
* in given array of objects.
|
||||
*
|
||||
* @param items - array of objects
|
||||
*
|
||||
* @param attr - variable with name of attribute,
|
||||
* which embedded in each object
|
||||
*/
|
||||
function mostFrequentValue(items, attr) {
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
var counter = countUniqueValues(items, attr);
|
||||
|
||||
var mostFrequentCount = 0;
|
||||
var mostFrequentValue;
|
||||
|
||||
for (var value in counter) {
|
||||
if (counter[value] > mostFrequentCount) {
|
||||
mostFrequentCount = counter[value];
|
||||
mostFrequentValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
return mostFrequentValue;
|
||||
}
|
||||
|
||||
var predicates = {
|
||||
'==': function (a, b) {
|
||||
return a === b;
|
||||
},
|
||||
'>=': function (a, b) {
|
||||
return a >= b;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Function for building decision tree
|
||||
*/
|
||||
function buildDecisionTree(builder) {
|
||||
var trainingSet = builder.trainingSet;
|
||||
var minItemsCount = builder.minItemsCount;
|
||||
var categoryAttr = builder.categoryAttr;
|
||||
var entropyThrehold = builder.entropyThrehold;
|
||||
var maxTreeDepth = builder.maxTreeDepth;
|
||||
var ignoredAttributes = builder.ignoredAttributes;
|
||||
|
||||
let _quality = 0;
|
||||
if (maxTreeDepth === 0 || trainingSet.length <= minItemsCount) {
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
let _hide = 0;
|
||||
//console.log(trainingSet);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] === _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
if (_quality !== 1) _hide = 25;
|
||||
_quality = _quality * 100;
|
||||
_quality = _quality.toFixed(2);
|
||||
// restriction by maximal depth of tree
|
||||
// or size of training set is to small
|
||||
// so we have to terminate process of building tree
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
hide: _hide,
|
||||
};
|
||||
}
|
||||
//console.log(categoryAttr);
|
||||
var initialEntropy = entropy(trainingSet, categoryAttr);
|
||||
console.log('initialEntropy ' + initialEntropy);
|
||||
if (initialEntropy <= entropyThrehold) {
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
let _hide = 0;
|
||||
//console.log(trainingSet);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] === _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
if (_quality !== 1) _hide = 25;
|
||||
_quality = _quality * 100;
|
||||
_quality = _quality.toFixed(2);
|
||||
// entropy of training set too small
|
||||
// (it means that training set is almost homogeneous),
|
||||
// so we have to terminate process of building tree
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
hide: _hide,
|
||||
};
|
||||
}
|
||||
|
||||
// used as hash-set for avoiding the checking of split by rules
|
||||
// with the same 'attribute-predicate-pivot' more than once
|
||||
var alreadyChecked = {};
|
||||
|
||||
// this variable expected to contain rule, which splits training set
|
||||
// into subsets with smaller values of entropy (produces informational gain)
|
||||
var bestSplit = { gain: 0 };
|
||||
|
||||
var pivot;
|
||||
var predicateName;
|
||||
var attrPredPivot;
|
||||
var predicate;
|
||||
var currSplit;
|
||||
var matchEntropy;
|
||||
var notMatchEntropy;
|
||||
var newEntropy;
|
||||
var currGain;
|
||||
|
||||
if (window.isChange) {
|
||||
let attr = window.CurrentAttribute;
|
||||
|
||||
// let the value of current attribute be the pivot
|
||||
pivot = window.inputValue;
|
||||
|
||||
//console.log(attr +" "+ pivot)
|
||||
if (!isNaN(pivot)) {
|
||||
pivot = parseFloat(pivot);
|
||||
}
|
||||
|
||||
// pick the predicate
|
||||
// depending on the type of the attribute value
|
||||
// var predicateName;
|
||||
if (typeof pivot == 'number') {
|
||||
predicateName = '>=';
|
||||
} else {
|
||||
// there is no sense to compare non-numeric attributes
|
||||
// so we will check only equality of such attributes
|
||||
predicateName = '==';
|
||||
}
|
||||
|
||||
attrPredPivot = attr + predicateName + pivot;
|
||||
if (alreadyChecked[attrPredPivot]) {
|
||||
// skip such pairs of 'attribute-predicate-pivot',
|
||||
// which been already checked
|
||||
//continue;
|
||||
}
|
||||
alreadyChecked[attrPredPivot] = true;
|
||||
|
||||
predicate = predicates[predicateName];
|
||||
|
||||
// splitting training set by given 'attribute-predicate-value'
|
||||
currSplit = split(trainingSet, attr, predicate, pivot);
|
||||
//console.log(currSplit.match)
|
||||
//console.log(currSplit.notMatch)
|
||||
|
||||
// calculating entropy of subsets
|
||||
matchEntropy = entropy(currSplit.match, categoryAttr);
|
||||
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
|
||||
|
||||
// calculating informational gain
|
||||
newEntropy = 0;
|
||||
newEntropy += matchEntropy * currSplit.match.length;
|
||||
newEntropy += notMatchEntropy * currSplit.notMatch.length;
|
||||
newEntropy /= trainingSet.length;
|
||||
currGain = initialEntropy - newEntropy;
|
||||
console.log('CURRENT GAIN ' + currGain);
|
||||
if (currGain > bestSplit.gain) {
|
||||
// remember pairs 'attribute-predicate-value'
|
||||
// which provides informational gain
|
||||
bestSplit = currSplit;
|
||||
bestSplit.predicateName = predicateName;
|
||||
bestSplit.predicate = predicate;
|
||||
bestSplit.attribute = attr;
|
||||
bestSplit.pivot = pivot;
|
||||
bestSplit.gain = currGain;
|
||||
}
|
||||
|
||||
window.isChange = false;
|
||||
} else {
|
||||
//delete space from property in object
|
||||
// let ignore=""
|
||||
// if(!(Object.entries(ignoredAttributes).length === 0 && ignoredAttributes.constructor === Object)){
|
||||
// ignore = Object.keys(ignoredAttributes)
|
||||
// ignore = ignore[0].substring(0, ignore[0].length - 1);
|
||||
// console.log(ignore)
|
||||
// }
|
||||
//console.log("Liczy")
|
||||
|
||||
for (var i = trainingSet.length - 1; i >= 0; i--) {
|
||||
var item = trainingSet[i];
|
||||
|
||||
// iterating over all attributes of item
|
||||
for (var attr in item) {
|
||||
//console.log(ignoredAttributes)
|
||||
//if(ignoredAttributes[attr]===true) console.log("równe")
|
||||
if (attr === categoryAttr || ignoredAttributes[attr]) {
|
||||
//if ((attr === categoryAttr) || ignore===attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// let the value of current attribute be the pivot
|
||||
pivot = item[attr];
|
||||
if (!isNaN(pivot)) {
|
||||
pivot = parseFloat(pivot);
|
||||
}
|
||||
// pick the predicate
|
||||
// depending on the type of the attribute value
|
||||
//var predicateName;
|
||||
if (typeof pivot == 'number') {
|
||||
//console.log('is number ' + pivot + ' ' + typeof pivot)
|
||||
predicateName = '>=';
|
||||
} else {
|
||||
//console.log('is not number ' + pivot + ' ' + typeof pivot)
|
||||
|
||||
// there is no sense to compare non-numeric attributes
|
||||
// so we will check only equality of such attributes
|
||||
predicateName = '==';
|
||||
}
|
||||
|
||||
attrPredPivot = attr + predicateName + pivot;
|
||||
if (alreadyChecked[attrPredPivot]) {
|
||||
// skip such pairs of 'attribute-predicate-pivot',
|
||||
// which been already checked
|
||||
continue;
|
||||
}
|
||||
alreadyChecked[attrPredPivot] = true;
|
||||
|
||||
predicate = predicates[predicateName];
|
||||
|
||||
// splitting training set by given 'attribute-predicate-value'
|
||||
currSplit = split(trainingSet, attr, predicate, pivot);
|
||||
////console.log(currSplit)
|
||||
// calculating entropy of subsets
|
||||
matchEntropy = entropy(currSplit.match, categoryAttr);
|
||||
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
|
||||
////console.log(bestSplit.gain)
|
||||
// calculating informational gain
|
||||
newEntropy = 0;
|
||||
newEntropy += matchEntropy * currSplit.match.length;
|
||||
newEntropy += notMatchEntropy * currSplit.notMatch.length;
|
||||
newEntropy /= trainingSet.length;
|
||||
currGain = initialEntropy - newEntropy;
|
||||
//console.log("CURRENT GAIN 2"+currGain)
|
||||
if (currGain > bestSplit.gain) {
|
||||
// remember pairs 'attribute-predicate-value'
|
||||
// which provides informational gain
|
||||
bestSplit = currSplit;
|
||||
bestSplit.predicateName = predicateName;
|
||||
bestSplit.predicate = predicate;
|
||||
bestSplit.attribute = attr;
|
||||
bestSplit.pivot = pivot;
|
||||
bestSplit.gain = currGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(bestSplit.gain);
|
||||
if (!bestSplit.gain) {
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
let _hide = 0;
|
||||
//console.log(trainingSet);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] === _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
if (_quality !== 1) _hide = 25;
|
||||
_quality = _quality * 100;
|
||||
_quality = _quality.toFixed(2);
|
||||
// can't find optimal split
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
hide: _hide,
|
||||
};
|
||||
}
|
||||
|
||||
if (window.isChange) {
|
||||
bestSplit.pivot = window.inputValue;
|
||||
window.isChange = false;
|
||||
}
|
||||
|
||||
let array = bestSplit.match.concat(bestSplit.notMatch);
|
||||
window.list_obj.push({
|
||||
id: window.counter,
|
||||
match: bestSplit.match,
|
||||
notMatch: bestSplit.notMatch,
|
||||
full: array,
|
||||
});
|
||||
window.counter++;
|
||||
//console.log(window.list_obj)
|
||||
|
||||
// building subtrees
|
||||
builder.maxTreeDepth = maxTreeDepth - 1;
|
||||
|
||||
builder.trainingSet = bestSplit.match;
|
||||
var matchSubTree = buildDecisionTree(builder);
|
||||
|
||||
builder.trainingSet = bestSplit.notMatch;
|
||||
var notMatchSubTree = buildDecisionTree(builder);
|
||||
|
||||
return {
|
||||
attribute: bestSplit.attribute,
|
||||
predicate: bestSplit.predicate,
|
||||
predicateName: bestSplit.predicateName,
|
||||
pivot: bestSplit.pivot,
|
||||
match: matchSubTree,
|
||||
notMatch: notMatchSubTree,
|
||||
matchedCount: bestSplit.match.length,
|
||||
notMatchedCount: bestSplit.notMatch.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifying item, using decision tree
|
||||
*/
|
||||
function predict(tree, item) {
|
||||
var attr, value, predicate, pivot;
|
||||
|
||||
// Traversing tree from the root to leaf
|
||||
while (true) {
|
||||
if (tree.category) {
|
||||
// only leafs contains predicted category
|
||||
return tree.category;
|
||||
}
|
||||
|
||||
attr = tree.attribute;
|
||||
value = item[attr];
|
||||
|
||||
predicate = tree.predicate;
|
||||
pivot = tree.pivot;
|
||||
|
||||
// move to one of subtrees
|
||||
if (predicate(value, pivot)) {
|
||||
tree = tree.match;
|
||||
} else {
|
||||
tree = tree.notMatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Building array of decision trees
|
||||
*/
|
||||
function buildRandomForest(builder, treesNumber) {
|
||||
var items = builder.trainingSet;
|
||||
|
||||
// creating training sets for each tree
|
||||
var trainingSets = [];
|
||||
for (var t = 0; t < treesNumber; t++) {
|
||||
trainingSets[t] = [];
|
||||
}
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
// assigning items to training sets of each tree
|
||||
// using 'round-robin' strategy
|
||||
var correspondingTree = i % treesNumber;
|
||||
trainingSets[correspondingTree].push(items[i]);
|
||||
}
|
||||
|
||||
// building decision trees
|
||||
var forest = [];
|
||||
for (var tt = 0; tt < treesNumber; tt++) {
|
||||
builder.trainingSet = trainingSets[tt];
|
||||
|
||||
var tree = new DecisionTree(builder);
|
||||
forest.push(tree);
|
||||
}
|
||||
return forest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each of decision tree classifying item
|
||||
* ('voting' that item corresponds to some class).
|
||||
*
|
||||
* This function returns hash, which contains
|
||||
* all classifying results, and number of votes
|
||||
* which were given for each of classifying results
|
||||
*/
|
||||
function predictRandomForest(forest, item) {
|
||||
var result = {};
|
||||
for (var i in forest) {
|
||||
var tree = forest[i];
|
||||
var prediction = tree.predict(item);
|
||||
result[prediction] = result[prediction] ? result[prediction] + 1 : 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var exports = {};
|
||||
exports.DecisionTree = DecisionTree;
|
||||
exports.RandomForest = RandomForest;
|
||||
return exports;
|
||||
})();
|
||||
|
||||
export function getListObj() {
|
||||
return window.list_obj;
|
||||
}
|
||||
|
||||
export function confusionMatrix(array) {}
|
||||
413
public/algorithms/c45-tree.js
Normal file
413
public/algorithms/c45-tree.js
Normal file
@@ -0,0 +1,413 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @typedef {Object} DecisionTreeBuilder
|
||||
* @property {Array<Object>} trainingSet
|
||||
* @property {Array<string>} allAttributes
|
||||
* @property {Array<string>} allClasses
|
||||
* @property {number} minItemsCount
|
||||
* @property {string} categoryAttr
|
||||
* @property {number} entropyThrehold
|
||||
* @property {number} maxTreeDepth
|
||||
* @property {Array<string>} ignoredAttributes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {DecisionTreeBuilder} _builder
|
||||
* @param {boolean} isChanged
|
||||
*/
|
||||
//TSP
|
||||
function buildDecisionTreeC45(
|
||||
_builder,
|
||||
isChanged = false,
|
||||
changedAttribute1 = null,
|
||||
changedAttribute2 = null
|
||||
) {
|
||||
//debugger;
|
||||
const builder = { ..._builder };
|
||||
const {
|
||||
trainingSet,
|
||||
minItemsCount,
|
||||
categoryAttr,
|
||||
entropyThrehold,
|
||||
maxTreeDepth,
|
||||
ignoredAttributes,
|
||||
} = builder;
|
||||
|
||||
let _quality = 0;
|
||||
if (maxTreeDepth === 0 || trainingSet.length <= minItemsCount) {
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
let _hide = 0;
|
||||
//console.log(trainingSet);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] === _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
if (_quality !== 1) _hide = 25;
|
||||
_quality = _quality * 100;
|
||||
// restriction by maximal depth of tree
|
||||
// or size of training set is to small
|
||||
// so we have to terminate process of building tree
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
trainingSet2: trainingSet.map(x => x[categoryAttr]),
|
||||
};
|
||||
}
|
||||
//console.log(categoryAttr);
|
||||
var initialEntropy = entropy(trainingSet, categoryAttr);
|
||||
console.log('initialEntropy ' + initialEntropy);
|
||||
if (initialEntropy <= entropyThrehold) {
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
let _hide = 0;
|
||||
//console.log(trainingSet);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] === _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
if (_quality !== 1) _hide = 25;
|
||||
_quality = _quality * 100;
|
||||
// entropy of training set too small
|
||||
// (it means that training set is almost homogeneous),
|
||||
// so we have to terminate process of building tree
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
trainingSet2: trainingSet.map(x => x[categoryAttr]),
|
||||
};
|
||||
}
|
||||
|
||||
// used as hash-set for avoiding the checking of split by rules
|
||||
// with the same 'attribute-predicate-pivot' more than once
|
||||
var alreadyChecked = {};
|
||||
|
||||
// this variable expected to contain rule, which splits training set
|
||||
// into subsets with smaller values of entropy (produces informational gain)
|
||||
var bestSplit = { gain: 0 };
|
||||
|
||||
var pivot;
|
||||
var predicateName;
|
||||
var attrPredPivot;
|
||||
var predicate;
|
||||
var currSplit;
|
||||
var matchEntropy;
|
||||
var notMatchEntropy;
|
||||
var newEntropy;
|
||||
var currGain;
|
||||
|
||||
if (isChanged) {
|
||||
let attr = changedAttribute1;
|
||||
|
||||
// let the value of current attribute be the pivot
|
||||
pivot = changedAttribute2;
|
||||
|
||||
//console.log(attr +" "+ pivot)
|
||||
if (!isNaN(pivot)) {
|
||||
pivot = parseFloat(pivot);
|
||||
}
|
||||
|
||||
// pick the predicate
|
||||
// depending on the type of the attribute value
|
||||
// var predicateName;
|
||||
if (typeof pivot == 'number') {
|
||||
predicateName = '>=';
|
||||
} else {
|
||||
// there is no sense to compare non-numeric attributes
|
||||
// so we will check only equality of such attributes
|
||||
predicateName = '==';
|
||||
}
|
||||
|
||||
attrPredPivot = attr + predicateName + pivot;
|
||||
if (alreadyChecked[attrPredPivot]) {
|
||||
// skip such pairs of 'attribute-predicate-pivot',
|
||||
// which been already checked
|
||||
//continue;
|
||||
}
|
||||
alreadyChecked[attrPredPivot] = true;
|
||||
|
||||
predicate = predicates[predicateName];
|
||||
|
||||
// splitting training set by given 'attribute-predicate-value'
|
||||
currSplit = split(trainingSet, attr, predicate, pivot);
|
||||
//console.log(currSplit.match)
|
||||
//console.log(currSplit.notMatch)
|
||||
|
||||
// calculating entropy of subsets
|
||||
matchEntropy = entropy(currSplit.match, categoryAttr);
|
||||
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
|
||||
|
||||
// calculating informational gain
|
||||
newEntropy = 0;
|
||||
newEntropy += matchEntropy * currSplit.match.length;
|
||||
newEntropy += notMatchEntropy * currSplit.notMatch.length;
|
||||
newEntropy /= trainingSet.length;
|
||||
currGain = initialEntropy - newEntropy;
|
||||
console.log('CURRENT GAIN ' + currGain);
|
||||
if (currGain > bestSplit.gain) {
|
||||
// remember pairs 'attribute-predicate-value'
|
||||
// which provides informational gain
|
||||
bestSplit = currSplit;
|
||||
bestSplit.predicateName = predicateName;
|
||||
bestSplit.predicate = predicate;
|
||||
bestSplit.attribute = attr;
|
||||
bestSplit.pivot = pivot;
|
||||
bestSplit.gain = currGain;
|
||||
}
|
||||
|
||||
isChanged = false;
|
||||
} else {
|
||||
//delete space from property in object
|
||||
// let ignore=""
|
||||
// if(!(Object.entries(ignoredAttributes).length === 0 && ignoredAttributes.constructor === Object)){
|
||||
// ignore = Object.keys(ignoredAttributes)
|
||||
// ignore = ignore[0].substring(0, ignore[0].length - 1);
|
||||
// console.log(ignore)
|
||||
// }
|
||||
//console.log("Liczy")
|
||||
|
||||
for (var i = trainingSet.length - 1; i >= 0; i--) {
|
||||
var item = trainingSet[i];
|
||||
|
||||
// iterating over all attributes of item
|
||||
for (var attr in item) {
|
||||
//console.log(ignoredAttributes)
|
||||
//if(ignoredAttributes[attr]===true) console.log("równe")
|
||||
if (attr === categoryAttr || ignoredAttributes[attr]) {
|
||||
//if ((attr === categoryAttr) || ignore===attr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// let the value of current attribute be the pivot
|
||||
pivot = item[attr];
|
||||
if (!isNaN(pivot)) {
|
||||
pivot = parseFloat(pivot);
|
||||
}
|
||||
// pick the predicate
|
||||
// depending on the type of the attribute value
|
||||
//var predicateName;
|
||||
if (typeof pivot == 'number') {
|
||||
//console.log('is number ' + pivot + ' ' + typeof pivot)
|
||||
predicateName = '>=';
|
||||
} else {
|
||||
//console.log('is not number ' + pivot + ' ' + typeof pivot)
|
||||
|
||||
// there is no sense to compare non-numeric attributes
|
||||
// so we will check only equality of such attributes
|
||||
predicateName = '==';
|
||||
}
|
||||
|
||||
attrPredPivot = attr + predicateName + pivot;
|
||||
if (alreadyChecked[attrPredPivot]) {
|
||||
// skip such pairs of 'attribute-predicate-pivot',
|
||||
// which been already checked
|
||||
continue;
|
||||
}
|
||||
alreadyChecked[attrPredPivot] = true;
|
||||
|
||||
predicate = predicates[predicateName];
|
||||
|
||||
// splitting training set by given 'attribute-predicate-value'
|
||||
currSplit = split(trainingSet, attr, predicate, pivot);
|
||||
////console.log(currSplit)
|
||||
// calculating entropy of subsets
|
||||
matchEntropy = entropy(currSplit.match, categoryAttr);
|
||||
notMatchEntropy = entropy(currSplit.notMatch, categoryAttr);
|
||||
////console.log(bestSplit.gain)
|
||||
// calculating informational gain
|
||||
newEntropy = 0;
|
||||
newEntropy += matchEntropy * currSplit.match.length;
|
||||
newEntropy += notMatchEntropy * currSplit.notMatch.length;
|
||||
newEntropy /= trainingSet.length;
|
||||
currGain = initialEntropy - newEntropy;
|
||||
//console.log("CURRENT GAIN 2"+currGain)
|
||||
if (currGain > bestSplit.gain) {
|
||||
// remember pairs 'attribute-predicate-value'
|
||||
// which provides informational gain
|
||||
bestSplit = currSplit;
|
||||
bestSplit.predicateName = predicateName;
|
||||
bestSplit.predicate = predicate;
|
||||
bestSplit.attribute = attr;
|
||||
bestSplit.pivot = pivot;
|
||||
bestSplit.gain = currGain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(bestSplit.gain);
|
||||
if (!bestSplit.gain) {
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
let _hide = 0;
|
||||
//console.log(trainingSet);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] === _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
if (_quality !== 1) _hide = 25;
|
||||
_quality = _quality * 100;
|
||||
// can't find optimal split
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
trainingSet2: trainingSet.map(x => x[categoryAttr]),
|
||||
};
|
||||
}
|
||||
|
||||
//console.log(window.list_obj)
|
||||
|
||||
// building subtrees
|
||||
builder.maxTreeDepth = maxTreeDepth - 1;
|
||||
|
||||
builder.trainingSet = bestSplit.match;
|
||||
var matchSubTree = buildDecisionTreeC45(builder);
|
||||
|
||||
builder.trainingSet = bestSplit.notMatch;
|
||||
var notMatchSubTree = buildDecisionTreeC45(builder);
|
||||
|
||||
return {
|
||||
attr2: bestSplit.attribute,
|
||||
predicateName: bestSplit.predicateName,
|
||||
pivot: bestSplit.pivot,
|
||||
match: matchSubTree,
|
||||
notMatch: notMatchSubTree,
|
||||
matchedCount: bestSplit.match.length,
|
||||
notMatchedCount: bestSplit.notMatch.length,
|
||||
nodeSet: bestSplit.match.concat(bestSplit.notMatch),
|
||||
};
|
||||
}
|
||||
|
||||
function countUniqueValues(items, attr) {
|
||||
var counter = {};
|
||||
|
||||
// detecting different values of attribute
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
// items[i][attr] - value of attribute
|
||||
counter[items[i][attr]] = 0;
|
||||
}
|
||||
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
for (var j = items.length - 1; j >= 0; j--) {
|
||||
counter[items[j][attr]] += 1;
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
function mostFrequentValue(items, attr) {
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
var counter = countUniqueValues(items, attr);
|
||||
|
||||
var mostFrequentCount = 0;
|
||||
var mostFrequentValue;
|
||||
|
||||
for (var value in counter) {
|
||||
if (counter[value] > mostFrequentCount) {
|
||||
mostFrequentCount = counter[value];
|
||||
mostFrequentValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
return mostFrequentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculating entropy of array of objects
|
||||
* by specific attribute.
|
||||
*
|
||||
* @param items - array of objects
|
||||
*
|
||||
* @param attr - variable with name of attribute,
|
||||
* which embedded in each object
|
||||
*/
|
||||
function entropy(items, attr) {
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
var counter = countUniqueValues(items, attr);
|
||||
|
||||
var entropy = 0;
|
||||
var p;
|
||||
for (var i in counter) {
|
||||
p = counter[i] / items.length;
|
||||
entropy += -p * Math.log(p);
|
||||
}
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splitting array of objects by value of specific attribute,
|
||||
* using specific predicate and pivot.
|
||||
*
|
||||
* Items which matched by predicate will be copied to
|
||||
* the new array called 'match', and the rest of the items
|
||||
* will be copied to array with name 'notMatch'
|
||||
*
|
||||
* @param items - array of objects
|
||||
*
|
||||
* @param attr - variable with name of attribute,
|
||||
* which embedded in each object
|
||||
*
|
||||
* @param predicate - function(x, y)
|
||||
* which returns 'true' or 'false'
|
||||
*
|
||||
* @param pivot - used as the second argument when
|
||||
* calling predicate function:
|
||||
* e.g. predicate(item[attr], pivot)
|
||||
*/
|
||||
function split(items, attr, predicate, pivot) {
|
||||
var match = [];
|
||||
var notMatch = [];
|
||||
|
||||
var item, attrValue;
|
||||
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
item = items[i];
|
||||
attrValue = item[attr];
|
||||
|
||||
if (predicate(attrValue, pivot)) {
|
||||
match.push(item);
|
||||
} else {
|
||||
notMatch.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
match: match,
|
||||
notMatch: notMatch,
|
||||
};
|
||||
}
|
||||
|
||||
var predicates = {
|
||||
'==': function (a, b) {
|
||||
return a === b;
|
||||
},
|
||||
'>=': function (a, b) {
|
||||
return a >= b;
|
||||
},
|
||||
};
|
||||
|
||||
/** @type {Worker} */
|
||||
// @ts-ignore
|
||||
const context = self; //eslint-disable-line
|
||||
context.onmessage = function (event) {
|
||||
console.log('received message', event);
|
||||
const {
|
||||
data: { _builder, isChanged = false, changedAttribute1 = null, changedAttribute2 = null },
|
||||
} = event;
|
||||
const result = buildDecisionTreeC45(_builder, isChanged, changedAttribute1, changedAttribute2);
|
||||
console.log(result);
|
||||
context.postMessage(result);
|
||||
};
|
||||
317
public/algorithms/tsp-tree.js
Normal file
317
public/algorithms/tsp-tree.js
Normal file
@@ -0,0 +1,317 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @typedef {Object} DecisionTreeBuilder
|
||||
* @property {Array<Object>} trainingSet
|
||||
* @property {Array<string>} allAttributes
|
||||
* @property {Array<string>} allClasses
|
||||
* @property {number} minItemsCount
|
||||
* @property {string} categoryAttr
|
||||
* @property {number} entropyThrehold
|
||||
* @property {number} maxTreeDepth
|
||||
* @property {Array<string>} ignoredAttributes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {DecisionTreeBuilder} _builder
|
||||
* @param {boolean} isChanged
|
||||
*/
|
||||
//TSP
|
||||
function buildDecisionTreeTSP(
|
||||
_builder,
|
||||
isChanged = false,
|
||||
changedAttribute1 = null,
|
||||
changedAttribute2 = null
|
||||
) {
|
||||
//debugger;
|
||||
const builder = { ..._builder };
|
||||
const {
|
||||
trainingSet,
|
||||
minItemsCount,
|
||||
categoryAttr,
|
||||
entropyThrehold,
|
||||
maxTreeDepth,
|
||||
ignoredAttributes,
|
||||
} = builder;
|
||||
//console.log("########## NOWY WEZEL ########", trainingSet.length);
|
||||
/** @type {string | number} */
|
||||
var _quality = 0;
|
||||
if (maxTreeDepth === 0 || trainingSet.length <= minItemsCount) {
|
||||
console.log('LISC BO MAX TREE DEPTH', maxTreeDepth, 'LISC ILOSC', trainingSet.length);
|
||||
//gger;
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
//console.log("KATEGORIA JAKO:", _category);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] == _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
_quality = _quality * 100;
|
||||
_quality = _quality.toFixed(2);
|
||||
//ugger;
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
trainingSet2: trainingSet.map(x => x[categoryAttr]),
|
||||
};
|
||||
}
|
||||
var attributes = builder.allAttributes.filter(function (el) {
|
||||
return ![...ignoredAttributes, categoryAttr].includes(el);
|
||||
});
|
||||
//console.log(builder.minItemsCount, builder.trainingSet.length);
|
||||
|
||||
// tu juz musi byc przekazana cm wyzerowana
|
||||
|
||||
var podzial = [];
|
||||
//console.log(attributes);
|
||||
var right = 0,
|
||||
left = 0;
|
||||
var maxDif = 100;
|
||||
/** @type {string | number} */ var attribute1 = -1;
|
||||
/** @type {string | number} */ var attribute2 = -1;
|
||||
var directrion = '<';
|
||||
var leftList = [],
|
||||
rightList = [],
|
||||
classMatrix = [
|
||||
new Array(builder.allClasses.length).fill(0),
|
||||
new Array(builder.allClasses.length).fill(0),
|
||||
],
|
||||
match = [],
|
||||
notMatch = [];
|
||||
|
||||
//#########################
|
||||
//# tu gdy zmieniamy #
|
||||
//#########################
|
||||
if (isChanged) {
|
||||
right = left = 0;
|
||||
leftList = [];
|
||||
rightList = [];
|
||||
classMatrix = [
|
||||
new Array(builder.allClasses.length).fill(0),
|
||||
new Array(builder.allClasses.length).fill(0),
|
||||
];
|
||||
|
||||
for (let index = 0; index < trainingSet.length; index++) {
|
||||
const element = trainingSet[index];
|
||||
|
||||
if (element[changedAttribute1] < element[changedAttribute2]) {
|
||||
left++;
|
||||
leftList.push(element);
|
||||
classMatrix[0][builder.allClasses.indexOf(element[categoryAttr])]++;
|
||||
} else {
|
||||
right++;
|
||||
rightList.push(element);
|
||||
classMatrix[1][builder.allClasses.indexOf(element[categoryAttr])]++;
|
||||
}
|
||||
}
|
||||
//console.log(classMatrix);
|
||||
var probR = 0,
|
||||
probL = 0,
|
||||
rankL = 0,
|
||||
rankR = 0;
|
||||
for (let k = 0; k < builder.allClasses.length; k++) {
|
||||
probL = left === 0 ? 0 : classMatrix[0][k] / left;
|
||||
probR = right === 0 ? 0 : classMatrix[1][k] / right;
|
||||
|
||||
rankL += probL * probL;
|
||||
rankR += probR * probR;
|
||||
}
|
||||
//console.log("Rank Lewy",rankL,"Rank Prawy",rankR);
|
||||
|
||||
var currentDif = (right / trainingSet.length) * (1 - rankR) + (left / trainingSet.length) * (1 - rankL);
|
||||
if (currentDif < maxDif) {
|
||||
//console.log("------Zapisanie maxDif-------");
|
||||
//console.log(attr1,attr2);
|
||||
//console.log("R/L ", right + ":" + left);
|
||||
//console.log("cur/mD",currentDif + ":" + maxDif);
|
||||
maxDif = currentDif;
|
||||
attribute1 = changedAttribute1;
|
||||
attribute2 = changedAttribute2;
|
||||
match = leftList;
|
||||
notMatch = rightList;
|
||||
podzial = classMatrix;
|
||||
//console.log("-----------------------------");
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < attributes.length; i++) {
|
||||
let attr1 = attributes[i];
|
||||
for (let j = 0; j < attributes.length; j++) {
|
||||
let attr2 = attributes[j];
|
||||
if (attr1 !== attr2) {
|
||||
right = left = 0;
|
||||
leftList = [];
|
||||
rightList = [];
|
||||
classMatrix = [
|
||||
new Array(builder.allClasses.length).fill(0),
|
||||
new Array(builder.allClasses.length).fill(0),
|
||||
];
|
||||
|
||||
for (let index = 0; index < trainingSet.length; index++) {
|
||||
const element = trainingSet[index];
|
||||
|
||||
if (element[attr1] < element[attr2]) {
|
||||
left++;
|
||||
leftList.push(element);
|
||||
classMatrix[0][builder.allClasses.indexOf(element[categoryAttr])]++;
|
||||
} else {
|
||||
right++;
|
||||
rightList.push(element);
|
||||
classMatrix[1][builder.allClasses.indexOf(element[categoryAttr])]++;
|
||||
}
|
||||
}
|
||||
//console.log(classMatrix);
|
||||
var probR = 0,
|
||||
probL = 0,
|
||||
rankL = 0,
|
||||
rankR = 0;
|
||||
for (let k = 0; k < builder.allClasses.length; k++) {
|
||||
probL = left === 0 ? 0 : classMatrix[0][k] / left;
|
||||
probR = right === 0 ? 0 : classMatrix[1][k] / right;
|
||||
|
||||
rankL += probL * probL;
|
||||
rankR += probR * probR;
|
||||
}
|
||||
//console.log("Rank Lewy",rankL,"Rank Prawy",rankR);
|
||||
|
||||
var currentDif =
|
||||
(right / trainingSet.length) * (1 - rankR) + (left / trainingSet.length) * (1 - rankL);
|
||||
if (currentDif < maxDif) {
|
||||
//console.log("------Zapisanie maxDif-------");
|
||||
//console.log(attr1,attr2);
|
||||
//console.log("R/L ", right + ":" + left);
|
||||
//console.log("cur/mD",currentDif + ":" + maxDif);
|
||||
maxDif = currentDif;
|
||||
attribute1 = attr1;
|
||||
attribute2 = attr2;
|
||||
match = leftList;
|
||||
notMatch = rightList;
|
||||
podzial = classMatrix;
|
||||
//console.log("-----------------------------");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("PO WYLICZENIU NAJLEPSZEGO");
|
||||
//console.log(attribute1, attribute2);
|
||||
//console.log("L/R ", match.length + ":" + notMatch.length);
|
||||
//console.log(podzial);
|
||||
console.log('MaxDifference:', maxDif);
|
||||
if (!maxDif) {
|
||||
//console.log("LISC BO MAX DIF ZERO", trainingSet.length);
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
//console.log("KATEGORIA JAKO:", _category);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] == _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
_quality = _quality * 100;
|
||||
_quality = _quality.toFixed(2);
|
||||
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
trainingSet2: trainingSet.map(x => x[categoryAttr]),
|
||||
};
|
||||
}
|
||||
// sprawdzic
|
||||
// wssytskies stringi do ignored
|
||||
if (match.length === 0 || notMatch.length === 0) {
|
||||
console.log('LISC BO JEDNA ZE STRON MA 0');
|
||||
let _category = mostFrequentValue(trainingSet, categoryAttr);
|
||||
let _positiveCounter = 0;
|
||||
//console.log(_category);
|
||||
trainingSet.forEach(element => {
|
||||
if (element[categoryAttr] == _category) _positiveCounter++;
|
||||
});
|
||||
let _negativeCounter = trainingSet.length - _positiveCounter;
|
||||
_quality = _positiveCounter / trainingSet.length;
|
||||
_quality = _quality * 100;
|
||||
_quality = _quality.toFixed(2);
|
||||
// restriction by maximal depth of tree
|
||||
// or size of training set is to small
|
||||
// so we have to terminate process of building tree
|
||||
return {
|
||||
category: _category,
|
||||
quality: _quality,
|
||||
matchedCount: _positiveCounter,
|
||||
notMatchedCount: _negativeCounter,
|
||||
trainingSet2: trainingSet.map(x => x[categoryAttr]),
|
||||
};
|
||||
}
|
||||
|
||||
builder.maxTreeDepth = maxTreeDepth - 1;
|
||||
builder.trainingSet = match;
|
||||
var matchSubTree = buildDecisionTreeTSP(builder);
|
||||
|
||||
builder.trainingSet = notMatch;
|
||||
var notMatchSubTree = buildDecisionTreeTSP(builder);
|
||||
console.log('TUTAJ');
|
||||
return {
|
||||
attr2: attribute2,
|
||||
pivot: attribute1,
|
||||
predicateName: directrion,
|
||||
match: matchSubTree,
|
||||
notMatch: notMatchSubTree, //{category: ...}
|
||||
matchedCount: match.length,
|
||||
notMatchedCount: notMatch.length,
|
||||
nodeSet: match.concat(notMatch),
|
||||
};
|
||||
//console.log(attributes);
|
||||
}
|
||||
|
||||
function countUniqueValues(items, attr) {
|
||||
var counter = {};
|
||||
|
||||
// detecting different values of attribute
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
// items[i][attr] - value of attribute
|
||||
counter[items[i][attr]] = 0;
|
||||
}
|
||||
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
for (var j = items.length - 1; j >= 0; j--) {
|
||||
counter[items[j][attr]] += 1;
|
||||
}
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
function mostFrequentValue(items, attr) {
|
||||
// counting number of occurrences of each of values
|
||||
// of attribute
|
||||
var counter = countUniqueValues(items, attr);
|
||||
|
||||
var mostFrequentCount = 0;
|
||||
var mostFrequentValue;
|
||||
|
||||
for (var value in counter) {
|
||||
if (counter[value] > mostFrequentCount) {
|
||||
mostFrequentCount = counter[value];
|
||||
mostFrequentValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
return mostFrequentValue;
|
||||
}
|
||||
|
||||
/** @type {Worker} */
|
||||
// @ts-ignore
|
||||
const context = self; //eslint-disable-line
|
||||
context.onmessage = function (event) {
|
||||
console.log('received message', event);
|
||||
const {
|
||||
data: { _builder, isChanged = false, changedAttribute1 = null, changedAttribute2 = null },
|
||||
} = event;
|
||||
const result = buildDecisionTreeTSP(_builder, isChanged, changedAttribute1, changedAttribute2);
|
||||
context.postMessage(result);
|
||||
};
|
||||
@@ -21,7 +21,8 @@ export function buildDecisionTreeTSPW(
|
||||
_builder,
|
||||
isChanged = false,
|
||||
changedAttribute1 = null,
|
||||
changedAttribute2 = null
|
||||
changedAttribute2 = null,
|
||||
weight = 1
|
||||
) {
|
||||
//debugger;
|
||||
const builder = { ..._builder };
|
||||
@@ -60,7 +61,6 @@ export function buildDecisionTreeTSPW(
|
||||
|
||||
var right = 0,
|
||||
left = 0,
|
||||
weight = 0,
|
||||
sum1 = 0,
|
||||
sum2 = 0,
|
||||
L_weight = 0;
|
||||
@@ -262,7 +262,7 @@ export function buildDecisionTreeTSPW(
|
||||
matchedCount: match.length,
|
||||
notMatchedCount: notMatch.length,
|
||||
nodeSet: match.concat(notMatch),
|
||||
weight: weight,
|
||||
weight: L_weight,
|
||||
};
|
||||
//console.log(attributes);
|
||||
}
|
||||
@@ -302,3 +302,15 @@ function mostFrequentValue(items, attr) {
|
||||
|
||||
return mostFrequentValue;
|
||||
}
|
||||
|
||||
/** @type {Worker} */
|
||||
// @ts-ignore
|
||||
const context = self; //eslint-disable-line
|
||||
context.onmessage = function (event) {
|
||||
console.log('received message', event);
|
||||
const {
|
||||
data: { _builder, isChanged = false, changedAttribute1 = null, changedAttribute2 = null, weight = 1 },
|
||||
} = event;
|
||||
const result = buildDecisionTreeTSPW(_builder, isChanged, changedAttribute1, changedAttribute2, weight);
|
||||
context.postMessage(result);
|
||||
};
|
||||
@@ -3,6 +3,8 @@ import '../css/main.scss';
|
||||
import Tree from './Tree';
|
||||
import Navigation from './Navigation';
|
||||
import { builder as _builder_ } from '../services/Playground';
|
||||
import { AttributesProvider } from '../contexts/AttributesContext';
|
||||
import { BuilderConfigProvider } from '../contexts/BuilderConfigContext';
|
||||
|
||||
function Main() {
|
||||
const [builder, setBuilder] = useState({});
|
||||
@@ -19,12 +21,16 @@ function Main() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div id="main" className="main">
|
||||
<Navigation onPrepareConfig={StartDrawing} />
|
||||
{/* <Button onClick={start}>Drzewo</Button> */}
|
||||
<br></br>
|
||||
<div>{isReady ? <Tree options={builder} /> : <div>czekam na ciebie</div>}</div>
|
||||
</div>
|
||||
<BuilderConfigProvider>
|
||||
<AttributesProvider>
|
||||
<div id="main" className="main">
|
||||
<Navigation onPrepareConfig={StartDrawing} />
|
||||
{/* <Button onClick={start}>Drzewo</Button> */}
|
||||
<br></br>
|
||||
<div>{isReady ? <Tree options={builder} /> : <div>czekam na ciebie</div>}</div>
|
||||
</div>
|
||||
</AttributesProvider>
|
||||
</BuilderConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,14 +26,18 @@ import Builder from './Builder';
|
||||
import FormControlNumberInput from './FormControlNumberInput';
|
||||
import DrawerRoll from './DrawerRoll';
|
||||
import { useLoadingContext } from '../../contexts/LoadingContext';
|
||||
import { useAttributesContext } from '../../contexts/AttributesContext';
|
||||
import { useBuilderConfigContext } from '../../contexts/BuilderConfigContext';
|
||||
|
||||
/**
|
||||
* @typedef {import('../../utils/decision-tree.js').DecisionTreeBuilder} DecisionTreeBuilder
|
||||
*/
|
||||
|
||||
function Navigation({ onPrepareConfig }) {
|
||||
const { setBuilderConfig } = useBuilderConfigContext();
|
||||
const { isOpen, onToggle } = useDisclosure({ defaultIsOpen: true });
|
||||
const [dataSet, setDataSet] = useState(null);
|
||||
const [algorithm, setAlgorithm] = useState('c45');
|
||||
const [decisionAttribute, setDecisionAttribute] = useState(null);
|
||||
const [ignoredAttributes, setIgnoredAttributes] = useState([]);
|
||||
const [minItems, setMinItems] = useState(4);
|
||||
@@ -42,11 +46,7 @@ function Navigation({ onPrepareConfig }) {
|
||||
const [allAttrs, setAllAttributes] = useState([]);
|
||||
const [allClazz, setAllClasses] = useState([]);
|
||||
const [config, setConfig] = useState({});
|
||||
const [options, setOptions] = useState([
|
||||
{ value: 'hamburger', name: 'Hamburger' },
|
||||
{ value: 'fries', name: 'Fries' },
|
||||
{ value: 'milkshake', name: 'Milkshake' },
|
||||
]);
|
||||
const { attributes: options, onAttributesChange } = useAttributesContext();
|
||||
const { isLoading } = useLoadingContext();
|
||||
|
||||
function handleSelectDecision(value) {
|
||||
@@ -76,12 +76,8 @@ function Navigation({ onPrepareConfig }) {
|
||||
*/
|
||||
function handleGetAllAttributes({ allAttributes, data }) {
|
||||
console.log(allAttributes, data);
|
||||
let array = [];
|
||||
allAttributes.forEach(element => {
|
||||
array.push({ value: element, name: element });
|
||||
});
|
||||
setAllAttributes(allAttributes);
|
||||
setOptions(array);
|
||||
onAttributesChange(allAttributes);
|
||||
setDataSet(data);
|
||||
}
|
||||
/**
|
||||
@@ -92,17 +88,20 @@ function Navigation({ onPrepareConfig }) {
|
||||
allAttributes: allAttrs,
|
||||
allClasses: allClazz || [],
|
||||
categoryAttr: decisionAttribute,
|
||||
ignoredAttributes: ignoredAttributes,
|
||||
entropyThrehold: entropy,
|
||||
maxTreeDepth: maxDepth,
|
||||
minItemsCount: minItems,
|
||||
trainingSet: dataSet || [],
|
||||
ignoredAttributes,
|
||||
algorithm,
|
||||
};
|
||||
}
|
||||
|
||||
function handleDrawTree() {
|
||||
setConfig(prepareConfig());
|
||||
onPrepareConfig(prepareConfig());
|
||||
const config = prepareConfig();
|
||||
setConfig(config);
|
||||
onPrepareConfig(config);
|
||||
setBuilderConfig(config);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -132,6 +131,22 @@ function Navigation({ onPrepareConfig }) {
|
||||
<FileReader onChange={handleGetAllAttributes} isHeaders={false} />{' '}
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box w={'100%'}>
|
||||
<FormControl id="algorithm" width="auto" zIndex={2}>
|
||||
<FormLabel fontSize={['md', 'md', 'xs', 'sm', 'md']}>Algorithm</FormLabel>
|
||||
<SearchBar
|
||||
placeholder="Select algorithm"
|
||||
onChange={setAlgorithm}
|
||||
value={algorithm}
|
||||
options={['c45', 'tsp', 'tspw'].map(v => ({ value: v, name: v.toUpperCase() }))}
|
||||
multiple={false}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
{/* <FormHelperText width={size}>
|
||||
<Builder size={size} builder={prepareConfig()} />
|
||||
</FormHelperText> */}
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box w={'100%'}>
|
||||
<FormControl id="decisionAttr" width="auto" zIndex={2}>
|
||||
<FormLabel fontSize={['md', 'md', 'xs', 'sm', 'md']}>Decision attribute</FormLabel>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Input, Stack } from '@chakra-ui/react';
|
||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||
import { Search as SearchBar } from '../../SearchBar';
|
||||
import './alg-style.scss';
|
||||
import { useAttributesContext } from '../../../contexts/AttributesContext';
|
||||
|
||||
export default function TabC45({ attribute, value, changeValues }) {
|
||||
const { attributes: options } = useAttributesContext();
|
||||
|
||||
const onAttributeChange = value => changeValues({ attribute: value });
|
||||
const onPivotChange = event => changeValues({ pivot: +event.target.value });
|
||||
|
||||
return (
|
||||
<Stack spacing={3} direction="row">
|
||||
{/* <div className="alg-search"> */}
|
||||
<SearchBar
|
||||
value={attribute}
|
||||
onChange={onAttributeChange}
|
||||
options={options}
|
||||
multiple={false}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
{/* </div> */}
|
||||
<ChevronLeftIcon w={10} h={10} />
|
||||
<Input value={value} size="md" name="c45-value" onChange={onPivotChange} />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Input, Stack } from '@chakra-ui/react';
|
||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||
import { Search as SearchBar } from '../../SearchBar';
|
||||
import './alg-style.scss';
|
||||
import { useAttributesContext } from '../../../contexts/AttributesContext';
|
||||
|
||||
export default function TabTSP({ attribute, value, changeValues }) {
|
||||
const { attributes: options } = useAttributesContext();
|
||||
|
||||
const onAttributeChange = value => changeValues({ attribute: value });
|
||||
const onPivotChange = value => changeValues({ pivot: value });
|
||||
|
||||
return (
|
||||
<Stack spacing={3} direction="row">
|
||||
{/* <div className="alg-search"> */}
|
||||
<SearchBar
|
||||
value={attribute}
|
||||
onChange={onAttributeChange}
|
||||
options={options}
|
||||
multiple={false}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
{/* </div> */}
|
||||
<ChevronLeftIcon w={10} h={10} />
|
||||
<SearchBar
|
||||
value={value}
|
||||
onChange={onPivotChange}
|
||||
options={options}
|
||||
multiple={false}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import { Icon, Input, Stack } from '@chakra-ui/react';
|
||||
import { ChevronLeftIcon } from '@chakra-ui/icons';
|
||||
import { Search as SearchBar } from '../../SearchBar';
|
||||
import './alg-style.scss';
|
||||
import { useAttributesContext } from '../../../contexts/AttributesContext';
|
||||
|
||||
export default function TabTSPW({ attribute, value, weight, changeValues }) {
|
||||
const { attributes: options } = useAttributesContext();
|
||||
|
||||
const onAttributeChange = value => changeValues({ attribute: value });
|
||||
const onWeightChange = event => changeValues({ weight: +event.target.value });
|
||||
const onPivotChange = value => changeValues({ pivot: value });
|
||||
|
||||
return (
|
||||
<Stack spacing={3} direction="row">
|
||||
{/* <div className="alg-search"> */}
|
||||
<SearchBar
|
||||
value={attribute}
|
||||
onChange={onAttributeChange}
|
||||
options={options}
|
||||
multiple={false}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
{/* </div> */}
|
||||
<ChevronLeftIcon w={10} h={10} />
|
||||
<Input value={weight} size="sm" name="c45-value" onChange={onWeightChange} />
|
||||
<Icon viewBox="0 0 200 200" color="black.500">
|
||||
<path fill="currentColor" d="M 100, 100 m -75, 0 a 75,75 0 1,0 150,0 a 75,75 0 1,0 -150,0" />
|
||||
</Icon>
|
||||
<SearchBar
|
||||
value={value}
|
||||
onChange={onPivotChange}
|
||||
options={options}
|
||||
multiple={false}
|
||||
closeOnSelect={true}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
3
src/components/Node/Configurator/alg-style.scss
Normal file
3
src/components/Node/Configurator/alg-style.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.alg-search {
|
||||
width: 110%;
|
||||
}
|
||||
@@ -1,26 +1,33 @@
|
||||
import { Button, Tab, TabList, TabPanel, TabPanels, Tabs } from '@chakra-ui/react';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import TabC45 from './TabC45';
|
||||
import TabTSP from './TabTSP';
|
||||
import TabTSPW from './TabTSPW';
|
||||
|
||||
function Configurator({ onChange }) {
|
||||
const algoritms = ['C45', 'TSP', 'TSPW'];
|
||||
const algorithms = ['C45', 'TSP', 'TSPW'];
|
||||
function Configurator({ onChange, attribute, pivot, weight }) {
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [state, setState] = useState({
|
||||
param1: 'a',
|
||||
param2: 'b',
|
||||
param3: 'c',
|
||||
algorithm: algoritms[tabIndex],
|
||||
attribute,
|
||||
pivot,
|
||||
weight,
|
||||
algorithm: algorithms[tabIndex],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setState({ ...state, algorithm: algorithms[tabIndex] });
|
||||
}, [tabIndex]); // eslint-disable-line
|
||||
|
||||
function handleConfirmChange() {
|
||||
let newState = state;
|
||||
newState = { ...state, algorithm: algoritms[tabIndex] };
|
||||
setState(newState);
|
||||
onChange(newState);
|
||||
onChange({ ...state });
|
||||
}
|
||||
const handleValuesChange = values => {
|
||||
setState({ ...state, ...values });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Tabs isFitted variant="line" colorScheme="blackAplha" onChange={index => setTabIndex(index)}>
|
||||
<Tabs isFitted variant="line" colorScheme="blackAlpha" value={tabIndex} onChange={setTabIndex}>
|
||||
<TabList mb="1em">
|
||||
<Tab>C 4.5</Tab>
|
||||
<Tab>TSP</Tab>
|
||||
@@ -28,13 +35,18 @@ function Configurator({ onChange }) {
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<p>one!</p>
|
||||
<TabC45 attribute={state.attribute} value={state.pivot} changeValues={handleValuesChange} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<p>two!</p>
|
||||
<TabTSP attribute={state.attribute} value={state.pivot} changeValues={handleValuesChange} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<p>three!</p>
|
||||
<TabTSPW
|
||||
attribute={state.attribute}
|
||||
value={state.pivot}
|
||||
weight={state.weight}
|
||||
changeValues={handleValuesChange}
|
||||
/>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
|
||||
@@ -44,6 +44,7 @@ function Joint({ children, attr2, predicateName, pivot, match, notMatch, onChang
|
||||
attr2={attr2}
|
||||
predicateName={predicateName}
|
||||
pivot={pivot}
|
||||
onChange={onChange}
|
||||
onClose={onClose}
|
||||
onOpen={onOpen}
|
||||
/>
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import Configurator from './Configurator';
|
||||
|
||||
function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onClose }) {
|
||||
function handleChange(value) {
|
||||
console.log(value);
|
||||
function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onChange, onClose }) {
|
||||
const handleOnChange = value => {
|
||||
onClose();
|
||||
}
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} size="lg" isOpen={isOpen}>
|
||||
@@ -26,13 +26,13 @@ function ModalPopup({ attr2, predicateName, pivot, isOpen, nodeSet, onOpen, onCl
|
||||
</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
Text ( kolejny komponent z konfiguratorem){' '}
|
||||
{/* Text ( kolejny komponent z konfiguratorem){' '}
|
||||
{nodeSet.map((x, index) => (
|
||||
<div key={index}>
|
||||
{x.attr1001} {x[attr2]}-{x[pivot]} {x[attr2] < x[pivot] ? 'Match' : 'NotMatch'}
|
||||
</div>
|
||||
))}
|
||||
<Configurator onChange={handleChange} />
|
||||
))} */}
|
||||
<Configurator onChange={handleOnChange} attribute={attr2} pivot={pivot} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Spinner } from '@chakra-ui/react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useBuilderConfigContext } from '../../contexts/BuilderConfigContext';
|
||||
import { executeAlgorithm } from '../../utils/algorithm-executor';
|
||||
//import { addNode } from "./utils";
|
||||
|
||||
import Joint from './Joint';
|
||||
@@ -6,7 +9,9 @@ import Leaf from './Leaf';
|
||||
|
||||
const Node = props => {
|
||||
//console.log(props.node);
|
||||
const { builderConfig } = useBuilderConfigContext();
|
||||
const [highlighted, setHighlighted] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [node, setNode] = useState(props.node || {});
|
||||
useEffect(() => setNode(props.node || {}), [props.node, setNode]);
|
||||
const {
|
||||
@@ -33,21 +38,57 @@ const Node = props => {
|
||||
setTimeout(() => setHighlighted(false), 500);
|
||||
};
|
||||
|
||||
const onChange = useMemo(() => {
|
||||
return node => {
|
||||
console.log(node);
|
||||
// const index = match.findIndex(c => c.id === node.id);
|
||||
// console.log(index);
|
||||
// console.log(match);
|
||||
// match.splice(index, 1, node);
|
||||
// props.node.match = [...match];
|
||||
const onChange = options => {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
console.log('builderConfig', builderConfig);
|
||||
const builderModel = {
|
||||
...builderConfig,
|
||||
trainingSet: node.nodeSet,
|
||||
algorithm: options.algorithm.toLowerCase(),
|
||||
};
|
||||
}, [match, props.node]);
|
||||
const changeOptions = {
|
||||
isChanged: true,
|
||||
changedAttribute1: options.attr2 || node.attr2,
|
||||
changedAttribute2: options.pivot || node.pivot,
|
||||
weight: typeof options.weight === 'number' ? options.weight : node.weight,
|
||||
};
|
||||
console.log('partial builder model', builderModel, changeOptions);
|
||||
setLoading(true);
|
||||
executeAlgorithm(builderModel, changeOptions)
|
||||
.then(value => {
|
||||
setNode(value);
|
||||
props.requestChildChange(value);
|
||||
})
|
||||
.catch(e => console.error(e))
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
// const index = match.findIndex(c => c.id === node.id);
|
||||
// console.log(index);
|
||||
// console.log(match);
|
||||
// match.splice(index, 1, node);
|
||||
// props.node.match = [...match];
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(category, category ? 'Leaf' : 'Joint');
|
||||
}, [category]);
|
||||
|
||||
const requestChildChangeIfMatchIs = match => newNode => {
|
||||
const targetNode = {
|
||||
...node,
|
||||
[match ? 'match' : 'notMatch']: newNode,
|
||||
};
|
||||
setNode(targetNode);
|
||||
props.requestChildChange(targetNode);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <Spinner size="xl" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`node ${highlighted ? 'highlight' : ''}`} onClick={onNodeClicked}>
|
||||
{category ? (
|
||||
@@ -67,8 +108,8 @@ const Node = props => {
|
||||
onChange={onChange}
|
||||
nodeSet={nodeSet}
|
||||
>
|
||||
<Node node={match} onChange={onChange} />
|
||||
<Node node={notMatch} onChange={onChange} />
|
||||
<Node node={match} onChange={onChange} requestChildChange={requestChildChangeIfMatchIs(true)} />
|
||||
<Node node={notMatch} onChange={onChange} requestChildChange={requestChildChangeIfMatchIs(false)} />
|
||||
</Joint>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -12,5 +12,6 @@ export const Search = props => (
|
||||
printOptions="on-focus"
|
||||
closeOnSelect={props.closeOnSelect}
|
||||
placeholder={props.placeholder}
|
||||
value={props.value}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,13 @@ import { Box, Spinner } from '@chakra-ui/react';
|
||||
// import { dt } from '../services/TSP';
|
||||
import Node from './Node';
|
||||
import { useLoadingContext } from '../contexts/LoadingContext';
|
||||
import { executeAlgorithm } from '../utils/algorithm-executor';
|
||||
|
||||
/**
|
||||
* @typedef {import('../utils/decision-tree.js').DecisionTreeBuilder} DecisionTreeBuilder
|
||||
*/
|
||||
|
||||
const logTree = root => console.log(root);
|
||||
const logTree = root => console.log('ROOT', root);
|
||||
|
||||
/**
|
||||
* @param {Object} props
|
||||
@@ -32,36 +33,45 @@ const Tree = ({ options }) => {
|
||||
setRoot(null);
|
||||
setIsLoading(true);
|
||||
let terminated = false;
|
||||
const worker = new Worker('decision-tree.js');
|
||||
worker.onmessage = ({ data }) => {
|
||||
console.log('got a result', data);
|
||||
if (terminated) return;
|
||||
setRoot(data);
|
||||
worker.terminate();
|
||||
terminated = true;
|
||||
setIsLoading(false);
|
||||
};
|
||||
worker.postMessage({ _builder: { ...options } });
|
||||
executeAlgorithm(options)
|
||||
.then(value => {
|
||||
if (terminated) {
|
||||
return;
|
||||
}
|
||||
setRoot(value);
|
||||
})
|
||||
.catch(e => console.error(e))
|
||||
.finally(() => {
|
||||
if (!terminated) setIsLoading(false);
|
||||
terminated = true;
|
||||
});
|
||||
return () => {
|
||||
if (terminated) {
|
||||
return;
|
||||
}
|
||||
worker.terminate();
|
||||
terminated = true;
|
||||
};
|
||||
}, [options]);
|
||||
}, [options, setIsLoading]);
|
||||
|
||||
useEffect(() => logTree(root), [root]);
|
||||
|
||||
const requestChildChange = newRoot => setRoot(newRoot);
|
||||
|
||||
return (
|
||||
<div id="tree">
|
||||
<h1>Tree</h1>
|
||||
<p>
|
||||
<button onClick={logTree}>Log tree</button>
|
||||
<button onClick={() => logTree(root)}>Log tree</button>
|
||||
</p>
|
||||
{isLoading && <Spinner size="xl" />}
|
||||
<h2>Nodes:</h2>
|
||||
<Box>{!root ? <p>No tree to show</p> : <Node node={root} onChange={() => {}} />}</Box>
|
||||
<Box>
|
||||
{!root ? (
|
||||
<p>No tree to show</p>
|
||||
) : (
|
||||
<Node node={root} onChange={() => {}} requestChildChange={requestChildChange} />
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
26
src/contexts/AttributesContext.jsx
Normal file
26
src/contexts/AttributesContext.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
const AttributesContext = React.createContext();
|
||||
|
||||
export const AttributesProvider = ({ children }) => {
|
||||
const [attributes, setAttributes] = useState([
|
||||
{ value: 'hamburger', name: 'Hamburger' },
|
||||
{ value: 'fries', name: 'Fries' },
|
||||
{ value: 'milkshake', name: 'Milkshake' },
|
||||
]);
|
||||
|
||||
const onAttributesChange = input =>
|
||||
setAttributes(input.map(element => ({ value: element, name: element })));
|
||||
|
||||
return (
|
||||
<AttributesContext.Provider value={{ attributes, onAttributesChange }}>
|
||||
{children}
|
||||
</AttributesContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAttributesContext = () => {
|
||||
const { attributes, onAttributesChange } = useContext(AttributesContext);
|
||||
return { attributes, onAttributesChange };
|
||||
};
|
||||
19
src/contexts/BuilderConfigContext.jsx
Normal file
19
src/contexts/BuilderConfigContext.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
const BuilderConfigContext = React.createContext();
|
||||
|
||||
export const BuilderConfigProvider = ({ children }) => {
|
||||
const [builderConfig, setBuilderConfig] = useState(null);
|
||||
|
||||
return (
|
||||
<BuilderConfigContext.Provider value={{ builderConfig, setBuilderConfig }}>
|
||||
{children}
|
||||
</BuilderConfigContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useBuilderConfigContext = () => {
|
||||
const { builderConfig, setBuilderConfig } = useContext(BuilderConfigContext);
|
||||
return { builderConfig, setBuilderConfig };
|
||||
};
|
||||
@@ -7,7 +7,8 @@ $raisin-black: #1b1b1eff;
|
||||
* Main wrapper
|
||||
*/
|
||||
.select-search {
|
||||
width: auto;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
position: relative;
|
||||
font-family: 'Nunito Sans', sans-serif;
|
||||
box-sizing: border-box;
|
||||
@@ -25,6 +26,7 @@ $raisin-black: #1b1b1eff;
|
||||
.select-search__value {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select-search__value::after {
|
||||
|
||||
19
src/utils/algorithm-executor.js
Normal file
19
src/utils/algorithm-executor.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const workersMap = {
|
||||
c45: 'c45',
|
||||
tsp: 'tsp',
|
||||
tspw: 'tsp-weight',
|
||||
};
|
||||
|
||||
export function executeAlgorithm(options, changeOptions = {}) {
|
||||
console.log(options);
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = new Worker(`algorithms/${workersMap[options.algorithm]}-tree.js`);
|
||||
worker.onmessage = ({ data }) => {
|
||||
console.log('got a result', data);
|
||||
resolve(data);
|
||||
worker.terminate();
|
||||
};
|
||||
worker.onerror = reject;
|
||||
worker.postMessage({ _builder: { ...options }, ...changeOptions });
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user