Skip to content

Commit

Permalink
Graph 增强
Browse files Browse the repository at this point in the history
feat(Graph): link 与 unlink 约束基础实现

feat(Alias): 别名约束基础实现
  • Loading branch information
muzea committed Apr 8, 2019
1 parent 187b16b commit d55a842
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 67 deletions.
26 changes: 24 additions & 2 deletions ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ constraint n int 1 10 | flag1 flag2

## link

`graph` 使用,指明两个点是相连的
`graph` 使用,指明两个点是**直接**相连的

语法类似 `link(v1,v2)`,这里不能存在空格。

## unlink

`graph` 使用,指明两个点是不相连的
`graph` 使用,指明两个点是不**直接**相连的

语法类似 `unlink(v1,v2)`,这里不能存在空格。

Expand Down Expand Up @@ -77,3 +77,25 @@ constraint g graph graphNum nodeNum edgeNum
示例参见 [graph](sample/graph.txt)

也可以参考一个 [可视化的demo](https://muzea-demo.github.io/random-data/graph.html) 它的源码 [源码](graph.html),在输出数据有 `graph` 的时候会自动画图。


# alias 别名类型

语法

constraint aliasName alias anotherConstraintName

给一个约束一个别名,别名会单独持有一次取值,起到一个类似 `reference` 的效果

比如
```
constraint c int 1 10
constraint c1 alias c
constraint c2 alias c
```

用例见 [graph.unlink.txt](/sample/graph.unlink.txt)

当重新对 `c1` 取值时,总是会重新取一次 c 的值。

**这个语法只是一个过度方案**,后面会被 `reference` 取代。
168 changes: 124 additions & 44 deletions generate.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { shuffleArray, getRandomInt, range, valueOf, splitArray, Type, Flag } from './lib.mjs'
import { shuffleArray, getRandomInt, range, valueOf, splitArray, removeItem, Type, Flag } from './lib.mjs'

/**
* 判断一个边是不是在边的集合里面
Expand All @@ -18,52 +18,111 @@ function maxEdge(nodeNum) {
return (nodeNum * (nodeNum - 1)) / 2;
}

/**
* @param {number[][]} constraint
* @param {number[]} edge
* @returns {boolean} 是否满足约束
*/
function checkUnlink(constraint, edge) {
return !constraint.some(constraintItem => constraintItem[0] === edge[0] && constraintItem[1] === edge[1]);
}

/**
* 给定顶点的集合,边的数量,返回一个边的集合
* 这个图是一个联通图
*
* connectivity 构建 left (被现有边集覆盖到的顶点) right (未被现有边集覆盖到的顶点)
* 0. 根据 nodeList 过滤出有效约束
* 1. 若有 link 约束 则将约束边加到边集 涉及到的点加到left 然后执行 3 否则执行 2
* 2. 从 right 随机挑选一个点到 left
* 3. 从 right 中挑选一个点 r 从 left 中挑选一个点 l
* 4. 检查 l r 是否符合 unlink 约束
* 不符合则重新执行 3
* 符合则把边 (l, r)加到返回值里面 同时把点r从right移动到left
* 若此时 right 的长度为 0 则 执行 5 否则 执行 3
* 5. 从 left 中挑选两个点组成一个边 检查 (node1, node2) 是否符合 unlink 约束
* 符合则增加一个边 (l, r) 重新执行 5 直到 选出来的边满足 edgeNumber
* 不符合则重新执行 5
*
* validity unlink 目前只在加边的时候校验
* link 暂无严格校验
*
* @param {number[]} nodeList 顶点集合
* @param {number} edgeNumber 边的数量
* @param {{[key: string]: number[][]}} staticConstraint 一些静态约束
* @return {[number, number][]} 边的集合
*/
function getRandomSubGraph(nodeList, edgeNumber) {
function getRandomSubGraph(nodeList, edgeNumber, staticConstraint) {
const ret = [];
const left = [];
const right = nodeList.slice();
const nodeCount = nodeList.length;
const maxEdgeCount = maxEdge(nodeCount);
const neededEdgeCount = edgeNumber;
if (neededEdgeCount > maxEdgeCount) {
/**
* step 0.
*/
const effectiveUnlink = staticConstraint[Flag.unlink].filter(([node1, node2]) => {
return nodeList.includes(node1) && nodeList.includes(node2);
});
const effectiveLink = staticConstraint[Flag.link].filter(([node1, node2]) => {
return nodeList.includes(node1) && nodeList.includes(node2);
});
if ((neededEdgeCount + effectiveUnlink.length) > maxEdgeCount) {
// 这里是数据错误,其实是不对的
throw new Error('边的数量过大 无法生成图');
}
// 随机一个生成树出来,先整一个联通图
const sa = shuffleArray(nodeList.slice());
// 还未添加子节点的列表开始
let prevParentIndex = 0;
// 还未使用过的节点的开始
let index = 1;
// 剩余节点数量
let remainder = nodeList.length - 1;
while (remainder) {
const currentValue = sa[prevParentIndex];
const childCount = getRandomInt(1, remainder + 1);
let childIndex = 0;
while (childIndex !== childCount) {
const currentChildValue = sa[index + childIndex];
if (currentValue > currentChildValue) {
ret.push([currentChildValue, currentValue])
} else {
ret.push([currentValue, currentChildValue])
if (effectiveUnlink.length > edgeNumber) {
// 这里是数据错误,其实是不对的
throw new Error('[flag][link] 的约束过多 edgeNumber 无法满足要求');
}
if (effectiveLink.length) {
/**
* step 1.
*/
effectiveLink.forEach((edge) => {
if (edge[0] > edge[1]) {
edge = [edge[1], edge[0]];
}
childIndex += 1;
ret.push(edge);
left.push(edge[0], edge[1]);
removeItem(right, edge[0]);
removeItem(right, edge[1]);
edgeCount += 1;
});
} else {
/**
* step 2.
*/
const pickMe = getRandomInt(0, right.length);
/**
* @todo 这里存在一个问题,有可能选出来的这个点无法和剩下的点组成边
*/
left.push(right[pickMe]);
right.splice(pickMe, 1);
}
shuffleArray(right);
let whileCount = 0;
while (neededEdgeCount > ret.length && right.length && whileCount < 233) {
whileCount += 1;
/**
* step 3-4.
* @todo 这里也存在问题 可能会频繁取到无法组成边的点
*/
const rightNode = right[0];
const pickMe = getRandomInt(0, left.length);
const leftNode = left[pickMe];
const edge = [leftNode, rightNode].sort((a, b) => a - b);
if (!checkUnlink(effectiveUnlink, edge)) {
continue;
}
prevParentIndex += 1;
remainder -= childCount;
index += childCount;
ret.push(edge);
left.push(rightNode);
right.shift();
}
if (whileCount === 233) {
throw new Error('循环过多 应该是bug');
}
// 现在我们有了一个联通图了,接下来就是胡乱加边
// 其实我觉得可以直接完全随机出所有的边,然后随机化一下,把前面的直接挑出来用
// 一个图如果接近全联通的时候,随机加边的效率会比较低
// 度比较小的时候整出来所有的边效率会比较低
// 等我写完基本逻辑后对比一下 :)
while (neededEdgeCount > ret.length) {
let i1;
let i2;
Expand All @@ -73,17 +132,14 @@ function getRandomSubGraph(nodeList, edgeNumber) {
} else {
i2 = getRandomInt(i1 + 1, nodeCount)
}
const v1 = nodeList[i1];
const v2 = nodeList[i2];
const edge = [];
if (v1 > v2) {
edge.push(v2, v1);
} else {
edge.push(v1, v2);
const edge = [left[i1], left[i2]].sort((a, b) => a - b);
if (hasEdge(ret, edge)) {
continue;
}
if (!hasEdge(ret, edge)) {
ret.push(edge);
if (!checkUnlink(effectiveUnlink, edge)) {
continue;
}
ret.push(edge);
}
return ret;
}
Expand Down Expand Up @@ -155,6 +211,12 @@ function generate(list, constraint) {
value.nodeList = rret.nodeList;
break;
}
case 'alias': {
value.type = Type.alias;
const rret = getRandomValue(store, constraintItem.aliasName);
value.value = rret.value;
break;
}
}

store[name] = value.value;
Expand All @@ -163,14 +225,32 @@ function generate(list, constraint) {

function getRandomGraph(store, config) {
let ret = [];
const { nodeNum, edgeNum, graphNum } = config;
const { nodeNum, edgeNum, graphNum, flag } = config;
const graphValue = getValueFromString(store, graphNum);
const nodeValue = getValueFromString(store, nodeNum);
const edgeValue = getValueFromString(store, edgeNum);
const nodeList = range(1, nodeValue + 1);
const staticConstraint = {
[Flag.link]: [],
[Flag.unlink]: [],
};
if (Array.isArray(flag[Flag.link])) {
flag[Flag.link].forEach(([n1, n2]) => {
const node1 = getValueFromString(store, n1);
const node2 = getValueFromString(store, n2);
staticConstraint[Flag.link].push([node1, node2].sort((a, b) => a - b));
});
}
if (Array.isArray(flag[Flag.unlink])) {
flag[Flag.unlink].forEach(([n1, n2]) => {
const node1 = getValueFromString(store, n1);
const node2 = getValueFromString(store, n2);
staticConstraint[Flag.unlink].push([node1, node2].sort((a, b) => a - b));
});
}
// 等一个优雅的可以指定每个图有多少边的语法
if (graphValue === 1) {
Array.prototype.push.apply(ret, getRandomSubGraph(nodeList, edgeValue));
Array.prototype.push.apply(ret, getRandomSubGraph(nodeList, edgeValue, staticConstraint));
} else {
const nodeListArr = splitArray(nodeList, graphValue);
// 检查这个分法是不是可以满足边的约束
Expand All @@ -193,16 +273,16 @@ function generate(list, constraint) {
throw new Error('每一个子图都应该是联通的');
}
if (isLast) {
Array.prototype.push.apply(ret, getRandomSubGraph(list, edgeValue - usedEdge));
Array.prototype.push.apply(ret, getRandomSubGraph(list, edgeValue - usedEdge, staticConstraint));
} else {
let subRealEdge = Math.min(subMaxEdge, Math.floor(p * subMaxEdge));
subRealEdge = Math.max(subRealEdge, list.length - 1);
if ((subRealEdge + usedEdge) > edgeValue) {
Array.prototype.push.apply(ret, getRandomSubGraph(list, edgeValue - usedEdge));
Array.prototype.push.apply(ret, getRandomSubGraph(list, edgeValue - usedEdge, staticConstraint));
usedEdge = edgeValue;
} else {
usedEdge += subRealEdge;
Array.prototype.push.apply(ret, getRandomSubGraph(list, subRealEdge));
Array.prototype.push.apply(ret, getRandomSubGraph(list, subRealEdge, staticConstraint));
}
}
});
Expand Down
16 changes: 16 additions & 0 deletions lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,29 @@ function valueOf(expStr, getValue) {
return func(env);
}

/**
*
* @param {any[]} arr
* @param {any} item
*/
function removeItem(arr, item) {
const index = arr.indexOf(item);
if (index >= 0) {
arr.splice(index, 1);
}
}

const Flag = {
shuffle: 'shuffle',
link: 'link',
unlink: 'unlink',
};

const Type = {
int: 'int',
set: 'set',
graph: 'graph',
alias: 'alias',
};

export {
Expand All @@ -110,6 +125,7 @@ export {
valueOf,
range,
splitArray,
removeItem,
Flag,
Type,
}
44 changes: 24 additions & 20 deletions parse.mjs
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import { isString } from './lib.mjs'

/**
* repeat n CONTENT
*
* repeat group n
* repeat line
* repeat line
* end group
*
*
* {
* type: 'line'
* repeat: string
* template: string,
* }
*
* {
* type: 'group'
* repeat: string
* children: line[]
* }
*
* @param {string} str
*/
function isFunctionFlag (str) {
return str.includes('(')
}

function parseFlag(flagPart) {
const ret = {};
if (isString(flagPart)) {
flagPart.split(' ').forEach((flagItem) => {
const key = flagItem.trim();
if (key) {
ret[key] = true;
if (isFunctionFlag(key)) {
const preStr = key.replace(/[(),]/g, ' ')
const [flagName, ...param] = preStr.split(' ')
if (!Array.isArray(ret[flagName])) {
ret[flagName] = []
}
ret[flagName].push(param)
} else {
ret[key] = true;
}
}
});
}
Expand Down Expand Up @@ -96,6 +90,12 @@ function parse(input) {
flag
};
}
function addAliasConstraint(name, aliasName) {
constraint[name] = {
aliasName,
type: 'alias',
};
}
const list = input.split('\n');
let index = 0;
const end = list.length;
Expand All @@ -118,6 +118,10 @@ function parse(input) {
addGraphConstraint(name, other, flag);
break;
}
if (type === 'alias') {
addAliasConstraint(name, other[0]);
break;
}
break;
}
if (isRepeatGroup(line)) {
Expand Down
Loading

0 comments on commit d55a842

Please sign in to comment.