Is it possible to express a boolean expression as a finite state machine such that the FSM accepts an input that matches the boolean expression ?
Is it possible to merge multiple such FSMs, and upon acceptation, to know which original FSM did match ?
Background: I'm trying to evaluate a lot of boolean expressions at once. I'm thinking that with the correct representation, maybe a DFA could do that super efficiently (i.e. better than O(n) with n = number of expressions).
I know this is a very old question but I did find myself in similar situations and I think there are some cases where an FSM might offer some help in complex boolean logic.
One example I outlined in a blog post of mine where I described the use of a state machine while walking (looping) through the tokens of a simple string.
The advantage in this case was that instead of each token in the parsing process having a dedicated boolean flag which other paths in the syntax tree would need to guard against I could just feed each token as an event to the FSM and let the machine fall into different states. I used Statechart actions to build up little op-codes while it was processing and then finally based on the final state either abort or go on to compiling the generated op-codes.
You will have to read the article to gain a context of what I said above. But the gist of it is that in some cases a series of booleans could be converted to event names and passed to an FSM for processing. In another example I had to choose which UI state I needed to render based on a set of overwhelming boolean logic.
Because some flags took precedence over others the resulting logic tree looked like this:
Figure 1 — example UML activity diagram
Which resulted in code that looked a bit like this:
Figure 2 — complex boolean logic code
let presenterFlags = {
showSpecialTypeTab: model.type === 'special',
showDefaultWarning: (model.type === 'special' && model.isDefault && !settings.enforced),
showDefaultInBrokenStateWarning: (model.type === 'special' && model.isDefault && settings.enforce),
disableSaveButton: (model.type === 'special' && model.isDefault && !settings.enforce) || !model.canWrite,
disableCancelButton: (model.type === 'special' && model.isDefault && !settings.enforce) || !model.canWrite,
disableDeleteButton: (model.type === 'special' && model.isDefault) || !model.canWrite,
disableEnforcementToggle: (model.type === 'special' && model.isDefault && !settings.enforced) || !model.canWrite,
disableAnotherToggle: (model.type === 'special' && model.isDefault) || !model.canWrite,
};
Which to me was too much for my brain to bear. So I leaned on the use of an FSM which resulted in a State diagram like:
Figure 3 — example UML State diagram
Using XState the code might have looked like this:
Figure 4 — example XState machine
let booleanMachine = Machine({
id: 'ExamplePresentationFlags',
strict: true,
initial: 'conditional',
context: {},
states: {
conditional: {
on: {
'EVALUATE': [
{ target: 'SpecialExample', cond: 'isSpecialExample' },
{ target: 'GenaricExample' },
],
},
},
GenaricExample: {
initial: 'conditional',
states: {
conditional: {
on: {
'': [
{ target: 'ReadOnly', cond: 'canNotWrite' },
{ target: 'Default', cond: 'isDefault' },
{ target: 'Writable' },
],
},
},
Writable: {},
Default: {},
ReadOnly: {
meta: {
disableSaveButton: true,
disableCancelButton: true,
disableDeleteButton: true,
},
},
},
},
SpecialExample: {
initial: 'conditional',
meta: { showSpecialTypeTab: true },
states: {
conditional: {
on: {
'': [
{ target: 'ReadOnly', cond: 'canNotWrite' },
{ target: 'Default', cond: 'isDefault' },
{ target: 'Writable' },
],
},
},
Writable: {},
ReadOnly: {
meta: {
disableSaveButton: true,
disableCancelButton: true,
disableDeleteButton: true,
disableAnotherToggle: true,
},
},
Default: {
initial: 'conditional',
states: {
conditional: {
on: {
'': [
{ target: 'Enforced', cond: 'isEnforced' },
{ target: 'Unenforced' },
],
},
},
Unenforced: {
meta: {
exampleWarning: 'default-read-only',
disableSaveButton: true,
disableCancelButton: true,
disableDeleteButton: true,
disableAnotherToggle: true,
},
},
Enforced: {
meta: {
exampleWarning: 'special-default-broken-enforce-state',
disableSaveButton: false,
disableCancelButton: false,
disableDeleteButton: true,
disableAnotherToggle: true,
},
},
},
},
},
},
},
}, {
guards: {
isSpecialExample: (ctx) => ctx.exampleType === 'special',
canNotWrite: (ctx) => !ctx.canWrite,
isEnforced: (ctx) => ctx.isEnforced,
isDefault: (ctx) => ctx.isDefault,
isNotDefault: (ctx) => !ctx.isDefault,
},
});
With a reducer like function:
Figure 5 — example XState reducer function
function presentorFlags({ canWrite, model, settings }) {
let machine = booleanMachine.withContext({
canWrite,
exampleType: model.type,
isEnforced: settings.enforced,
isDefault: model.isDefault,
});
let { meta } = machine.transition(machine.initialState, 'EVALUATE');
return Object.keys(meta)
.reduce((acc, key) => ({ ...acc, ...meta[key] }), {});
}
Understand that I agree this example is unconventional and also bigger. It did provide me the ability to understand the logic especially with the visualization tools at hand (i.e. Figure 3). At the time it allowed me to conceptualize all the edge case states and not have to worry about what each state of the UI meant as far as view code was concerned. Instead I could focus on the states themselves and what logic would get the machine into that state. Then I gave the reducer the actual boolean values and let the machine do the work. What I get back is just a set of UI flags that is easy to place in my template.
Again, maybe this is not better or maybe it is. The point is that it is possible to use a state machine to express boolean logic.
Related
When using findByIdAndUpdate() or findOneAndUpdate() (that is, updating the database atomically to avoid race conditions), is there a way to reference the existing data to conditionally update a field?
PersonModel.findByIdAndUpdate(
req.user.id,
{
$set: {
// set this to true
// if person.color === blue for example
hasFavoriteColor: true,
}
},
{ new: true },
(err, updatedDoc => {
if (err) return;
return updatedDoc;
});
);
I'm not sure you can do that, I see nowhere in the document. However, for your query particularly, you can add the condition about the color in the filter part, something like this :
PersonModel.findOneAndUpdate(
{_id : req.user.id, color : "blue"},
{
$set: {
// set this to true
// if person.color === blue for example
hasFavoriteColor: true,
}
},
{ new: true },
(err, updatedDoc => {
if (err) return;
return updatedDoc;
});
);
I want to see if an array contains one or more values inside it.
This is a model field definition:
keywords: {
type: Sequelize.ARRAY(Sequelize.STRING),
defaultValue: [],
allowNull: false,
},
This is the query I am testing:
context.app.services.jobOffers.Model.findAll({
where: {
keywords: {
$contains: ['hello'],
},
},
})
.then(result => {
console.log('RESULT:', result)
})
.catch(err => {
console.log('ERROR:', err)
})
This is the error I am receiving:
ERROR: TypeError: values.map is not a function
This is a reference to a similar issue, where I tried to replicate the solution but to no effect.
https://github.com/feathersjs-ecosystem/feathers-sequelize/issues/135
It seems the issue was with the Sequelize versions. This was made clear to me by one of the authors of Feathers.js #daffl
"In Sequelize v5 and later you need to use Symbols like [Op.contains] see (http://docs.sequelizejs.com/manual/querying.html#operators)"
So the fix is to change this line of code
where: {
keywords: {
[Op.contains]: ['hello'],
},
},
I need to filter a Tree (search for a node by comparing a text to a node attribute).
This fiddle is exactly what I need, but seems it is not working with ExtJS 6.2.
After a day trying to find out what is wrong in my code, I've decided to simply change the framework version in the fiddle from 4.2.1 (default) to 6.2.981 classic gray and can see the code is obsolete (removing all nodes or an arbitrary amount I can't see why).
Can someone verify that code and tell me how to port it to v6.2?
EDIT:
Almost there. This code is doing the job, but stops in deep level 2, not all the tree: JSFiddle
Ext.define('TreeFilter', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.treefilter',
collapseOnClear: true, // collapse all nodes when clearing/resetting the filter
allowParentFolders: false, // allow nodes not designated as 'leaf' (and their child items) to be matched by the filter
init: function(tree) {
var me = this;
me.tree = tree;
tree.filter = Ext.Function.bind(me.filter, me);
tree.clearFilter = Ext.Function.bind(me.clearFilter, me);
},
filter: function(value, property, re) {
var me = this,
tree = me.tree,
matches = [], // array of nodes matching the search criteria
root = tree.getRootNode(), // root node of the tree
property = property || 'text', // property is optional - will be set to the 'text' propert of the treeStore record by default
re = re || new RegExp(value, "ig"), // the regExp could be modified to allow for case-sensitive, starts with, etc.
visibleNodes = [], // array of nodes matching the search criteria + each parent non-leaf node up to root
viewNode;
if (Ext.isEmpty(value)) { // if the search field is empty
me.clearFilter();
return;
}
tree.expandAll(); // expand all nodes for the the following iterative routines
// iterate over all nodes in the tree in order to evalute them against the search criteria
root.cascadeBy(function(node) {
if (node.get(property).match(re)) { // if the node matches the search criteria and is a leaf (could be modified to searh non-leaf nodes)
matches.push(node) // add the node to the matches array
}
});
if (me.allowParentFolders === false) { // if me.allowParentFolders is false (default) then remove any non-leaf nodes from the regex match
Ext.each(matches, function(match) {
if (!match.isLeaf()) {
Ext.Array.remove(matches, match);
}
});
}
Ext.each(matches, function(item, i, arr) { // loop through all matching leaf nodes
root.cascadeBy(function(node) { // find each parent node containing the node from the matches array
if (node.contains(item) == true) {
visibleNodes.push(node) // if it's an ancestor of the evaluated node add it to the visibleNodes array
}
});
if (me.allowParentFolders === true && !item.isLeaf()) { // if me.allowParentFolders is true and the item is a non-leaf item
item.cascadeBy(function(node) { // iterate over its children and set them as visible
visibleNodes.push(node)
});
}
visibleNodes.push(item) // also add the evaluated node itself to the visibleNodes array
});
root.cascadeBy(function(node) { // finally loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional
viewNode.setVisibilityMode(Ext.Element.DISPLAY); // set the visibility mode of the dom node to display (vs offsets)
viewNode.setVisible(Ext.Array.contains(visibleNodes, node));
}
});
}
,
clearFilter: function() {
var me = this,
tree = this.tree,
root = tree.getRootNode();
if (me.collapseOnClear) {
tree.collapseAll();
} // collapse the tree nodes
root.cascadeBy(function(node) { // final loop to hide/show each node
viewNode = Ext.fly(tree.getView().getNode(node)); // get the dom element assocaited with each node
if (viewNode) { // the first one is undefined ? escape it with a conditional and show all nodes
viewNode.show();
}
});
}
});
// EXAMPLE
var store = Ext.create('Ext.data.TreeStore', {
root: {
expanded: true,
children: [{
text: "detention",
leaf: true
}, {
text: "homework",
expanded: false,
children: [{
text: "book report",
leaf: true
}, {
text: "algebra",
leaf: true
}]
}, {
text: "chores",
expanded: false,
children: [{
text: "do homework",
leaf: true
}, {
text: "walk dog",
leaf: true
}, {
text: "clean room",
leaf: true
}, {
text: "wash dishes",
leaf: true
}, {
text: "laundry",
leaf: true
}]
}, {
text: "buy lottery tickets",
leaf: true
}, {
text: "take over world",
leaf: true
}, {
text: "Sencha",
expanded: false,
children: [{
text: "Touch",
expanded: false,
children: [{
text: 'Viewport',
leaf: true
}, {
text: 'Panel',
leaf: true
}, {
text: 'Carousel',
leaf: true
}]
}, {
text: "ExtJS",
expanded: false,
children: [{
text: 'viewport.Viewport',
leaf: true
}, {
text: 'panel.Panel',
leaf: true
}, {
text: 'tree.Panel',
leaf: true
}]
}]
}]
}
});
Ext.create('Ext.tree.Panel', {
title: 'Simple Tree',
width: 200,
height: 150,
store: store,
rootVisible: false,
renderTo: Ext.getBody(),
plugins: [{
ptype: 'treefilter',
allowParentFolders: true
}],
dockedItems: [{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype: 'trigger',
triggerCls: 'x-form-clear-trigger',
onTriggerClick: function() {
this.reset();
this.focus();
},
listeners: {
change: function(field, newVal) {
var tree = field.up('treepanel');
tree.filter(newVal);
},
buffer: 250
}
}]
}]
});
Without using filter plugin i have added some useful code in change event and it works for all depth.
"dockedItems": [{
xtype: 'toolbar',
dock: 'top',
items: [{
xtype: 'textfield', //As of Ext JS 5.0 trigger class has been deprecated. It is recommended to use {#link Ext.form.field.Text Text Field}.
triggerCls: 'x-form-clear-trigger',
width: 300,
onTriggerClick: function () {
this.reset();
this.focus();
},
listeners: {
change: function (field, newVal) {
var reportBuilderStore = field.up('panel').getStore();
if (!Ext.isEmpty(field.value)) {
reportBuilderStore.filterBy(function (rec) {
var childs = !Ext.isEmpty(rec.get('children')) ? rec.get('children').map(function (x) {
return x.text;
}) : [];
var matched = false;
for (var val of childs) {
if (val.toUpperCase().match((field.value).toUpperCase())) {
matched = true;
break;
}
}
if (!Ext.isEmpty(rec.get('text').toUpperCase().match((field.value).toUpperCase())) || rec.get('text').toUpperCase() == "ROOT" || matched)
return true;
});
} else {
reportBuilderStore.clearFilter();
}
},
buffer: 250
}
}
]
}
],
I'm using this for doing an upsert:
Articles.update(
{ title: title },
{
title: title,
parent: type
},
{ upsert: true },
function(res) {
return console.log(res);
}
);
console.log(needToGetID);
Now I need to get the _id of the document which has been updated or inserted. I thought I can get that via callback, but res is undefined.
I assume there is just one unique document which is defined by the query.
Update
Forgot to mention that I'm using meteor...
The intent of .update() is to basically just "update" the matching document(s) ( as "multi" can also be applied here ) and be done with it, therefore that especially considering this "could" be applied to multiple documents then returning such information would not really make sense in the terms of that operation.
However if your intent is to modifiy a single "specific docucment", then the .findOneAndUpdate() method would apply assuming you are using mongoose:
Articles.findOneAndUpdate(
{ title: title },
{
title: title,
parent: type
},
{ upsert: true, new: true },
function(res) {
return console.log(res);
}
);
Also note the new: true which is important, as without it the default behavior is to return the document "before" it was modified by the statement.
At any rate, as the return here is the document that is matched and modified, then the _id and any other value is present in the response.
With meteor you can add a plugin to enable .findAndModify() which is the root method:
meteor add fongandrew:find-and-modify
And then in code:
Articles.findAndModify(
{
"query": { title: title },
"update": {
title: title,
parent: type
},
"upsert": true,
"new": true
},
function(res) {
return console.log(res);
}
);
Note that you should also really be using the $set operator, as without it the changes basically "overwrite" the target document:
Articles.findAndModify(
{
"query": { "title": title },
"update": {
"$set": {
"parent": type
}
},
"upsert": true,
"new": true
},
function(res) {
return console.log(res);
}
);
Hi I am encountering a wierd behavior with my application:when I modify an item and then my proxy doas a put request, the first time is ok, the second time it sends two requests: the first with the data of the previous one, the second one with the actual data, the third time it sends three requests, onmy system it is not a big issue, because at end I get the right value on my database, but on my customer's system the result it is not always correct. Then I would like to remove this behavior.
this is my store:
Ext.create('Ext.data.Store',
{
storeId: 'bbCompaniesStore',
model:'Company',
pageSize: pageSize,
proxy:
{
idProperty : '_id',
type: 'rest',
url: 'data/companies/',
autoload: true,
noCache: true,
sortParam: undefined,
actionMethods:
{
create : 'PUT',
read : 'GET',
update : 'POST',
destroy: 'DELETE'
},
reader:
{
type: 'json',
root: 'data',
totalProperty: 'total'
},
},// proxy
listeners: {
exception: function(proxy, response, operation) {
Ext.gritter.add({
title: MongoVision.text['action.' + operation.action] || operation.action,
text: (operation.error ? operation.error.statusText : null) || MongoVision.text.exception
});
// Ext JS 4.0 does not handle this exception!
switch (operation.action) {
case 'create':
Ext.each(operation.records, function(record) {
record.store.remove(record);
});
break;
case 'destroy':
Ext.each(operation.records, function(record) {
if (record.removeStore) {
record.removeStore.insert(record.removeIndex, record);
}
});
break;
}
}
}
}
);
this is my model:
Ext.define('Company',
{
extend: 'Ext.data.Model',
fields: [
{
name : 'id',
type : 'string'
},
{
name :'firm',
type : 'string',
allowBlank: false
},{
name : 'p'
},
{
name: 'linee'
},
{
name : 'c'
},
{
name : 'data',
type: 'date'
},
{
name :'note'
},
{
name :'paese'
},
{
name : 'email'
},
{
name : 'telefono'
},
{
name : 'type'
},
{
name : 'website'
},
{
name : 'session_id'
},
{
name : 'group_id'
}
],
proxy : {
type : 'rest',
url : 'data/companies/'
}
}
);
after googling around I found a similar issue for extjs3, with no solution, I think it is strange that after so long time, there is no yet a solution; it should work now
I faced the same issue, resolved it by simply passing
batchActions: true when creating the Proxy. The default behavior for 'rest' proxies is to turn off batching.... (See extjs/src/data/proxy/Rest.js)