├── .gitignore ├── .img ├── 1557392429041.png ├── 1557393644750.png ├── 1557394592083.png ├── 1557472239275.png └── flow.png ├── README.md └── jsdemo ├── GA1.js ├── common1.js ├── data.js ├── ga-demo.html ├── lib ├── echarts.min.js ├── element-ui │ ├── fonts │ │ └── element-icons.woff │ ├── index.css │ └── index.js └── vue.js ├── old ├── GA.data.js ├── GA.js ├── common.js ├── data.js ├── ga.data.html ├── ga.html └── lib │ ├── echarts.min.js │ ├── grid.locale-cn.js │ ├── jquery-1.11.0.min.js │ ├── jquery-ui-custom.css │ ├── jquery.jqGrid.min.js │ └── ui.jqgrid.css └── view.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | **/.idea 3 | **/*.iml -------------------------------------------------------------------------------- /.img/1557392429041.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzlz000/class-sort/93beb3a80fcc497ae2a19f9a70a8cc92b92c58cf/.img/1557392429041.png -------------------------------------------------------------------------------- /.img/1557393644750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzlz000/class-sort/93beb3a80fcc497ae2a19f9a70a8cc92b92c58cf/.img/1557393644750.png -------------------------------------------------------------------------------- /.img/1557394592083.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzlz000/class-sort/93beb3a80fcc497ae2a19f9a70a8cc92b92c58cf/.img/1557394592083.png -------------------------------------------------------------------------------- /.img/1557472239275.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzlz000/class-sort/93beb3a80fcc497ae2a19f9a70a8cc92b92c58cf/.img/1557472239275.png -------------------------------------------------------------------------------- /.img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzlz000/class-sort/93beb3a80fcc497ae2a19f9a70a8cc92b92c58cf/.img/flow.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 基于遗传算法的自动排课算法 2 | 3 | 基于我对此问题的尝试性研究,目前仅仅停留在demo阶段,提供思路,仅供参考 4 | 5 | 可以运行jsdemo目录下的html文件 来体验效果 6 | 7 | ### 模型 8 | 9 | 排课,即求课程、地点、时间三个集合的笛卡尔积 10 | $$ 11 | \{课程\} ×\{地点\} × \{时间\} = \{排课结果\} 12 | $$ 13 | 14 | #### 约束条件 15 | 16 | 这个结果集合需要满足排课的需要,则需满足以下约束条件: 17 | 18 | 1. 同一时间、地点不能安排多次课程 19 | 2. 参与课程的个人(学生、老师等)在同一时间不能上多门课程 20 | 3. 同一门课程在一个排课周期内的多次排课不能在同一时间 21 | 4. 尽可能满足课程所需的其他要求,如偏好的上课时间、合适的地点 22 | 23 | #### 约束的分类 24 | 25 | ##### 硬约束和软约束 26 | 27 | 排课结果必须满足其要求才能做到最基本的可用的条件为**硬性约束**,如上述1、2、3 28 | 29 | 尽可能去满足的约束条件为 **软性约束**,如 4 30 | 31 | ##### 静态约束和动态约束 32 | 33 | 我把条件1、4定义为 **静态约束**,2、3定义为 **动态约束**,在后面的算法中要通过不同的逻辑进行处理。 34 | 35 | 这里解释下这两个约束定义: 36 | 37 | - **静态约束**: 条件不随结果的变化而变化,如教室A星期一早上第1节只能安排唯一的课程。如课程a需要安排在早上,人数为100,则需要匹配的教室容量不小于100,选择time<=4的时段(如果定义了时段1,2,3,4为早晨)。静态约束的处理相对简单 38 | 39 | - **动态约束** : 条件随着排课结果的变化而变化,我把它分为两种情况 40 | - 动态时间约束: 如星期一早上第1节安排了老师A的课程,那么老师A的其他课程均不能安排在该时段。但是如果老师A没有安排周一早上第1节课,该要求便不存在了,同门课程的多次安排同理。按上课的学生/班级/专业区分的约束,这个维度的约束和教师的约束条件完全一样,可以按照和老师一样的方式处理 41 | 42 | - 动态地点约束: 于时间约束一般为排斥不同,地点约束一般为吸引。同一门课从的多次安排需要尽量安排在相同的或者相近的教室,以相同为优先,而相近的教室需要有办法来获取,把这一条约束成为动态地点约束(该部分算法在demo中未实现) 43 | 44 | ### 算法实现 45 | 46 | 下文会通过一个例子来说明问题 47 | 48 | ![1557392429041](.img/1557392429041.png) 49 | 50 | #### 约束的处理 51 | 52 | ##### 时间-地点数组 53 | 54 | 对于静态约束,通过结果集的模型来处理。 55 | 56 | 一天安排n节课,一个排课周期为m天,总共有s个可用教室。从0至n×m×s-1的整数,代表了所有可能的排课时间地点的组合。排课结果就是这个整数集合中的一部分,只要保证值不重复,便满足了“同一时间、地点不能安排多次课程”。通过这个值来获日期时间或者地点也非常的方便: 57 | 58 | ```javascript 59 | const MAX_DAY = 7; 60 | const MAX_TIME = 12; 61 | const TIME_LEN = MAX_DAY * MAX_TIME; 62 | // 获取结果值 63 | let getIndex = (day,time,room)=>{ 64 | return TIME_LEN * room + MAX_TIME*day + time; 65 | } 66 | // 根据 index 获取 日期 时间 教室的索引 67 | let getPosition = (index)=>{ 68 | var room= Math.floor(index/TIME_LEN); 69 | var daytime = index%TIME_LEN; 70 | var day = Math.floor(daytime/ MAX_TIME); 71 | var time = daytime%MAX_TIME; 72 | return {day,time,room}; 73 | } 74 | // 获取时间索引,可用于比较是否为相同时间 75 | let getDayTime = (index)=>{ 76 | return index%TIME_LEN; 77 | } 78 | ``` 79 | 80 | 运行 81 | 82 | ```javascript 83 | getIndex(1,2,3) 84 | > 266 85 | getPosition(266) 86 | > {day: 1, time: 2, room: 3} 87 | ``` 88 | 89 | 以上述例子中的情况为例,结果集中的每一个值应该从2×2×3= 12 即 0-11中取得并不可重复 90 | 91 | 它们代表的意思是 92 | 93 | | 地点\时间 | 第1天第1节 | 第1天第2节 | 第2天第1节 | 第2天第2节 | 94 | | :-------: | :--------: | :--------: | :--------: | :--------: | 95 | | 教室1 | 0 | 1 | 2 | 3 | 96 | | 教室2 | 4 | 5 | 6 | 7 | 97 | | 教室3 | 8 | 9 | 10 | 11 | 98 | 99 | 我们把这个数组定义为 “**时间-地点数组**”,下文中不再赘述 100 | 101 | ##### 动态约束 102 | 103 | 我把所有运行中会改变的约束条件定义为“动态约束”,可以抽象它们的特性通过统一的方式进行处理。我们把不能安排在存在时间互斥的课程集合定义为一个**约束组**,后续的约束判断会和其相关,在后文中逐步解释。 104 | 105 | 在上述例子中,约束组是这样的,这个关系在整个排课过程中也是固定不变的,每次都可以根据这个来判断是否有冲突 106 | 107 | ![1557394592083](.img/1557394592083.png) 108 | 109 | 实际上上面的约束关系之间还略有不同,例如同一门课从的多次安排需要尽量安排在相同的或者相近的教室,以相同为优先,而相近的教室需要有办法来获取,把这一条约束成为动态地点约束(该部分算法在demo中未实现) 110 | 111 | 为了方便后续计算,需要一个map来维护课程下标和约束组下标的关系,如果约束组按照上面图中的顺序排列,则关系为: 112 | 113 | ```json 114 | { 115 | 0: [0, 1, 4], 116 | 1: [0, 4], 117 | 2: [0, 4], 118 | 3: [1], 119 | 4: [2], 120 | 5: [2], 121 | 6: [3, 5], 122 | 7: [3, 5], 123 | 8: [1, 2, 6], 124 | 9: [1, 2, 6] 125 | } 126 | ``` 127 | 128 | 129 | 130 | #### 染色体 131 | 132 | 每一个可行的结果集就是一条染色体 133 | 134 | 使用上述的例子,10门课的安排的结果集可能是这样的 135 | 136 | ![1557393644750](.img/1557393644750.png) 137 | 138 | #### 适应度 139 | 140 | 适应度代表一节课程在一个时间-地点索引上安排是否合适,这个值和硬性约束、软性约束相关,是算法参数调整的重点。 141 | 142 | 我们约定以 **值越小代表越合适**,原因是方便累加,不可用的安排可以通过函数返回一个很大的值。而越大约合适碰到不可用时要清空其适应度值为0,虽然实际上保证了后面的加权随机中完全不可取得,但是这不是个好事情,即使是完全不可用的时间我们也要让他可以以极小的概率被选择,这样,即使出现完全排不下课的情况,也可以得到一个可供参考的解而不是报错或者陷入死循环。 143 | 144 | 上述例子中,课程1-1 需要 A类型教室,人数40,按常理来说,教室1最合适,教室2浪费了容量, 教室3类型不同不可用。 145 | 146 | 约定教室类型匹配作为硬性条件(实际情况并非一定如此,在工程化中要对这些约束条件进行可配置),给一个相对很大的适应度值 例如 100, 147 | 148 | 教室容量,容量小于人数不可用,大于其容量,越接近其容量 适应度最小,可定义函数: 149 | 150 | ```java 151 | rate = 教室容量/课程人数 152 | if (rate<1) { 153 | return 100; // 容量小于人数不可用 154 | } else if (rate<1.5) { 155 | return 0; // 非常合适 156 | } else if (rate<3) { 157 | return 5; // 浪费容量 158 | } else { 159 | return 10; // 浪费容量 160 | } 161 | ``` 162 | 163 | ##### 适应度&概率数组 164 | 165 | 各种适应度判断条件都有一个对应的适应度值函数,然后在初始化的过程中计算每节课程对应所有时间-地点的适应度数组,这个数组在整个排课算法过程中是固定的。所有 **静态约束** 的信息均在该数组中体现. 166 | 167 | 仍以上述例子做说明: 168 | 169 | 适应度数组的长度等于**时间-地点数组** 170 | 171 | 形如: 172 | 173 | ``` 174 | [12, 1000, 1005, 4, 1, 0, 0, 1, 2, 5, 1122, 1] 175 | ``` 176 | 177 | 相对很大的数字一般代表着这个时段非常不可用,很可能有硬性冲突,越小的值说明该时间-地点越适合当前课程, 178 | 179 | 按概率取值时,会按照适应度数组的倒数来获取,我把它定义为 **适应度概率数组** 180 | 181 | #### 轮盘赌随机 182 | 183 | 轮盘赌随机其实就是按照**适应度概率数组**的加权随机,即适应度越小获取的概率越高 184 | 185 | 返回数组中随机到的位置的下标,并且在下一轮随机中不会再选择到该下标(下面代码中的skip代表这个意思),保证在一个染色体生成的过程中,一个下标只能得到一次,即满足了约束条件1,并且越合适的安排被选择的概率越高。即适应度数组便是遗传算法中的“自然选择” 186 | 187 | 轮盘赌代码如下,实际上在普通的轮盘赌基础上,加入了跳过和过滤的功能 188 | 189 | ```javascript 190 | /** 191 | * 带跳过的轮盘赌 经测试,轮盘赌是整个算法中耗时最多的部分,看看能否优化。 192 | * @param {array} weight 课程概率数组(适应度数组的倒数) (下标: 元素编号,值: 该元素对应的概率) 193 | * @param {array} skip 可选参数 跳过序号的集合 若skip=[1,2]则不会选择数组中下标为1,2的元素,其他值按原概率分部 194 | * @param {function} negativeFilter 消极过滤函数,接受参数 时空索引, 195 | * 返回 ture 则按照消极对待 会以极小的概率选择此值,可以认为只有没有其他任何选择时才会选中此值 196 | * @returns {object} 返回 {index,bad} 概率数组中某一元素的下标 及是否为较差选择 197 | */ 198 | function roll(weights,skip,negativeFilter) { 199 | var sum = 0; 200 | var length = weights.length; 201 | let badSet = new Set(); 202 | for (var i=0; i= rand) { 228 | return i; 229 | } 230 | } 231 | return -1; 232 | } 233 | ``` 234 | 初始化染色体代码 235 | ```javascript 236 | /** 237 | * 随机生成一组染色体 轮盘赌 238 | */ 239 | function generateChromosome(){ 240 | 241 | var gene = []; 242 | // 从所有课程中随机取值 而不是从第一个开始 避免每次都是排在数组最开始位置的课程有最优先的选择,使种群的基因更丰富 243 | var randomIndex = new RandomIndex(adaptability.length); 244 | var lessonIndex; 245 | while((lessonIndex=randomIndex.next())>=0){ 246 | let adapt = adaptability[lessonIndex] 247 | // 此处获取冲突中的动态约束关联的课程已占用的时间段 248 | let conflictSet = conflict.relatedDayTime(lessonIndex,gene); 249 | // 当下表存在于冲突集合中,被过滤函数判断为"极低概率选择" 250 | let result = roll(adapt,gene,roomTime => conflictSet.has(indexUtil.getDayTime(roomTime))); 251 | let dayTimeRoom = result; 252 | gene[lessonIndex] = dayTimeRoom; 253 | } 254 | // 初始化时所谓的“badSelect”即时间冲突的差选择为0(已经通过轮盘赌把选中概率降到了极低) 255 | var chromosome = new Chromosome(gene,0); 256 | 257 | return chromosome; 258 | } 259 | ``` 260 | 261 | 262 | 263 | #### 迭代 264 | 265 | 经过上述步骤便可获得一个初始化的基本可用的染色体,它满足了静态约束条件,对于约束1,严格满足,对于约束4 越适合的安排被选中的概率越大。**如果我们直接选择选择最大的适应度,也就无所谓遗传迭代了,如果追求快速获得一个基本可行排课结果,算法可以到此为止,它的执行效率是比较高的**,设置迭代次数为0即可。 266 | 267 | 然后便进入了遗传算法迭代的部分,目的是找出适应度的倒数尽可能最大结果。首先 初始化 n个染色体,这n个染色体(排课结果集)就是遗传算法中的“种群”,以此为基础进行迭代 268 | 269 | 270 | 271 | ##### 动态适应度 272 | 273 | 上述适应度概率数组解决了静态约束的问题,每轮迭代的交叉变异还会引入新的冲突不满足**约束条件2、3**,原先我尝试通过严格的检查来确保不会发生此情况,但是带来的问题是得到的解相对较差,效率低下。因此定义动态适应度解决动态冲突问题,上文中我们已经建立了和每节排课关联的**约束组**,从每个约束组中计算是否存在时间冲突的情况,每个冲突记为一个 **坏选择**,在计算适应度的时候,根据坏选择数量进行扣分,这样冲突较多的染色体便不再优秀。这个扣分策略目前采用简单的 数量*分数 的方式,分数可配置。 274 | 275 | 染色体代码 276 | 277 | ```javascript 278 | /** 279 | * 染色体类 包括基因序列 适应度 280 | * @param {number[]} geneOrder 基因序列 281 | * @param {number} badSelect 差选择数量 282 | */ 283 | function Chromosome(geneOrder,badSelect){ 284 | this.badSelect = badSelect; 285 | this.setGeneOrder=(geneOrder)=>{ 286 | _setGeneOrder(geneOrder); 287 | } 288 | let _setGeneOrder = (geneOrder) => { 289 | this.geneOrder = geneOrder; 290 | this.adapt = 0; 291 | for (let lessonIndex = 0; lessonIndex < geneOrder.length; lessonIndex++) { 292 | // 此时 i 代表课程序列 val 代表时空序列 293 | const spaceTimeIndex = geneOrder[lessonIndex]; 294 | this.adapt += adaptability[lessonIndex][spaceTimeIndex]; 295 | } 296 | this.adapt -= badSelect *CONFIG.badSelectVal; 297 | } 298 | _setGeneOrder(geneOrder); 299 | } 300 | ``` 301 | 302 | ##### 淘汰策略 303 | 304 | 初始化/一轮迭代 完成的染色体要检测其适应度值。并不是所有的染色体都有机会进入迭代,我们要根据一定的策略选择适应度相对大的结果,这也是算法调优的重要环节。如果每次都选择最高的结果,那么实际上会陷入局部极大值当中,难以获得尽量追求全局最优的效果。 305 | 306 | 淘汰策略目前我有有以下几种思路 307 | 308 | 1. 选取适应度相对较大的一部分(比例可配置)参与交叉变异,目前就是这种方式。 309 | 310 | 2. 使用所谓**模拟退火**的方式:即确定一个适应度的接受准则, 当适应度提高,接收该值,当适应度降低,以一定概率接收。 随着迭代的进行,这个概率会越来越小。 311 | 312 | 一个满足要求的接收结果的概率函数: 313 | 314 | $$ 315 | p = \begin{cases} 1 \quad if \quad (\Delta a>0) 316 | \\ exp(k\Delta a × t) \quad if \quad (\Delta a<=0) \end{cases} 317 | $$ 318 | 319 | 其中k为可调节的参数,a为适应度值,t为迭代次数 320 | 321 | 322 | 323 | ##### 交叉 324 | 325 | 经过上述步骤获得的染色体集合,对他们随机选择两组进行交叉 326 | 327 | 交叉是算法的难点之一,因为染色体的生成满足了很多的约束条件,交叉会破坏这些约束条件,但是不通过这个步骤又无法获得更优的结果,总结其规律我定义了一个**关联基因**来解决问题。 328 | 329 | 这个步骤比较麻烦我画图说明: 330 | 331 | ![157472239275](.img/1557472239275.png) 332 | 333 | 整体流程代码如下: 334 | 335 | ```js 336 | var childGene = [];// 以父基因为基础添加母基因来实现交叉 337 | var set = new Set(); // 用于保存已经关联的基因 338 | var relatedGenes = []; //关联基因的索引 339 | // 遍历寻找关联基因 340 | for (let i = 0; i < fatherGene.length; i++) { 341 | if(set.has(i)) 342 | continue; 343 | let gene = motherGene[i]; 344 | let point = fatherGene.indexOf(gene); 345 | let related = [i]; 346 | // 当父基因组中存相同的基因且不是初始索引位置 347 | // 关联基因寻找完成的标志为: 348 | // 1 形成闭环 例如 [1,2,3] 和 [2,3,1] 349 | // 2 不再存在相同的基因 例如 [1,2,3] 和 [2,3,5] 350 | while (point >=0 && point!=i) { 351 | gene = motherGene[point]; 352 | set.add(point); 353 | related.push(point); 354 | point = fatherGene.indexOf(gene); 355 | } 356 | relatedGenes.push(related); 357 | } 358 | for (let i = 0; i < relatedGenes.length; i++) { 359 | let releted = relatedGenes[i]; 360 | let fromFather = Math.random()<0.5; 361 | 362 | for (let j = 0; j < releted.length; j++) { 363 | let index = releted[j]; 364 | childGene[index] = fromFather? fatherGene[index]: motherGene[index]; 365 | } 366 | } 367 | return childGene; 368 | ``` 369 | 370 | ##### 变异 371 | 372 | 变异是一个增加种群丰富程度的方式,但不能概率过高,一个较低的变异概率可以增加种群丰富程度,提高得到解的适应度,较高的变异概率会埋没掉优秀的基因,变异概率的参数也是可配置的。 373 | 374 | ```js 375 | /** 376 | * 变异 377 | * @param {number[]} geneOrder 基因序列 378 | */ 379 | function vary(geneOrder){ 380 | for (let i = 0; i < geneOrder.length; i++) { 381 | // CONFIG.varyRate 为变异概率,经过不断测试,我设置为了0.05 382 | if(Math.random() 388 | conflictSet.has(indexUtil.getDayTime(dayTimeRoom))); 389 | if (result>=0) { 390 | geneOrder[i] = result; 391 | } 392 | } 393 | } 394 | } 395 | ``` 396 | 397 | #### 整体流程 398 | 399 | ![](.img/flow.png) 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /jsdemo/GA1.js: -------------------------------------------------------------------------------- 1 | // 遗传算法 2 | 3 | // TODO 把联排数量不同的课程分段进行 先排4节-3节-2节这样 4 | /* 5 | TODO 迭代结束后可以做一些转换,例如把同一门课换到同一个教室里 6 | 比如 A课程在 教室1-教室10的权重是相同的,那么他的多节课会被随机地分配到这10个教室 7 | 8 | 另一种方式为动态转换 9 | 10 | */ 11 | /* 12 | 时空索引: 13 | 时间(day,time),空间(room) 他们以确定的关系组成一个唯一的正整数(详见common1.js IndexUtil类), 14 | 相同的时空索引意味着占据了相同时间的相同教室,根据时空索引我们可以非常方便(详见IndexUtil,简单的加减乘除取余计算) 15 | 的获得对应索引的 day time room在数组中的索引值,从而避免了复杂的数组嵌套 16 | 静态约束: 17 | 一门课的教室类型、校区、老师、人数、时间需求等在排课之前就已经确定的条件,详见 fixedCondition 18 | 排课单元: 19 | 在时间上互相有约束关系的一组课程,例如同一个老师的多门课程,同一个培养方案中的多门课程,同一门课在一周内的不同课时 20 | 动态时间约束: 21 | 排课单元内的课程安排是动态的,他们之间的约束关系也需要动态的检查,在初始化基因组、交叉、变异时都必须考虑动态约束 22 | 详见 common1.js-Conflict类 和其实体 conflict变量的使用 23 | 交叉: 24 | 详见 cross()函数 25 | 变异: 26 | 详见 vary()函数 27 | 28 | */ 29 | 30 | /** 算法参数 */ 31 | const CONFIG = { 32 | /** 染色体数量 */ 33 | chromosomeNum : 20, 34 | /** 迭代次数 */ 35 | iteratorNum : 200, 36 | /** 不参加交叉直接保留给下一代的最好基因 应该小于crossRate 在小数据量下测试0.15是最佳选择 */ 37 | survivalRate : 0.15, 38 | /** 有权繁殖下一代的比例 按照适应度排序 取值(0,1) 取值过大难以淘汰劣势基因,取值过小容易过早陷入局部最优解 */ 39 | crossRate : 0.5, 40 | /** 变异比例 取值[0,1) 不宜太大,较大会减少优势基因,并且影响运行效率 */ 41 | varyRate : 0.05, 42 | // /** 发生冲突时 重试的次数 在减少冲突和保证效率间找到平衡 */ 43 | // conflictRetry : 100, 44 | 45 | // 以下为适应度参数 46 | /** 初始的适应度值 一个不太大的正整数 */ 47 | initAdaptValue : 30, 48 | /** 一个绝对值较大的负数,代表完全不可用的适应度,保证适应度+unable<=0*/ 49 | unable : -10000, 50 | /** 差选择扣分:生成基因过程中产生的冲突数量,每个冲突是一个“差选择”,每个差选择扣除一定的适应度值的比例, 51 | * 不宜太大,会影响基因的丰富程度,迭代效果下降 */ 52 | badSelectVal : 15 53 | } 54 | 55 | // start 变量定义 56 | /** 一天的课时 */ 57 | const TIMES = 12; 58 | /** 一周上课日 规定 0-6为周一-周日 */ 59 | const DAYS = 7; 60 | /** 61 | * 不可用时段 62 | * 例如{ 0:[1,2],4:[4,7] } 代表不可用时间段为周1 1,2节课 周5 4,7节课 63 | */ 64 | const unableDayTime = {}; 65 | /** 初始排一周课时的上课日遍历顺序 */ 66 | var daySels = [1,4,3,5,2,6]; 67 | 68 | /** 校区 */ 69 | var Zone = {SP:'SP', JD:'JD'}; 70 | /** 教室类别 */ 71 | var RoomType = {NORMAL:'normal', COMPUTER:'computer', PLAYGROUD:'playgroud', LAB:'lab', MEDIA:'media'}; 72 | 73 | /** 教学楼 zone-校区 floors-楼层数(0为操场)*/ 74 | var buildings = [ 75 | {id:70001, zone:Zone.SP, floors:20}, 76 | {id:70002, zone:Zone.SP, floors:5}, 77 | {id:70003, zone:Zone.SP, floors:8}, 78 | {id:70004, zone:Zone.SP, floors:12}, 79 | {id:70005, zone:Zone.SP, floors:0}, 80 | {id:70006, zone:Zone.JD, floors:10}, 81 | {id:70007, zone:Zone.JD, floors:18}, 82 | {id:70008, zone:Zone.JD, floors:3}, 83 | {id:70009, zone:Zone.JD, floors:0} 84 | ]; 85 | var buildingsMap = {}; 86 | 87 | /** 教室 */ 88 | var classRooms = [ 89 | {id:50001, roomType: RoomType.NORMAL, capacity:40, building:70001, floor:5, roomNO:502}, 90 | {id:50002, roomType: RoomType.NORMAL, capacity:120, building:70001, floor:12, roomNO:1201}, 91 | {id:50003, roomType: RoomType.MEDIA, capacity:30, building:70002, floor:3, roomNO:302}, 92 | {id:50004, roomType: RoomType.NORMAL, capacity:40, building:70004, floor:4, roomNO:406}, 93 | {id:50005, roomType: RoomType.PLAYGROUD, capacity:140, building:70005, floor:0, roomNO:1}, 94 | {id:50006, roomType: RoomType.NORMAL, capacity:60, building:70006, floor:5, roomNO:505}, 95 | {id:50007, roomType: RoomType.PLAYGROUD, capacity:150, building:70009, floor:0, roomNO:1} 96 | ]; 97 | var classroomsMap = {}; 98 | 99 | /** 100 | * 课程教学班 101 | * 原名 classes 改为 lessons不会引起歧义 102 | * 以后还要加入 班级约束 103 | */ 104 | var lessonClasses = [ 105 | {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP}, 106 | {id:30102, course:10001, teacher:20001, studentNum:28, zone:Zone.JD}, 107 | {id:30201, course:10002, teacher:20002, studentNum:90, zone:Zone.SP}, 108 | {id:30301, course:10003, teacher:20001, studentNum:48, zone:Zone.SP}, 109 | {id:30302, course:10003, teacher:20003, studentNum:52, zone:Zone.SP}, 110 | {id:30303, course:10003, teacher:20004, studentNum:28, zone:Zone.SP}, 111 | {id:30401, course:10004, teacher:20005, studentNum:28, zone:Zone.SP}, 112 | {id:30402, course:10004, teacher:20005, studentNum:28, zone:Zone.JD}, 113 | {id:30501, course:10005, teacher:20003, studentNum:24, zone:Zone.SP}, 114 | {id:30502, course:10005, teacher:20003, studentNum:24, zone:Zone.SP}, 115 | {id:30503, course:10005, teacher:20004, studentNum:28, zone:Zone.SP}, 116 | {id:30504, course:10005, teacher:20004, studentNum:24, zone:Zone.SP}, 117 | {id:30601, course:10006, teacher:20006, studentNum:28, zone:Zone.SP}, 118 | {id:30602, course:10006, teacher:20006, studentNum:28, zone:Zone.JD}, 119 | {id:30701, course:10007, teacher:20008, studentNum:28, zone:Zone.SP}, 120 | {id:30702, course:10007, teacher:20008, studentNum:28, zone:Zone.SP}, 121 | {id:30801, course:10008, teacher:20009, studentNum:28, zone:Zone.SP}, 122 | {id:30802, course:10008, teacher:20009, studentNum:28, zone:Zone.SP}, 123 | {id:30803, course:10008, teacher:20009, studentNum:28, zone:Zone.JD}, 124 | {id:30901, course:10009, teacher:20012, studentNum:88, zone:Zone.SP} 125 | ]; 126 | /** 课程教学班 每一个连续开设的课程都是一个lesson 包括一个教学班一周内的多次开课 */ 127 | var lessons = []; 128 | 129 | /** 教师 disableTime-不可排课的时间段(302-周三的第3/4课时*/ 130 | var teachers = [ 131 | {id:20001, name:'教师1', disableTime: null}, 132 | {id:20002, name:'教师2', disableTime: {day:[2,4], time:[1,2,3,4]}}, 133 | {id:20003, name:'教师3'}, 134 | {id:20004, name:'教师4'}, 135 | {id:20005, name:'教师5'}, 136 | {id:20006, name:'教师6'}, 137 | {id:20007, name:'教师7'}, 138 | {id:20008, name:'教师8'}, 139 | {id:20009, name:'教师9'}, 140 | {id:20010, name:'教师10'}, 141 | {id:20011, name:'教师11'}, 142 | {id:20012, name:'教师12'} 143 | ]; 144 | var teachersMap = {}; 145 | 146 | /** 课程 147 | * onceHour: 上一次课的课时,实际应用中根据统一规则来计算,若有例外则单独指定或手工排课 148 | * timeRequire: 上课的特殊时间要求 day-周几(为null则无特殊要求), time-课时范围(为null则无特殊要求) 149 | */ 150 | var courses = [ 151 | {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 152 | {id:10002, name:'计算机原理', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 153 | {id:10003, name:'大学英语', weekHour:4, totalHour:16, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 154 | {id:10004, name:'JAVA语言', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 155 | {id:10005, name:'英语听力', weekHour:2, totalHour:14, roomType:RoomType.MEDIA, onceHour:2, timeRequire:{day:[2,4], time:[1,2,3,4]}}, 156 | {id:10006, name:'通信原理', weekHour:4, totalHour:48, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 157 | {id:10007, name:'数据结构', weekHour:2, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 158 | {id:10008, name:'经济学基础', weekHour:6, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 159 | {id:10009, name:'体育', weekHour:2, totalHour:20, roomType:RoomType.PLAYGROUD, onceHour:2, timeRequire:{day:null, time:[1,2,3,4,5,6,7,8]}} 160 | ]; 161 | var coursesMap = {}; 162 | 163 | /** 任务处理适应度结果集([迭代次数][染色体编号] = 适应度) */ 164 | var logGenData = []; 165 | 166 | /** 167 | * 结果集 即 染色体合集 即 种群. 168 | * 例如 8门课 排2天*8段*2教室=32个时空位置 则每个染色体的长度为8 按课程数组的顺序排列 每门课的的时空位置在0-31中取值,不可重复. 169 | 形如: 170 | [ 171 | [1,3,2,20,15,13,6,7], // chromosomeSet 中存的是 Chromosome 对象 其中包含了这个基因序列 172 | [32,3,2,12,1,5,4,7], 173 | ... 174 | ] 175 | */ 176 | var chromosomeSet; 177 | 178 | /** 适应度矩阵(下标:染色体编号、值:该染色体的适应度) 179 | * 下表为课程编号,内部数组为该课程对于每个时空编号的适应度 180 | * 例如 8门课 排2天*8段*2教室=32个时空位置 则每个染色体的长度为8 按课程数组的顺序排列 每个内部数组长度为32 181 | 形如: 182 | [ 183 | [1,1,1,1,2,3,4,5....,0,12,344,12], //长度为32 classes[0] 184 | [32,1,1,11,2,3,4,5....,0,12,344,12], //长度为32 classes[1] 185 | ... 一共8个数组 186 | ] 187 | */ 188 | var adaptability; 189 | // end 变量定义 190 | 191 | const indexUtil = new IndexUtil(DAYS,TIMES);indexUtil.hh 192 | const logger = new Logger(Logger.LEVEL.INFO); 193 | const conflict = new Conflict(); 194 | // 算法入口 195 | function gaMain(){ 196 | var timer = new Timer(); 197 | // 初始化map缓存 198 | initCaches(); 199 | logger.info("初始化map完成\tcost:",timer.get()); 200 | // 初始化课程 包括动态冲突关联 201 | initLessons(); 202 | logger.info("初始化课程完成\tcost:",timer.get()); 203 | // 初始化适应度 204 | initAdapt(); 205 | logger.info("初始化适应度完成\tcost:",timer.get()); 206 | initChromosome(); 207 | logger.info("初始化基因完成\tcost:",timer.get()); 208 | logGenData = []; 209 | gaIterate(CONFIG.iteratorNum); 210 | logger.info("迭代完成\t\t\tcost:",timer.get()); 211 | } 212 | /** 213 | * 父母基因交叉获得下一代染色体 214 | * @param {*} father Chromosome对象 215 | * @param {*} mother Chromosome对象 216 | */ 217 | function cross(fatherGene,motherGene){ 218 | /* 219 | 交叉时 有一个棘手的问题 220 | 例如 221 | 父基因 [1,2,9,4,7,6,8,5] 222 | 母基因 [1,3,2,4,5,6,7,8] 223 | 子基因随机从两边获取 224 | 子基因 [1,2,2,4,7,6,7,8] 225 | 则获得了不可用的基因序列 因为(时间*教室)的序号重复了 即 选择了同一时间的同一教室 得到了冲突的解 226 | 因此我们引入一个“相关基因”的概念: 227 | 在上述例子中 228 | 父母基因存在两对相关基因 2,9--3,2 7,8,5--5,7,8 相关基因必须当做一个基因做整体的调整 229 | 对应数组中的索引位置为 (2,3) (4,6,8) 230 | 上述父母基因可以表示为关联基因的形式,数组中存储的是下标 231 | relatedGene = [[0],[1,2],[3],[4,6,8],[5]] 相关基因作为一个数组当做一个单独的基因进行选择 232 | 233 | */ 234 | var childGene = [];// 以父基因为基础添加母基因来实现交叉 235 | var set = new Set(); // 用于保存已经关联的基因 236 | var relatedGenes = []; //关联基因的索引 237 | // 遍历寻找关联基因 238 | for (let i = 0; i < fatherGene.length; i++) { 239 | if(set.has(i)) 240 | continue; 241 | let gene = motherGene[i]; 242 | let point = fatherGene.indexOf(gene); 243 | let related = [i]; 244 | // 当父基因组中存相同的基因且不是初始索引位置 245 | // 关联基因寻找完成的标志为: 246 | // 1 形成闭环 例如 [1,2,3] 和 [2,3,1] 247 | // 2 不再存在相同的基因 例如 [1,2,3] 和 [2,3,5] 248 | while (point >=0 && point!=i) { 249 | gene = motherGene[point]; 250 | set.add(point); 251 | related.push(point); 252 | point = fatherGene.indexOf(gene); 253 | } 254 | relatedGenes.push(related); 255 | } 256 | for (let i = 0; i < relatedGenes.length; i++) { 257 | let releted = relatedGenes[i]; 258 | let fromFather = Math.random()<0.5?true:false; 259 | 260 | for (let j = 0; j < releted.length; j++) { 261 | let index = releted[j]; 262 | childGene[index] = fromFather? fatherGene[index]: motherGene[index]; 263 | } 264 | } 265 | return childGene; 266 | } 267 | /** 268 | * 检查子代基因的动态冲突并计数 269 | * @param {number[]} gene 基因序列 270 | */ 271 | function checkBadSelect(gene){ 272 | let badSelect = 0; 273 | for (let i = 0; i < gene.length; i++) { 274 | // let adapt = adaptability[i]; 275 | let check= conflict.relatedDayTime(i,gene); 276 | if(check.has(indexUtil.getDayTime(gene[i]))){ 277 | badSelect ++; 278 | } 279 | } 280 | return badSelect; 281 | } 282 | /** 283 | * 变异 284 | * @param {number[]} geneOrder 基因序列 285 | */ 286 | function vary(geneOrder){ 287 | for (let i = 0; i < geneOrder.length; i++) { 288 | if(Math.random() conflictSet.has(indexUtil.getDayTime(dayTimeRoom))); 292 | if (result>=0) { 293 | geneOrder[i] = result; 294 | } 295 | } 296 | } 297 | } 298 | /** 299 | * 遗传迭代 300 | * @param {number} num 迭代的次数 301 | */ 302 | function gaIterate(num){ 303 | var timer = new Timer() 304 | logger.info("---开始迭代---"); 305 | for (let i = 0; i < num; i++) { 306 | logger.info("---第"+i+"代\tcost:",timer.get(),"---"); 307 | let nextGen = []; // 下一代染色体集合 308 | logGenData.push(chromosomeSet.map(chromosome => chromosome.adapt)); 309 | 310 | //保留最好的基因直接传给下一代 311 | var survivalCount = chromosomeSet.length *CONFIG.survivalRate; 312 | for (let i = 0; i < survivalCount && nextGen.length < CONFIG.chromosomeNum; i++) { 313 | nextGen.push(chromosomeSet[i]) 314 | } 315 | // 有权参与交叉的基因数量 316 | var crossCount = (chromosomeSet.length-1)*CONFIG.crossRate; // randomInt函数是包括末尾的 所以 length-1 317 | // survival==1会导致程序死循环 虽然一般不会这么设置这么小的值 318 | if (crossCount<=1) 319 | throw "PARAM.crossRate 的值过小 没有足够的父代够供交叉配对" 320 | while(nextGen.length < CONFIG.chromosomeNum ){ 321 | let fatherIndex = randomInt(0,crossCount); 322 | let motherIndex = randomInt(0,crossCount); 323 | if (fatherIndex == motherIndex){ // 不可自交 324 | continue; 325 | } 326 | let father = chromosomeSet[fatherIndex]; 327 | let mother = chromosomeSet[motherIndex]; 328 | // 交叉 329 | let child = cross(father.geneOrder,mother.geneOrder); 330 | // 变异 331 | vary(child); 332 | let badSelect = checkBadSelect(child); 333 | nextGen.push(new Chromosome(child,badSelect)) 334 | } 335 | chromosomeSet = nextGen; 336 | chromosomeSet.sort((a,b) => b.adapt-a.adapt); //染色体根据适应度由大到小排序 337 | } 338 | } 339 | function initLessons(){ 340 | lessons = []; 341 | // TODO 动态时间冲突检测如果多起来,可写成类似静态条件的工厂方法 342 | /** 教师冲突 */ 343 | let teacherConflict = {}; 344 | lessonClasses.forEach(function(lesson, index) { 345 | let course = coursesMap[lesson.course]; 346 | let timesPerWeek = course.weekHour/course.onceHour; 347 | let conflictArr = []; // 同一课程的不同课时不能在同一时间 需要检查冲突 348 | for (let i = 0; i < timesPerWeek; i++) { 349 | lessons.push(lesson); 350 | if (!teacherConflict[lesson.teacher]){ 351 | teacherConflict[lesson.teacher] = []; 352 | } 353 | teacherConflict[lesson.teacher].push(lessons.length-1); 354 | conflictArr.push(lessons.length-1); 355 | } 356 | // 同门课程动态冲突 ,一周两节 则保证不在相邻天 3-4节保证不在同一天 否则保证不在同个半天 357 | switch (timesPerWeek) { 358 | case 2: 359 | conflict.add(conflictArr,Conflict.Scope.skipDay,"同门课程"+lesson.id); 360 | break; 361 | case 3: 362 | case 4: 363 | conflict.add(conflictArr,Conflict.Scope.day,"同门课程"+lesson.id); 364 | break; 365 | default: 366 | conflict.add(conflictArr,Conflict.Scope.halfDay,"同门课程"+lesson.id); 367 | } 368 | }); 369 | for (const key in teacherConflict) { 370 | const conflictArr = teacherConflict[key]; 371 | conflict.add(conflictArr ,Conflict.Scope.time,"教师"+key); 372 | 373 | } 374 | } 375 | function initCaches() { 376 | courses.forEach(function(course, index) { 377 | coursesMap[course.id] = course; 378 | }); 379 | teachers.forEach(function (teacher, index) { 380 | teachersMap[teacher.id] = teacher; 381 | }); 382 | buildings.forEach(function(building, index) { 383 | buildingsMap[building.id] = building; 384 | }); 385 | classRooms.forEach(function(classroom, index) { 386 | classroomsMap[classroom.id] = classroom; 387 | 388 | }); 389 | } 390 | /** 391 | * 初始化染色体 392 | */ 393 | function initChromosome (){ 394 | chromosomeSet = []; 395 | var timer = new Timer(); 396 | for (let i = 0; i < CONFIG.chromosomeNum; i++) { 397 | let chromosome = generateChromosome(); 398 | chromosomeSet.push(generateChromosome()); 399 | logger.info("初始化基因",i,timer.get()) 400 | } 401 | chromosomeSet.sort((a,b) => b.adapt-a.adapt); 402 | } 403 | /** 404 | * 随机生成一组染色体 轮盘赌 405 | */ 406 | function generateChromosome(){ 407 | 408 | var gene = []; 409 | // 从所有课程中随机取值 而不是从第一个开始 避免每次都是排在数组最开始位置的课程有最优先的选择,使种群的基因更丰富 410 | var randomIndex = new RandomIndex(adaptability.length); 411 | var lessonIndex; 412 | var badSelect = 0; 413 | while((lessonIndex=randomIndex.next())>=0){ 414 | let adapt = adaptability[lessonIndex] 415 | let conflictSet = conflict.relatedDayTime(lessonIndex,gene); 416 | let result = roll(adapt,gene,roomTime => conflictSet.has(indexUtil.getDayTime(roomTime))); 417 | let dayTimeRoom = result; 418 | gene[lessonIndex] = dayTimeRoom; 419 | } 420 | var chromosome = new Chromosome(gene,badSelect); 421 | 422 | return chromosome; 423 | } 424 | 425 | /** 426 | * 根据约束条件初始化适应度 427 | */ 428 | function initAdapt(){ 429 | adaptability = []; 430 | for (let i = 0; i < lessons.length; i++) { 431 | adaptability[i] = []; 432 | for (let d = 0; d < DAYS; d++) { 433 | for (let t = 0; t < TIMES; t++) { 434 | for (let r = 0; r < classRooms.length; r++) { 435 | // 判断不可用时段 436 | if (unableDayTime[d] && unableDayTime[d].includes(t)){ 437 | adaptability[i][index] = 0; 438 | continue; 439 | } 440 | var index = indexUtil.getIndex(d,t,r); 441 | adaptability[i][index] = adapt(i,d,t,r); 442 | } 443 | } 444 | } 445 | } 446 | } 447 | // start 适应度条件 448 | /** 449 | * 根据condition数组中的所有条件来改变适应度的值 450 | * @param {number} lessonIndex 课程数组中的下标 451 | * @param {number} day 排课周期中的第几天 452 | * @param {number} time 一天的第几个课时 453 | * @param {number} roomIndex 教室数组中的下标 454 | */ 455 | function adapt(lessonIndex,day,time,roomIndex){ 456 | // lesson {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP} 457 | // course {id:10001, name:'高等数学', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 458 | var lesson = lessons[lessonIndex]; 459 | var value = CONFIG.initAdaptValue; 460 | 461 | for (let i = 0; i < fixedCondition.length; i++) { 462 | if (value == 0) // 不可用时段 463 | continue; 464 | const conditionFunction = fixedCondition[i]; 465 | value +=conditionFunction(lesson,day,time,roomIndex,value) 466 | if( value<0 ) { 467 | value = 0; 468 | } 469 | } 470 | return value; 471 | } 472 | 473 | /* 474 | * 满足的约束(冲突) √ 代表已解决 475 | * 476 | * √ 同一时间片一个教师只能上一门课程 477 | * √ 某一教室的某一时间片只能被一门课程占用 478 | * √ 某班学生在某一时间片只能被安排在一个教室上课 479 | * √ 某课程m必须安排在预定的时间片n上 480 | * √ 某教师m在时间片n时不能上课 481 | * √ 课程对教室的要求 482 | * √ 同一课程的一次课分配在同一时段(上午/下午/晚上) 483 | * √ 较高的教室利用率(上课人数 484 | * √ 晚上/周二下午/周六尽量不排课 (这个应该是动态设置的) 485 | * √ 同一教学班任务不要在同一天内连续的开课,一周内同一门课程均匀分布 动态冲突 a)一周2节保证不在相邻天排课 b)3-5节保证不在同一天排课 486 | * √ 老师不能在同一个半天上跨校区课 487 | */ 488 | /** 489 | * 此处为固定条件 动态条件需要每次迭代修改适应度 490 | * 例如 课程的时间教室类型选择属于固定条件 491 | * 老师或学生的时间冲突属于动态条件 492 | * 493 | */ 494 | var fixedCondition = [ 495 | // 注意适用性越普遍的条件放在数组的越前方 可增加效率 496 | // 单节时间需求 497 | function(lesson,day,time,roomIndex,value){ 498 | /** 联排不可用的时间段 2-两节联排 3-三节联排 4-四节联排 从0开始 */ 499 | var unitUnableTime = { 500 | 1: [], 501 | 2: [1,3,5,7,9,11], 502 | 3: [2,3,6,7,8,9,10,11], 503 | 4: [1,2,3,5,6,7,8,9,10,11] 504 | }; 505 | // lesson {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP} 506 | // {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null} 507 | var course = coursesMap[lesson.course]; 508 | var unable = unitUnableTime[course.onceHour]; 509 | if(unable.includes(time)){ 510 | return CONFIG.unable; 511 | } 512 | return 0; 513 | }, 514 | // 公共不可用时间 此例中为周日不排课 晚上/周二下午/周六尽量不排课 早上排课更优先 515 | function (lesson, day, time, roomIndex, value) { 516 | if (day == 6) { // 周日不排课 517 | return CONFIG.unable; 518 | } 519 | if (time >= 8 || (day == 1 && time >= 4)) { // 晚上/周二下午不排课 520 | return CONFIG.unable; 521 | } 522 | let val = 0; 523 | if (day == 5){ // 周六尽量不排课 524 | val += -10; 525 | } 526 | if (time < 4) { // 早上最优先 527 | val += 6; 528 | } 529 | if (time>=4 && time < 6) { // 下午前两节优先 530 | val += 3; 531 | } 532 | return val; 533 | }, 534 | // 课程时间需求 course timeRequire 535 | function(lesson,day,time,roomIndex,value){ 536 | // timeRequire:{day:[2,4], time:[1,2,3,4]} 537 | // timeRequire:{day:null, time:[1,2,3,4]} 538 | // timeRequire:{day:[1,3], time:[1,2,3,4]} 539 | var timeRequire = coursesMap[lesson.course].timeRequire; 540 | if (timeRequire){ 541 | if (timeRequire.day &&timeRequire.time ){ 542 | if(timeRequire.day.includes(day) && timeRequire.time.includes(time)){ 543 | logger.debug('满足课程 day time:',timeRequire.day,timeRequire.time); 544 | return 10; 545 | } 546 | }else if(timeRequire.day){ 547 | if(timeRequire.day.includes(day)){ 548 | logger.debug('满足课程 day:',timeRequire.time); 549 | return 5; 550 | } 551 | }else if(timeRequire.time){ 552 | if(timeRequire.time.includes(time)){ 553 | logger.debug('满足课程 time:',timeRequire.time); 554 | return 5; 555 | } 556 | }else{ 557 | logger.debug('不满足课程 timeRequire:',timeRequire.time); 558 | return CONFIG.unable; 559 | } 560 | } 561 | return 0; 562 | }, 563 | // 教室类型 564 | function(lesson,day,time,roomIndex,value){ 565 | var course = coursesMap[lesson.course]; 566 | var room = classRooms[roomIndex]; 567 | if(buildingsMap[room.building].zone != lesson.zone){ 568 | logger.debug('不满足校区要求 lesson:',lesson.id); 569 | return CONFIG.unable; 570 | } 571 | if(course.roomType != room.roomType){ 572 | logger.debug('不满足教室类型 lesson:',lesson.id,'roomtype',room.roomType); 573 | return CONFIG.unable; 574 | } 575 | var ratio = lesson.studentNum/room.capacity; 576 | if (ratio > 1) { 577 | logger.debug('教室容量不足 lesson:',lesson.id,'studentNum',lesson.studentNum,'capacity',room.capacity); 578 | return CONFIG.unable; 579 | } 580 | if (ratio > 0.8) { 581 | return 10; 582 | } 583 | if (ratio < 0.5) { 584 | return -5; 585 | } 586 | 587 | return 0; 588 | } 589 | ]; 590 | // end 适应度条件 591 | 592 | 593 | -------------------------------------------------------------------------------- /jsdemo/common1.js: -------------------------------------------------------------------------------- 1 | //一些工具 2 | // 小写字母开头的是普通函数 3 | // 大写字母开头的均为类 使用是请new对象 4 | 5 | 6 | /** 7 | * 数组随机取值 8 | * 随机获取数组中的一个值 直到数组取光为止 9 | * 当为正整数时 随机返回 0~n的值直到数组取光为止 10 | * @param {} value number 或者 Array 11 | */ 12 | function RandomIndex(value){ 13 | var _arr; 14 | if(value instanceof Array) 15 | _arr = [].concat(value); // 克隆数组 16 | else{ 17 | _arr = []; 18 | for (let i = 0; i < value; i++) { 19 | _arr.push(i); 20 | 21 | } 22 | } 23 | this.next = ()=>{ 24 | if(_arr.length>0){ 25 | var ran = Math.floor(Math.random()*_arr.length); 26 | return _arr.splice(ran,1)[0] 27 | } 28 | } 29 | } 30 | 31 | /** 32 | * 根据时间空间获取索引 也可以反过来获取 注意要用room在数组中的index而不是Id 33 | */ 34 | function IndexUtil(days,times){ 35 | const timeLength = days * times; 36 | const timesArr = (()=>{ 37 | let arr = []; 38 | for (let t = 0; t < times; t++) { 39 | arr.push(t); 40 | } 41 | return arr; 42 | })(); 43 | /** 44 | * @param {number} day 45 | * @param {number} time 46 | * @param {number} roomIndex 47 | */ 48 | this.getIndex = (day,time,roomIndex)=>{ 49 | return timeLength *roomIndex + times*day + time; 50 | } 51 | /** 52 | * 根据 index 获取 日期 时间 教室 的索引 53 | * @param {number} index 索引值 54 | * @return json {day,time,room} 55 | */ 56 | this.getPosition = (index)=>{ 57 | var room= Math.floor(index/timeLength); 58 | var daytime = index%timeLength; 59 | var day = Math.floor(daytime/ times); 60 | var time = daytime%times; 61 | return {day,time,room}; 62 | } 63 | /** 获取时间索引 这个值一样意味着在同一个时间点 */ 64 | this.getDayTime = (index) =>{ 65 | return index%timeLength; 66 | } 67 | this.getDayAndTime = (index) =>{ 68 | var daytime = index%timeLength; 69 | var day = Math.floor(daytime/ times); 70 | var time = daytime%times; 71 | return {day,time}; 72 | } 73 | this.getRoom = (index) =>{ 74 | return Math.floor(index/timeLength); 75 | } 76 | this.sameHalfDay = (index) =>{ 77 | let dayAndTime = this.getDayAndTime(index); 78 | let timeScopeArr; 79 | if(dayAndTime.time<4){ 80 | timeScopeArr = [0,1,2,3]; 81 | }else if(dayAndTime.time<8){ 82 | timeScopeArr = [4,5,6,7]; 83 | }else { 84 | timeScopeArr = [8,9,10,11]; 85 | } 86 | return timeScopeArr.map(time=>times*dayAndTime.day + time) 87 | } 88 | this.sameDay = (index) =>{ 89 | let day = this.getDayAndTime(index).day; 90 | return timesArr.map(time=>times*day + time) 91 | } 92 | this.skipDay = (index) =>{ 93 | let day = this.getDayAndTime(index).day; 94 | let prevDay = day==0?days-1:day-1; 95 | let nextDay = day>=days-1?0:day+1; 96 | return timesArr.map(time=>times*day + time) 97 | .concat(timesArr.map(time=>times*prevDay + time)) 98 | .concat(timesArr.map(time=>times*nextDay + time)); 99 | } 100 | } 101 | 102 | 103 | /** 104 | * 带跳过的轮盘赌 TODO 经测试,轮盘赌是整个算法中耗时最多的部分,看看能否优化。 105 | * @param {array} weight 课程概率数组 (下标: 元素编号、值: 该元素对应的概率) 106 | * @param {array} skip 可选参数 跳过序号的集合 若skip=[1,2]则不会选择数组中下标为1,2的元素,其他值按原概率分部 107 | * @param {function} negativeFilter 消极过滤函数,接受参数 时空索引, 108 | * 返回 ture 则按照消极对待 会以极小的概率选择此值,可以认为只有没有其他任何选择时才会选中此值 109 | * @returns {object} 返回 {index,bad} 概率数组中某一元素的下标 及是否为较差选择 110 | */ 111 | function roll(weights,skip,negativeFilter) { 112 | var sum = 0; 113 | var length = weights.length; 114 | let badSet = new Set(); 115 | for (var i=0; i= rand) { 141 | return i; 142 | } 143 | } 144 | return -1; 145 | } 146 | 147 | /** 148 | * 随机整数 范围 [start,end] 149 | * @param {*} start 150 | * @param {*} end 151 | */ 152 | function randomInt(start, end){ 153 | var length = end-start+1; 154 | return Math.floor(Math.random() * length + start); 155 | } 156 | 157 | /** 158 | * 计时和上一次的时间间隔 159 | */ 160 | function Timer(){ 161 | var _time = new Date().getTime(); 162 | this.__proto__={ 163 | get: ()=>{ 164 | let time = new Date().getTime(); 165 | let interval = time-_time; 166 | _time = time; 167 | return interval; 168 | } 169 | } 170 | } 171 | function Logger(level){ 172 | this.level = level; 173 | 174 | this.debug = (... args)=>{ 175 | if(this.level <= Logger.LEVEL.DEBUG) 176 | console.log(args.reduce((a,b)=>a+" "+b)) 177 | } 178 | this.info = (... args)=>{ 179 | if(this.level <= Logger.LEVEL.INFO) 180 | console.log(args.reduce((a,b)=>a+" "+b)) 181 | } 182 | } 183 | Logger.LEVEL={DEBUG:0,INFO:1,NONE:2} 184 | 185 | 186 | /** 187 | * 染色体类 包括基因序列 适应度 188 | * @param {number[]} geneOrder 基因序列 189 | * @param {number} badSelect 差选择数量 190 | */ 191 | function Chromosome(geneOrder,badSelect){ 192 | this.badSelect = badSelect; 193 | this.setGeneOrder=(geneOrder)=>{ 194 | _setGeneOrder(geneOrder); 195 | } 196 | var _setGeneOrder = (geneOrder) => { 197 | this.geneOrder = geneOrder; 198 | this.adapt = 0; 199 | for (let lessonIndex = 0; lessonIndex < geneOrder.length; lessonIndex++) { 200 | // 此时 i 代表课程序列 val 代表时空序列 201 | const spaceTimeIndex = geneOrder[lessonIndex]; 202 | this.adapt += adaptability[lessonIndex][spaceTimeIndex]; 203 | } 204 | this.adapt -= badSelect *CONFIG.badSelectVal; 205 | } 206 | _setGeneOrder(geneOrder); 207 | } 208 | 209 | /** 210 | * 动态时间冲突检测 211 | * 动态时间冲突 212 | */ 213 | function Conflict(){ 214 | this._conflicts = []; 215 | this._conflictMap = {}; 216 | this._campusConflicts = []; 217 | this._campusConflictMap = {}; 218 | /** 219 | * 记录同个排课单元的课程索引 220 | * 添加冲突数组,代表一个冲突所关联的所有课程下标 221 | * 例如 老师A需要上的课程对应下标为 1,3,45,123,333 则这些下表代表一个冲突 222 | * @param {number[]} arr arr.length<=1会被忽略,1个元素不存在冲突 223 | * @param scope Conflict.Scope 224 | * @param {string} remark 备注 方便调试 225 | */ 226 | this.add = (arr,scope,remark)=>{ 227 | if(arr.length<=1) 228 | return; 229 | this._conflicts.push({arr,scope,remark}); 230 | var conflictIndex = this._conflicts.length-1; 231 | for (let i = 0; i < arr.length; i++) { 232 | let index = arr[i]; 233 | if (!this._conflictMap[index]){ 234 | this._conflictMap[index] = []; 235 | } 236 | this._conflictMap[index].push(conflictIndex) 237 | } 238 | } 239 | /** 240 | * 241 | * @param {number} index 检查冲突的课程索引 242 | * @param {number[]} geneOrder 所属的基因序列 243 | * @returns {Set} 类型 geneOrder中所有和index关联课程的dayTime 244 | */ 245 | this.relatedDayTime= (index,geneOrder)=>{ 246 | let conflicts = this._conflictMap[index]; 247 | let lesson = lessons[index]; 248 | let campus = lesson.zone; 249 | let set = new Set(); 250 | if (!conflicts) 251 | return set; 252 | for (let i = 0; i < conflicts.length; i++) { 253 | let conflict = this._conflicts[conflicts[i]]; 254 | let arr = conflict.arr; 255 | let scope = conflict.scope; 256 | for (let j = 0; j < arr.length; j++) { 257 | let lessonIndex = arr[j]; 258 | if (lessonIndex==index) 259 | continue; 260 | let dayTimeRoom = geneOrder[lessonIndex]; 261 | if (dayTimeRoom>=0){ 262 | // set.add(indexUtil.getDayTime(dayTimeRoom)) 263 | switch (scope) { 264 | case Conflict.Scope.time: 265 | set.add(indexUtil.getDayTime(dayTimeRoom)) 266 | break; 267 | case Conflict.Scope.halfDay: 268 | indexUtil.sameHalfDay(dayTimeRoom).forEach(dayTimeRoom1 => set.add(dayTimeRoom1)); 269 | break; 270 | case Conflict.Scope.day: 271 | indexUtil.sameDay(dayTimeRoom).forEach(dayTimeRoom1 => set.add(dayTimeRoom1)); 272 | break; 273 | case Conflict.Scope.skipDay: 274 | indexUtil.skipDay(dayTimeRoom).forEach(dayTimeRoom1 => set.add(dayTimeRoom1)); 275 | break; 276 | default: 277 | throw "没有选择冲突时间范围" 278 | } 279 | let conflictLesson = lessons[lessonIndex]; 280 | //校区冲突 281 | if (conflictLesson.zone != campus){ 282 | indexUtil.sameHalfDay(dayTimeRoom).forEach(dayTimeRoom1 => set.add(dayTimeRoom1)); 283 | } 284 | } 285 | } 286 | } 287 | return set; 288 | } 289 | /** 290 | * 返回所有与 lessonIndex时间冲突的课程index 291 | */ 292 | this.getRelatedArr = (index)=>{ 293 | let conflictIndexArr = this._conflictMap[index]; 294 | let result = [];// 包含所有不能与他相同时间的课程 295 | if (conflictIndexArr){ 296 | for (let i = 0; i < conflictIndexArr.length; i++) { 297 | let conflictIndex = conflictIndexArr[i]; 298 | let conflict = this._conflicts[conflictIndex]; 299 | let conflictArr = conflict.arr; 300 | result = result.concat(conflictArr); 301 | } 302 | } 303 | let set = new Set(result); 304 | set.delete(index); 305 | return Array.from(set);// 去重 306 | } 307 | } 308 | /** 309 | * 冲突的时间占用范围,例如老师、班级的不同课程只要不在同一时间即可 选择time 310 | * 跨校区冲突选择 halfDay 311 | * 312 | */ 313 | Conflict.Scope={ 314 | time:0, 315 | halfDay:1, 316 | day:2, 317 | skipDay:3 318 | } 319 | 320 | -------------------------------------------------------------------------------- /jsdemo/data.js: -------------------------------------------------------------------------------- 1 | // 7day * 12 timeframe 2 | // courses {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 3 | // classroom {id:50001, roomType: RoomType.NORMAL, capacity:40, building:70001, floor:5, roomNO:502}, 4 | // teacher {id:20002, name:'教师2', disableTime: '30304'} 5 | // Zone {SP:0, JD:1} 6 | // RoomType = {NORMAL:0, COMPUTER:1, PLAYGROUD:2, LAB:3, MEDIA:4} 7 | 8 | // var Zone = {SP:0, JD:1}; 9 | // var RoomType = {NORMAL:0, COMPUTER:1, PLAYGROUD:2, LAB:3, MEDIA:4}; 10 | // courses {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null} 11 | // teacher {id:20002, name:'教师2', disableTime: '30304'} 12 | // class {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP}, 13 | var campus = {0:'SP',1:'JD'}; 14 | var DataGenerator = function(){ 15 | var Param = { 16 | campus, 17 | buildings : [ 18 | { 19 | roomTypeId :0, 20 | roomType : 'normal', 21 | zone: 0, 22 | capacityNum:{ // 容量数量 23 | 50: 15, 24 | 100: 8, 25 | 150: 6, 26 | } 27 | },{ 28 | roomTypeId :0, 29 | roomType : 'normal', 30 | zone:1, 31 | capacityNum:{ // 容量数量 32 | 50: 15, 33 | 100: 8, 34 | 150: 6, 35 | } 36 | },{ 37 | roomTypeId :3, 38 | roomType : 'lab', 39 | zone:0, 40 | capacityNum:{ // 容量数量 41 | 50: 2, 42 | } 43 | },{ 44 | roomTypeId :3, 45 | roomType : 'lab', 46 | zone: 1, 47 | capacityNum:{ // 容量数量 48 | 50: 2, 49 | } 50 | }, 51 | ], 52 | // 排课类型 53 | courseType :{ 54 | A:{ 55 | roomType: 'normal', 56 | onceHour: 2, 57 | weekHour: 4, 58 | count: 50, 59 | }, 60 | B:{ 61 | roomType: 'normal', 62 | onceHour: 4, 63 | weekHour: 4, 64 | count: 40 65 | }, 66 | C:{ 67 | roomType: 'lab', 68 | onceHour: 2, 69 | weekHour: 4, 70 | count: 20 71 | } 72 | }, 73 | // 教师人数 74 | teacher :{ 75 | count: 100 76 | } 77 | } 78 | console.log(Param) 79 | // 0开始转换为 字母表示 80 | var letterIndex = function(index){ 81 | // ascii 65 A-90 Z 82 | var strIndex = ''; 83 | while(index >= 0) { 84 | var mod = index % 26; 85 | strIndex = String.fromCharCode(mod+65)+ strIndex; 86 | index = (index-mod)/26-1; 87 | } ; 88 | return strIndex; 89 | } 90 | var randomStudentNum = function(){ 91 | var ran = Math.random()*30; 92 | if(ran<15){ 93 | return 45; 94 | } 95 | if (ran<25){ 96 | return 85; 97 | } 98 | return 140; 99 | } 100 | // teacher {id:20002, name:'教师2', disableTime: '30304'} 101 | this.generateTeachers = function (){ 102 | var count = Param.teacher.count; 103 | var teachers =[]; 104 | for (let i = 0; i < count; i++) { 105 | teachers.push({ 106 | id: 20000+teachers.length, 107 | name :'教师'+i, 108 | disableTime : null 109 | }) 110 | } 111 | this.teachers = teachers; 112 | } 113 | 114 | this.generateLessons = function () { 115 | var lessons = []; 116 | for (const key in Param.courseType) { 117 | if (Param.courseType.hasOwnProperty(key)) { 118 | let course = Param.courseType[key]; 119 | for (let i = 0; i < course.count; i++) { 120 | // courses {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null} 121 | lessons.push({ 122 | id: 10000+lessons.length, 123 | name: '课程'+letterIndex(lessons.length), 124 | weekHour: course.weekHour, 125 | roomType: course.roomTypeId, 126 | onceHour: course.onceHour 127 | }); 128 | } 129 | } 130 | } 131 | this.lessons = lessons; 132 | } 133 | // class {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP}, 134 | this.generateClasses = function () { 135 | 136 | var lessons = this.lessons; 137 | var classes = []; 138 | for (const lesson of lessons) { 139 | var classCount = Math.ceil(Math.random()*3) //随机给每门课1-3个班级 140 | var teachers = this.teachers; 141 | var campus = Math.round(Math.random()); //随机 0和1 142 | var teacher =teachers[ Math.floor(Math.random()*teachers.length)]; // 随机取一个老师 143 | for(let i =0; i 2 | 3 | 4 | 5 | 遗传算法 6 | 7 | 8 | 9 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 执行排课算法 21 | 覆盖初始数据 22 | 清空图表 23 | 24 | 25 | 26 | 28 | DEBUG 29 | INFO 30 | NONE 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 47 | 100 48 | 200 49 | 500 50 | 51 | 52 | 53 |
54 | 55 |
算法未执行
56 |
57 |
58 | 需满足要求:
59 | 1. 同个老师(定义为排课单元,同个教学班/培养计划同理)课程时间不能冲突
60 | 2. 同门课程 a)一周2节保证不在相邻天排课 b)3-5节保证不在同一天排课
61 | 3. 同个排课单元不同校区的课程不在同一个半天
62 | 要求为尽量满足,如果无法满足将会给出带有冲突的解,后续可供手动调整

63 |
64 | 65 | 66 | 67 | 68 | 69 | 格式:{{lessonFormat}} 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 格式:{{lessonFormat}} 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 格式:{{lessonFormat}} 88 | {{info.room}} 89 | 90 | 91 | 92 |
93 | 94 | 95 | 96 | 97 | 98 | 365 | 366 | -------------------------------------------------------------------------------- /jsdemo/lib/element-ui/fonts/element-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzlz000/class-sort/93beb3a80fcc497ae2a19f9a70a8cc92b92c58cf/jsdemo/lib/element-ui/fonts/element-icons.woff -------------------------------------------------------------------------------- /jsdemo/old/GA.data.js: -------------------------------------------------------------------------------- 1 | /** 染色体(教室时间片)集合,对应值为教学班编号; 四维矩阵 A={ChromosomeNO, ClassRoom, Day, Time} -> ClassNO */ 2 | var chromosomeMatrix = []; 3 | /** 教学班对应的时间片/排课结果 三维矩阵 B={ChromosomeNO, Class,Times} -> roomNO+dayNO+timeNO */ 4 | var classMatrixes = []; 5 | 6 | /** 自适应交叉率公式常量 */ 7 | var KC1 = 0.9; 8 | var KC2 = 0.6; 9 | /** 自适应变异率公式常量 */ 10 | var KM1 = 0.1; 11 | var KM2 = 0.001; 12 | 13 | /** 一天的课时 */ 14 | var times = [1,2,3,4,5,6,7,8,9,10,11,12]; 15 | /** 一周上课日 */ 16 | var days = [1,2,3,4,5,6,7]; 17 | /** 一学期上课日 17周 (为单双周排课准备)*/ 18 | var allDays = new Array(days.length*17); 19 | /** 联排起始小节数 2-两节联排 3-三节联排 4-四节联排 */ 20 | var unitTimeStart = { 21 | 1: [1,2,3,4,5,6,7,8,9,10,11,12], 22 | 2: [1,3,5,7,9,11], 23 | 3: [1,2,5,6], 24 | 4: [1,5,9] 25 | }; 26 | /** 初始排一周课时的上课日遍历顺序 */ 27 | var daySels = [1,4,3,5,2,6]; 28 | 29 | /** 校区 */ 30 | var Zone = {SP:'SP', JD:'JD'}; 31 | /** 教室类别 */ 32 | var RoomType = {NORMAL:'normal', COMPUTER:'computer', PLAYGROUD:'playgroud', LAB:'lab', MEDIA:'media'}; 33 | 34 | /** 教学楼 zone-校区 floors-楼层数(0为操场)*/ 35 | var buildings = [ 36 | {id:70001, zone:Zone.SP, floors:20}, 37 | {id:70002, zone:Zone.SP, floors:5}, 38 | {id:70003, zone:Zone.SP, floors:8}, 39 | {id:70004, zone:Zone.SP, floors:12}, 40 | {id:70005, zone:Zone.SP, floors:0}, 41 | {id:70006, zone:Zone.JD, floors:10}, 42 | {id:70007, zone:Zone.JD, floors:18}, 43 | {id:70008, zone:Zone.JD, floors:3}, 44 | {id:70009, zone:Zone.JD, floors:0} 45 | ]; 46 | var buildingsMap = {}; 47 | 48 | /** 教室 */ 49 | var classRooms = [ 50 | {id:50001, roomType: RoomType.NORMAL, capacity:40, building:70001, floor:5, roomNO:502}, 51 | {id:50002, roomType: RoomType.NORMAL, capacity:120, building:70001, floor:12, roomNO:1201}, 52 | {id:50003, roomType: RoomType.MEDIA, capacity:30, building:70002, floor:3, roomNO:302}, 53 | {id:50004, roomType: RoomType.NORMAL, capacity:40, building:70004, floor:4, roomNO:406}, 54 | {id:50005, roomType: RoomType.PLAYGROUD, capacity:140, building:70005, floor:0, roomNO:1}, 55 | {id:50006, roomType: RoomType.NORMAL, capacity:60, building:70006, floor:5, roomNO:505}, 56 | {id:50007, roomType: RoomType.PLAYGROUD, capacity:150, building:70009, floor:0, roomNO:1} 57 | ]; 58 | var classroomsMap = {}; 59 | 60 | /** 教学班 61 | */ 62 | var lessons = [ 63 | {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP}, 64 | {id:30102, course:10001, teacher:20001, studentNum:28, zone:Zone.JD}, 65 | {id:30201, course:10002, teacher:20002, studentNum:90, zone:Zone.SP}, 66 | {id:30301, course:10003, teacher:20001, studentNum:48, zone:Zone.SP}, 67 | {id:30302, course:10003, teacher:20003, studentNum:52, zone:Zone.SP}, 68 | {id:30303, course:10003, teacher:20004, studentNum:28, zone:Zone.SP}, 69 | {id:30401, course:10004, teacher:20005, studentNum:28, zone:Zone.SP}, 70 | {id:30402, course:10004, teacher:20005, studentNum:28, zone:Zone.JD}, 71 | {id:30501, course:10005, teacher:20003, studentNum:24, zone:Zone.SP}, 72 | {id:30502, course:10005, teacher:20003, studentNum:24, zone:Zone.SP}, 73 | {id:30503, course:10005, teacher:20004, studentNum:28, zone:Zone.SP}, 74 | {id:30504, course:10005, teacher:20004, studentNum:24, zone:Zone.SP}, 75 | {id:30601, course:10006, teacher:20006, studentNum:28, zone:Zone.SP}, 76 | {id:30602, course:10006, teacher:20006, studentNum:28, zone:Zone.JD}, 77 | {id:30701, course:10007, teacher:20008, studentNum:28, zone:Zone.SP}, 78 | {id:30702, course:10007, teacher:20008, studentNum:28, zone:Zone.SP}, 79 | {id:30801, course:10008, teacher:20009, studentNum:28, zone:Zone.SP}, 80 | {id:30802, course:10008, teacher:20009, studentNum:28, zone:Zone.SP}, 81 | {id:30803, course:10008, teacher:20009, studentNum:28, zone:Zone.JD}, 82 | {id:30901, course:10009, teacher:20012, studentNum:88, zone:Zone.SP} 83 | ]; 84 | var classesMap = {}; 85 | 86 | /** 教师 disableTime-不可排课的时间段(302-周三的第3/4课时*/ 87 | var teachers = [ 88 | {id:20001, name:'教师1', disableTime: null}, 89 | {id:20002, name:'教师2', disableTime: '30304'}, 90 | {id:20003, name:'教师3'}, 91 | {id:20004, name:'教师4'}, 92 | {id:20005, name:'教师5'}, 93 | {id:20006, name:'教师6'}, 94 | {id:20007, name:'教师7'}, 95 | {id:20008, name:'教师8'}, 96 | {id:20009, name:'教师9'}, 97 | {id:20010, name:'教师10'}, 98 | {id:20011, name:'教师11'}, 99 | {id:20012, name:'教师12'} 100 | ]; 101 | var teachersMap = {}; 102 | 103 | /** 课程 104 | * onceHour: 上一次课的课时,实际应用中根据统一规则来计算,若有例外则单独指定或手工排课 105 | * timeRequire: 上课的特殊时间要求 day-周几(为null则无特殊要求), time-课时范围(为null则无特殊要求) 106 | */ 107 | var courses = [ 108 | {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 109 | {id:10002, name:'计算机原理', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 110 | {id:10003, name:'大学英语', weekHour:4, totalHour:16, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 111 | {id:10004, name:'JAVA语言', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 112 | {id:10005, name:'英语听力', weekHour:2, totalHour:14, roomType:RoomType.MEDIA, onceHour:2, timeRequire:{day:[2,4], time:[1,2,3,4]}}, 113 | {id:10006, name:'通信原理', weekHour:4, totalHour:48, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 114 | {id:10007, name:'数据结构', weekHour:2, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 115 | {id:10008, name:'经济学基础', weekHour:6, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 116 | {id:10009, name:'体育', weekHour:2, totalHour:20, roomType:RoomType.PLAYGROUD, onceHour:2, timeRequire:{day:null, time:[1,2,3,4,5,6,7,8]}} 117 | ]; 118 | var coursesMap = {}; 119 | 120 | /** 迭代次数 */ 121 | var iteratorNum = 200; 122 | 123 | /** 染色体数量 */ 124 | var chromosomeNum = 20; 125 | 126 | /** 适应度矩阵(下标:染色体编号、值:该染色体的适应度) */ 127 | var adaptability = []; 128 | /** 自然选择的概率矩阵(下标:染色体编号、值:该染色体被选择的概率) */ 129 | var selectionProbability = []; 130 | 131 | /** 染色体复制的比例(每代中保留适应度较高的染色体直接成为下一代) */ 132 | var cp = 0.2; 133 | /** 参与交叉变异的染色体数量 */ 134 | var crossoverMutationNum; 135 | 136 | /** 任务处理适应度结果集([迭代次数][染色体编号] = 适应度) */ 137 | var logGenData = []; 138 | 139 | /** 必须满足的约束(冲突) */ 140 | var neceReq = [ 141 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 142 | //log('nece-func-1: 同一时间片一个教师只能上一门课程: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 143 | var matchable = true; 144 | for(var c = 0; c < classRooms.length; c++) { 145 | var existClassNO = roomMatrix[c][dayNO][timeNO]; 146 | if (!existClassNO || existClassNO == classNO) { 147 | continue; 148 | } 149 | if (!lessons[existClassNO] || !lessons[classNO]) { 150 | continue; 151 | } 152 | 153 | if (lessons[existClassNO].teacher == lessons[classNO].teacher) { 154 | matchable = false; 155 | break; 156 | } 157 | } 158 | if (!matchable) { 159 | log('[UnMatch]nece-func-1: 同一时间片一个教师只能上一门课程: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 160 | } 161 | return matchable; 162 | }, 163 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 164 | //log('nece-func-2: 某一教室的某一时间片只能被一门课程占用: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 165 | var matchable = true; 166 | var existClassNO = roomMatrix[classRoomNO][dayNO][timeNO]; 167 | if(existClassNO && lessons[existClassNO] && existClassNO != classNO) { 168 | matchable = false; 169 | } 170 | if (!matchable) { 171 | log('[UnMatch]nece-func-2: 某一教室的某一时间片只能被一门课程占用: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 172 | } 173 | return matchable; 174 | }, 175 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 176 | //log('nece-func-3: 某班学生在某一时间片只能被安排在一个教室上课: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 177 | var matchable = true; 178 | for(var c = 0; c < classRooms.length; c++) { 179 | var existClassNO = roomMatrix[classRoomNO][dayNO][timeNO]; 180 | if (!existClassNO || existClassNO == classNO) { 181 | continue; 182 | } 183 | if (!lessons[existClassNO] || !lessons[classNO]) { 184 | continue; 185 | } 186 | 187 | if (classNO == existClassNO) { 188 | matchable = false; 189 | break; 190 | } 191 | } 192 | if (!matchable) { 193 | log('[UnMatch]nece-func-3: 某班学生在某一时间片只能被安排在一个教室上课: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 194 | } 195 | return matchable; 196 | }, 197 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 198 | //log('nece-func-4: 某课程m必须安排在预定的时间片n上: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 199 | var matchable = true; 200 | var classSel = lessons[classNO]; 201 | if (classSel && classSel.timeRequire) { 202 | if (classSel.timeRequire.day) { 203 | var dayFilter = classSel.timeRequire.day.filter(function (day) { 204 | return (dayNO+1) == day; 205 | }); 206 | if (!dayFilter || dayFilter.length <= 0) { 207 | matchable = false; 208 | } 209 | } 210 | if (classSel.timeRequire.time && matchable) { 211 | var timeFilter = classSel.timeRequire.time.filter(function (time) { 212 | return (timeNO+1) == time; 213 | }); 214 | if (!timeFilter || timeFilter.length <= 0) { 215 | matchable = false; 216 | } 217 | } 218 | } 219 | if (!matchable) { 220 | log('[UnMatch]nece-func-4: 某课程m必须安排在预定的时间片n上: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 221 | } 222 | return matchable; 223 | }, 224 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 225 | var matchable = true; 226 | if (!matchable) { 227 | log('[UnMatch]nece-func-5: 某教师m在时间片n时不能上课: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 228 | } 229 | return matchable; 230 | }, 231 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 232 | //log('nece-func-6: 同一教学班任务不要在同一天内连续的开课: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 233 | var matchable = true; 234 | var dayHourCnt = 0; 235 | var classResult = classMatrix[classNO]; 236 | if (classResult && classResult.length > 0) { 237 | for(var c = 0; c < classResult.length; c++) { 238 | var timeObj = decodeTimeCode(classResult[c]); 239 | if (timeObj.dayNO == dayNO) { 240 | dayHourCnt++; 241 | } 242 | } 243 | } 244 | matchable = (dayHourCnt < coursesMap[lessons[classNO].course].onceHour); 245 | if (!matchable) { 246 | log('[UnMatch]nece-func-6: 同一教学班任务不要在同一天内连续的开课: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 247 | } 248 | return matchable; 249 | }, 250 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 251 | //log('nece-func-7: 课程对教室的要求: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 252 | var matchable = true; 253 | var classRoom = classRooms[classRoomNO]; 254 | var course = coursesMap[lessons[classNO].course]; 255 | // 匹配教室类型,容量,校区 256 | matchable = (classRoom.roomType == course.roomType) 257 | && (lessons[classNO].studentNum <= classRoom.capacity) 258 | && (buildingsMap[classRoom.building].zone == lessons[classNO].zone); 259 | if (!matchable) { 260 | log('[UnMatch]nece-func-7: 课程对教室的要求: ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 261 | } 262 | return matchable; 263 | }, 264 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 265 | //log('nece-func-8: 同一课程的一次课分配在同一时段(上午/下午/晚上): ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 266 | var matchable = true; 267 | var classResult = classMatrix[classNO]; 268 | if (classResult && classResult.length > 0) { 269 | for(var c = 0; c < classResult.length; c++) { 270 | var timeObj = decodeTimeCode(classResult[c]); 271 | if (timeObj.dayNO == dayNO && !isSameTimeSlot(timeObj.timeNO, timeNO)) { 272 | matchable = false; 273 | break; 274 | } 275 | } 276 | } 277 | if (!matchable) { 278 | log('[UnMatch]nece-func-8: 同一课程的一次课分配在同一时段(上午/下午/晚上): ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 279 | } 280 | return matchable; 281 | } 282 | ]; 283 | 284 | /** 尽可能满足的约束(适应能力) */ 285 | var possReq = [ 286 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 287 | var suitVal = 0; 288 | var rate = lessons[classNO].studentNum / classRooms[classRoomNO].capacity; 289 | 290 | // 比值越接近1,适应度越高: rate*100 291 | suitVal = rate * 100; 292 | 293 | log('poss-func-1: 较高的教室利用率(上课人数/教室的座位数): ' + suitVal + ' | ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 294 | return suitVal; 295 | }, 296 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 297 | var suitVal = 0; 298 | 299 | // 周二下午排课 -10 300 | if (dayNO == 2-1) { 301 | suitVal -= 10; 302 | } 303 | // 晚上排课 -15 304 | if (timeNO >8) { 305 | suitVal -= 15; 306 | } 307 | // 周六排课 -20 308 | if (dayNO == 6-1) { 309 | suitVal -= 20; 310 | } 311 | 312 | log('poss-func-2: 晚上/周二下午/周六尽量不排课: ' + suitVal + ' | ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 313 | return suitVal; 314 | }, 315 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 316 | var suitVal = 0; 317 | var classNO = roomMatrix[classRoomNO][dayNO][timeNO]; 318 | if (classNO) { 319 | // 计算最大间隔天数 320 | var interMax = calcPreDayInterval(classMatrix[classNO]); 321 | // 间隔一天+5 322 | if (interMax == 1) { 323 | suitVal += 5; 324 | } 325 | // 间隔两天+10 326 | if (interMax == 2) { 327 | suitVal += 10; 328 | } 329 | // 其他情况不加分 330 | } 331 | 332 | log('poss-func-3: 一周内同一门课程均匀分布: ' + suitVal + ' | ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 333 | return suitVal; 334 | }, 335 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 336 | var suitVal = 0; 337 | var classNO = roomMatrix[classRoomNO][dayNO][timeNO]; 338 | var classObj = lessons[classNO]; 339 | var classRoom = classRooms[classRoomNO]; 340 | 341 | // 遍历课程所属老师 342 | for (var c=0; c timeObj.timeNO 352 | && isSameTimeSlot(timeObj.timeNO, timeNO) 353 | && buildingObj.id==buildingCur.id) { 354 | suitVal += 10; 355 | } 356 | 357 | // 教师同一天的课程尽量在同一校区 358 | if (timeObj && timeObj.dayNO==dayNO && timeNO > timeObj.timeNO 359 | && buildingObj.zone == buildingCur.zone) { 360 | suitVal += 5; 361 | } 362 | } 363 | } 364 | } 365 | 366 | log('poss-func-4: 教师同一时段(上午/下午/晚上)的课程尽量在同一教学楼/同一天的课程尽量在同一校区: ' + suitVal + ' | ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 367 | return suitVal; 368 | }, 369 | function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 370 | var suitVal = 0; 371 | // todo: 需要查教学班对应的具体学生,根据学生的不同课程所属教学楼来计算比例,计算量很大 372 | 373 | log('poss-func-5: 学生同一时段(上午/下午/晚上)的课程尽量在同一教学楼/同一天的课程尽量在同一校区: ' + suitVal + ' | ' + classRoomNO + ',' + dayNO + ',' + timeNO + ',' + classNO); 374 | return suitVal; 375 | } 376 | ]; 377 | 378 | /** 拆分一个课程的周课时规则 379 | * 偶数:按一次课2个课时分配; 380 | * 奇数:>3 一次课2个课时分配,最后一次1课时;=3 一次课3个课时; =1 一次课1个课时 381 | */ 382 | var splitWeekHours = function(_class) { 383 | courses.forEach(function(course, index) { 384 | if (course.weekHour % 2 == 0) { 385 | course.onceHour = 2; 386 | } else if (course.weekHour == 3) { 387 | course.onceHour = 3; 388 | } else if (course.weekHour == 1) { 389 | course.onceHour = 1; 390 | } else { 391 | course.onceHour = 2; 392 | } 393 | }); 394 | }; 395 | 396 | /** 适应度计算 */ 397 | var calcSuitVal = function(roomMatrix, classMatrix, classRoomNO, dayNO, timeNO, classNO) { 398 | var suitVal = 0; 399 | for (var i=0; i0 && preClassNO==roomMatrix[c][d][t])) { 694 | preClassNO = roomMatrix[c][d][t]; 695 | continue; 696 | } 697 | 698 | suitVal += calcSuitVal(roomMatrix, classMatrix, c, d, t, roomMatrix[c][d][t]); 699 | preClassNO = roomMatrix[c][d][t]; 700 | } 701 | } 702 | } 703 | 704 | adaptability.push(suitVal); 705 | } 706 | } 707 | 708 | /** 709 | * 计算自然选择概率 710 | * @param adaptability 711 | */ 712 | function calSelectionProbability(adaptability) { 713 | selectionProbability = []; 714 | 715 | // 计算适应度总和 716 | var sumAdaptability = 0; 717 | for (var i=0; i0 && timeNO>0 738 | && classNO == roomMatrix[roomNO][dayNO][timeNO-1]); 739 | } 740 | 741 | /** 742 | * 按天索引教学班的排课课时 743 | * @param classMatrix 744 | * @param classNO 745 | */ 746 | function collectTimesMap(classMatrix, classNO) { 747 | var timeObjMap = {}; 748 | for (var to=0; to -1) { 766 | for (var to=0; to-1 && classMatrix[classNO].push(encodeTimeCode(timeObjDst.classRoomNO, timeObjDst.dayNO, timeObjDst.timeNO)); 820 | roomMatrix[timeObjDst.classRoomNO][timeObjDst.dayNO][timeObjDst.timeNO] = classNO; 821 | classNODst>-1 && classMatrix[classNODst].push(encodeTimeCode(timeObj.classRoomNO, timeObj.dayNO, timeObj.timeNO)); 822 | roomMatrix[timeObj.classRoomNO][timeObj.dayNO][timeObj.timeNO] = classNODst; 823 | } 824 | } 825 | 826 | /** 827 | * 交叉生成{crossoverMutationNum}条染色体 828 | * @param copyIndexArr 上一代直接复制的下标 829 | */ 830 | function cross(crossRoomMatrixes, crossClassMatrixes) { 831 | var crossIndexArr = []; 832 | 833 | for (var chromosomeIndex=0; chromosomeIndex= crossNum) { 863 | break; 864 | } 865 | 866 | // 查找baba中该教学班的排课 867 | var timeObjMapBaba = collectTimesMap(classMatrixBaba, classNOBaba); 868 | 869 | // 查找mama中该教学班的排课 870 | var timeObjMapMama = collectTimesMap(classMatrixMama, classNOBaba); 871 | 872 | for (var dayIndex in timeObjMapMama) { 873 | // 找到相同课时的上课日 874 | var dayIndexBaba = -1; 875 | for (dayIndexBaba in timeObjMapBaba) { 876 | if (timeObjMapBaba[dayIndexBaba].length == timeObjMapMama[dayIndex].length) { 877 | break; 878 | } 879 | } 880 | if (dayIndexBaba == -1) { 881 | continue; 882 | } 883 | var timeObjBabaArr = timeObjMapBaba[dayIndexBaba]; 884 | timeObjMapBaba[dayIndexBaba] = []; 885 | 886 | var roomNOMama = parseInt(dayIndex.substr(0, 5)); 887 | var dayNOMama = parseInt(dayIndex.substr(5, 1)); 888 | var timeNOMama = timeObjMapMama[dayIndex][0].timeNO; 889 | 890 | var roomNOBaba = parseInt(dayIndexBaba.substr(0, 5)); 891 | var dayNOBaba = parseInt(dayIndexBaba.substr(5, 1)); 892 | var timeNOBaba = timeObjBabaArr[0].timeNO; 893 | 894 | // Baba目标位置是否有排课 895 | var classNOBabaDst = chromosomeBaba[roomNOMama][dayNOMama][timeNOMama]; 896 | // 目标教学班要不相同才有交换的意义 897 | if (classNOBabaDst == classNOBaba) { 898 | continue; 899 | } 900 | // 需要为本次课开始的课时 901 | if (!isStartClassTime(chromosomeBaba, roomNOMama, dayNOMama, timeNOMama, classNOBabaDst)) { 902 | continue; 903 | } 904 | 905 | // 收集目标位置资源 906 | var timeObjBabaArrDst = collectDstTimes(chromosomeBaba, classMatrixBaba, 907 | timeObjMapMama[dayIndex], roomNOMama, dayNOMama, timeNOMama, classNOBabaDst); 908 | 909 | // 课时相同才交换 910 | if (timeObjMapMama[dayIndex].length == timeObjBabaArr.length 911 | && timeObjBabaArrDst.length == timeObjBabaArr.length) { 912 | 913 | // 先将该排课信息置空以便冲突检测 914 | resetOnceTimes(chromosomeBaba, classMatrixBaba, classNOBaba, roomNOBaba, dayNOBaba); 915 | resetOnceTimes(chromosomeBaba, classMatrixBaba, classNOBabaDst, roomNOMama, dayNOMama); 916 | 917 | // 满足冲突检测则交换 918 | if (checkConflict(chromosomeBaba, classMatrixBaba, roomNOMama, dayNOMama, timeNOMama, classNOBaba) 919 | && checkConflict(chromosomeBaba, classMatrixBaba, roomNOBaba, dayNOBaba, timeNOBaba, classNOBabaDst)) 920 | { 921 | commitExchOnceTimes(chromosomeBaba, classMatrixBaba, timeObjBabaArr, timeObjMapMama[dayIndex], classNOBaba, classNOBabaDst); 922 | log("交换成功+1,累计成功:" + (++crossAllCnt)); 923 | continue; 924 | } else { 925 | // 还原 926 | commitExchOnceTimes(chromosomeBaba, classMatrixBaba, timeObjBabaArr, timeObjMapMama[dayIndex], classNOBabaDst, classNOBaba); 927 | } 928 | } 929 | } 930 | 931 | } 932 | } 933 | 934 | return crossIndexArr; 935 | } 936 | 937 | /** 938 | * 从数组中寻找最大的n个元素 939 | * @param array 940 | * @param n 941 | */ 942 | function maxNIndex(array, n) { 943 | var sortArr = array.slice(0); 944 | sortArr.sort(compareNumDesc); 945 | 946 | // 取最大的n个元素 947 | var maxIndex = []; 948 | for (var i=0; i= rand) { 1015 | return i; 1016 | } 1017 | } 1018 | } 1019 | 1020 | /** 1021 | * 获取随机课时资源位置及其教学班 1022 | * @param roomMatrix 1023 | * @param timeStarts 1024 | * @param resClassNO 1025 | * @returns {TimeUnit} 1026 | */ 1027 | function getRandDstTimes(roomMatrix, timeStarts, resClassNO) { 1028 | // 随机获取目标位置 1029 | var roomNODst = random(0, classRooms.length-1); 1030 | var dayNODst = random(0, days.length-1); 1031 | var timeNODstIndex = random(0, timeStarts.length-1); 1032 | var timeNODst = timeStarts[timeNODstIndex] - 1; 1033 | 1034 | // 目标位置是否有排课 1035 | var classNODst = roomMatrix[roomNODst][dayNODst][timeNODst]; 1036 | 1037 | // 目标教学班要不相同才有交换的意义 1038 | // 需要为本次课开始的课时 1039 | while ((classNODst == resClassNO) || !isStartClassTime(roomMatrix, roomNODst, dayNODst, timeNODst, classNODst)) { 1040 | roomNODst = random(0, classRooms.length-1); 1041 | dayNODst = random(0, days.length-1); 1042 | timeNODstIndex = random(0, timeStarts.length-1); 1043 | timeNODst = timeStarts[timeNODstIndex] - 1; 1044 | classNODst = roomMatrix[roomNODst][dayNODst][timeNODst]; 1045 | } 1046 | 1047 | var timeDst = new TimeUnit(roomNODst, dayNODst, timeNODst); 1048 | timeDst.classNO = classNODst; 1049 | 1050 | return timeDst; 1051 | } 1052 | 1053 | /** 1054 | * 变异 1055 | * @param crossRoomMatrixes 参与交叉之后的种群 1056 | */ 1057 | function mutation(crossRoomMatrixes, crossClassMatrixes) { 1058 | var mutationIndexArr = []; 1059 | 1060 | // 随机选择一条染色体让其变异 1061 | var chromosomeIndex = random(0, crossRoomMatrixes.length-1); 1062 | mutationIndexArr.push(mutationIndexArr); 1063 | 1064 | var roomMatrix = crossRoomMatrixes[chromosomeIndex]; 1065 | var classMatrix = crossClassMatrixes[chromosomeIndex]; 1066 | 1067 | // todo: 自适应变异率计算 1068 | var mutationRate = 0; 1069 | var mutationNum = 10; 1070 | 1071 | // 累计变异成功的个数 1072 | var mutationAllCnt = 0; 1073 | 1074 | // 随机遍历教学班 1075 | var classRandArr = initRandomArray(classMatrix.length, [1, classMatrix.length]); 1076 | for (var cIndex=0; cIndex= mutationNum) { 1078 | break; 1079 | } 1080 | var classNO = classRandArr[cIndex] - 1; 1081 | var classObj = lessons[classNO]; 1082 | var course = coursesMap[classObj.course]; 1083 | 1084 | // 联排起始节次 1085 | var timeStarts = unitTimeStart[course.onceHour]; 1086 | 1087 | // 查找该教学班的排课 1088 | var timeObjMap = collectTimesMap(classMatrix, classNO); 1089 | 1090 | for (var dayIndex in timeObjMap) { 1091 | var timeObjArr = timeObjMap[dayIndex]; 1092 | 1093 | var roomNO = parseInt(dayIndex.substr(0, 5)); 1094 | var dayNO = parseInt(dayIndex.substr(5, 1)); 1095 | var timeNO = timeObjMap[dayIndex][0].timeNO; 1096 | 1097 | // 随机获取目标位置 1098 | var timeUnitDst = getRandDstTimes(roomMatrix, timeStarts, classNO); 1099 | var roomNODst = timeUnitDst.classRoomNO; 1100 | var dayNODst = timeUnitDst.dayNO; 1101 | var timeNODst = timeUnitDst.timeNO; 1102 | var classNODst = timeUnitDst.classNO; 1103 | 1104 | // 收集目标位置资源 1105 | var timeObjArrDst = collectDstTimes(roomMatrix, classMatrix, 1106 | timeObjArr, roomNODst, dayNODst, timeNODst, classNODst); 1107 | 1108 | // 课时相同才交换 1109 | if (timeObjArrDst.length == timeObjArr.length) { 1110 | 1111 | // 先将该排课信息置空以便冲突检测 1112 | resetOnceTimes(roomMatrix, classMatrix, classNO, roomNO, dayNO); 1113 | resetOnceTimes(roomMatrix, classMatrix, classNODst, roomNODst, dayNODst); 1114 | 1115 | // 满足冲突检测则交换 1116 | if (checkConflict(roomMatrix, classMatrix, roomNODst, dayNODst, timeNODst, classNO) 1117 | && checkConflict(roomMatrix, classMatrix, roomNO, dayNO, timeNO, classNODst)) 1118 | { 1119 | commitExchOnceTimes(roomMatrix, classMatrix, timeObjArr, timeObjArrDst, classNO, classNODst); 1120 | log("变异成功+1,累计成功:" + (++mutationAllCnt)); 1121 | continue; 1122 | } else { 1123 | // 还原 1124 | commitExchOnceTimes(roomMatrix, classMatrix, timeObjArr, timeObjArrDst, classNODst, classNO); 1125 | } 1126 | } 1127 | } 1128 | } 1129 | 1130 | return mutationIndexArr; 1131 | } -------------------------------------------------------------------------------- /jsdemo/old/common.js: -------------------------------------------------------------------------------- 1 | /** 打印log */ 2 | var gEnableLog = false; 3 | 4 | /** 课时资源对象 */ 5 | function TimeUnit(classRoomNO, dayNO, timeNO) { 6 | this.classRoomNO = classRoomNO; 7 | this.dayNO = dayNO; 8 | this.timeNO = timeNO; 9 | this.classNO = -1; 10 | } 11 | TimeUnit.prototype.toString = function () { 12 | return '(' + this.classRoomNO + ', ' + this.dayNO + ', ' + this.timeNO + ', ' + this.classNO + ')'; 13 | }; 14 | 15 | 16 | function log(msg,force) { 17 | if (gEnableLog || force) { 18 | console.log(msg); 19 | } 20 | } 21 | 22 | /** 23 | * 获取指定范围内的随机数 24 | * @param start 起点 25 | * @param end 终点 26 | * @returns {number} 27 | */ 28 | function random(start, end){ 29 | var length = end-start+1; 30 | return Math.floor(Math.random() * length + start); 31 | } 32 | 33 | /** 34 | * 得到随机的课时开始时间点(奇数课时) 35 | */ 36 | function randomClassTime(){ 37 | var tmp = random(1, 12); 38 | return (tmp%2>0) ? tmp : tmp-1; 39 | } 40 | 41 | 42 | /** 43 | * 创建随机数组 44 | * @param length 数组长度 45 | * @param range 数组取值范围 46 | */ 47 | function initRandomArray(length, range) { 48 | var randomArray = []; 49 | var existed = {}; 50 | for (var i=0; i= 4 && timeNOa < 8) && (timeNOb >=4 && timeNOb < 8)) 95 | || ((timeNOa >= 8) && (timeNOb >=8)); 96 | } 97 | 98 | 99 | /** 填值为时间片编码: roomNO+dayNO+timeNO,5字符+1字符+2字符=8字符 */ 100 | function encodeTimeCode(classRoomNO, dayNO, timeNO) { 101 | return prefixInteger(classRoomNO, 5) + dayNO.toString() + prefixInteger(timeNO, 2); 102 | } 103 | 104 | /** 填值为时间片解码: roomNO+dayNO+timeNO,5字符+1字符+2字符=8字符 */ 105 | function decodeTimeCode(timeCode) { 106 | if (!timeCode) { 107 | return null; 108 | } 109 | return new TimeUnit(parseInt(timeCode.substr(0, 5)), 110 | parseInt(timeCode.substr(5, 1)), parseInt(timeCode.substr(6))); 111 | } 112 | 113 | function isExistClassInDay(day, _class) { 114 | var filter = day.forEach(function(time) { 115 | return time == _class; 116 | }) 117 | return filter && filter.length>0; 118 | } 119 | 120 | function isExistClassInRoom(room, _class) { 121 | for (var d=0; d interval) { 139 | interval = tmp; 140 | } 141 | } 142 | } 143 | return interval; 144 | } 145 | 146 | /** 数组升序 */ 147 | function compareNumAsc(a, b) { 148 | return a - b; 149 | } 150 | 151 | /** 数组降序 */ 152 | function compareNumDesc(a, b) { 153 | return b - a; 154 | } 155 | -------------------------------------------------------------------------------- /jsdemo/old/data.js: -------------------------------------------------------------------------------- 1 | // 7day * 12 timeframe 2 | // courses {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null}, 3 | // classroom {id:50001, roomType: RoomType.NORMAL, capacity:40, building:70001, floor:5, roomNO:502}, 4 | // teacher {id:20002, name:'教师2', disableTime: '30304'} 5 | // Zone {SP:0, JD:1} 6 | // RoomType = {NORMAL:0, COMPUTER:1, PLAYGROUD:2, LAB:3, MEDIA:4} 7 | 8 | // var Zone = {SP:0, JD:1}; 9 | // var RoomType = {NORMAL:0, COMPUTER:1, PLAYGROUD:2, LAB:3, MEDIA:4}; 10 | // courses {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null} 11 | // teacher {id:20002, name:'教师2', disableTime: '30304'} 12 | // class {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP}, 13 | var DataGenerator = function(){ 14 | var Param = { 15 | campus:[0,1], 16 | buildings : [ 17 | { 18 | roomTypeId :0, 19 | roomType : 'normal', 20 | zone:0, 21 | capacityNum:{ // 容量数量 22 | 50: 15, 23 | 100: 8, 24 | 150: 6, 25 | } 26 | },{ 27 | roomTypeId :0, 28 | roomType : 'normal', 29 | zone:1, 30 | capacityNum:{ // 容量数量 31 | 50: 15, 32 | 100: 8, 33 | 150: 6, 34 | } 35 | },{ 36 | roomTypeId :3, 37 | roomType : 'lab', 38 | zone: 0, 39 | capacityNum:{ // 容量数量 40 | 50: 2, 41 | } 42 | },{ 43 | roomTypeId :3, 44 | roomType : 'lab', 45 | zone: 1, 46 | capacityNum:{ // 容量数量 47 | 50: 2, 48 | } 49 | }, 50 | ], 51 | // 排课类型 52 | courseType :{ 53 | A:{ 54 | roomType: 'normal', 55 | onceHour: 2, 56 | weekHour: 4, 57 | count: 50, 58 | classes 59 | }, 60 | B:{ 61 | roomType: 'normal', 62 | onceHour: 4, 63 | weekHour: 4, 64 | count: 40 65 | }, 66 | C:{ 67 | roomType: 'lab', 68 | onceHour: 2, 69 | weekHour: 4, 70 | count: 20 71 | } 72 | }, 73 | // 教师人数 74 | teacher :{ 75 | count: 100 76 | } 77 | } 78 | console.log(Param) 79 | // 0开始转换为 字母表示 80 | var letterIndex = function(index){ 81 | // ascii 65 A-90 Z 82 | var strIndex = ''; 83 | while(index >= 0) { 84 | var mod = index % 26; 85 | strIndex = String.fromCharCode(mod+65)+ strIndex; 86 | index = (index-mod)/26-1; 87 | } ; 88 | return strIndex; 89 | } 90 | var randomStudentNum = function(){ 91 | var ran = Math.random()*30; 92 | if(ran<15){ 93 | return 45; 94 | } 95 | if (ran<25){ 96 | return 85; 97 | } 98 | return 140; 99 | } 100 | // teacher {id:20002, name:'教师2', disableTime: '30304'} 101 | this.generateTeachers = function (){ 102 | var count = Param.teacher.count; 103 | var teachers =[]; 104 | for (let i = 0; i < count; i++) { 105 | teachers.push({ 106 | id: 20000+teachers.length, 107 | name :'教师'+i, 108 | disableTime : null 109 | }) 110 | } 111 | this.teachers = teachers; 112 | } 113 | 114 | this.generateLessons = function () { 115 | var lessons = []; 116 | for (const key in Param.courseType) { 117 | if (Param.courseType.hasOwnProperty(key)) { 118 | let course = Param.courseType[key]; 119 | for (let i = 0; i < course.count; i++) { 120 | // courses {id:10001, name:'游戏设计', weekHour:4, totalHour:34, roomType:RoomType.NORMAL, onceHour:2, timeRequire:null} 121 | lessons.push({ 122 | id: 10000+lessons.length, 123 | name: '课程'+letterIndex(lessons.length), 124 | weekHour: course.weekHour, 125 | roomType: course.roomType, 126 | onceHour: course.onceHour 127 | }); 128 | } 129 | } 130 | } 131 | this.lessons = lessons; 132 | } 133 | // class {id:30101, course:10001, teacher:20001, studentNum:28, zone:Zone.SP}, 134 | this.generateClasses = function () { 135 | 136 | var lessons = this.lessons; 137 | var classes = []; 138 | for (const lesson of lessons) { 139 | var classCount = Math.ceil(Math.random()*3) //随机给每门课1-3个班级 140 | var teachers = this.teachers; 141 | var campus = Math.round(Math.random()); //随机 0和1 142 | var teacher =teachers[ Math.floor(Math.random()*teachers.length)]; // 随机取一个老师 143 | for(let i =0; i 2 | 3 | 4 | 5 | 遗传算法 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 教室课表: 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | 教学班课表: 34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 346 | 347 | -------------------------------------------------------------------------------- /jsdemo/old/ga.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 遗传算法 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 教室课表: 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | 教学班课表: 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /jsdemo/old/lib/grid.locale-cn.js: -------------------------------------------------------------------------------- 1 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery","../grid.base"],a):a(jQuery)}(function(a){a.jgrid=a.jgrid||{},a.jgrid.hasOwnProperty("regional")||(a.jgrid.regional=[]),a.jgrid.regional.cn={defaults:{recordtext:"第{0}到第{1}条 共 {2} 条",emptyrecords:"没有记录!",loadtext:"读取中...",savetext:"保存中...",pgtext:"第{0}页 共{1}页",pgfirst:"第一页",pglast:"最后一页",pgnext:"下一页",pgprev:"上一页",pgrecs:"每页记录数",showhide:"切换 展开 折叠 表格",pagerCaption:"表格::页面设置",pageText:"Page:",recordPage:"每页记录数",nomorerecs:"没有更多记录...",scrollPullup:"加载更多...",scrollPulldown:"刷新...",scrollRefresh:"滚动刷新..."},search:{caption:"搜索...",Find:"查找",Reset:"重置",odata:[{oper:"eq",text:"等于  "},{oper:"ne",text:"不等于 "},{oper:"lt",text:"小于  "},{oper:"le",text:"小于等于"},{oper:"gt",text:"大于  "},{oper:"ge",text:"大于等于"},{oper:"bw",text:"开头是"},{oper:"bn",text:"开头不是"},{oper:"in",text:"属于  "},{oper:"ni",text:"不属于"},{oper:"ew",text:"结尾是"},{oper:"en",text:"结尾不是"},{oper:"cn",text:"包含  "},{oper:"nc",text:"不包含"},{oper:"nu",text:"为空"},{oper:"nn",text:"不为空"},{oper:"bt",text:"区间"}],groupOps:[{op:"AND",text:"满足所有条件"},{op:"OR",text:"满足任一条件"}],operandTitle:"单击进行搜索。",resetTitle:"重置搜索条件",addsubgrup:"添加条件组",addrule:"添加条件",delgroup:"删除条件组",delrule:"删除条件"},edit:{addCaption:"添加记录",editCaption:"编辑记录",bSubmit:"提交",bCancel:"取消",bClose:"关闭",saveData:"数据已修改,是否保存?",bYes:"是",bNo:"否",bExit:"取消",msg:{required:"此字段必需",number:"请输入有效数字",minValue:"输值必须大于等于 ",maxValue:"输值必须小于等于 ",email:"这不是有效的e-mail地址",integer:"请输入有效整数",date:"请输入有效时间",url:"无效网址。前缀必须为 ('http://' 或 'https://')",nodefined:" 未定义!",novalue:" 需要返回值!",customarray:"自定义函数需要返回数组!",customfcheck:"必须有自定义函数!"}},view:{caption:"查看记录",bClose:"关闭"},del:{caption:"删除",msg:"删除所选记录?",bSubmit:"删除",bCancel:"取消"},nav:{edittext:"",edittitle:"编辑所选记录",addtext:"",addtitle:"添加新记录",deltext:"",deltitle:"删除所选记录",searchtext:"",searchtitle:"查找",refreshtext:"",refreshtitle:"刷新表格",alertcap:"注意",alerttext:"请选择记录",viewtext:"",viewtitle:"查看所选记录",savetext:"",savetitle:"保存记录",canceltext:"",canceltitle:"取消编辑记录",selectcaption:"操作..."},col:{caption:"选择列",bSubmit:"确定",bCancel:"取消"},errors:{errcap:"错误",nourl:"没有设置url",norecords:"没有需要处理的记录",model:"colNames 和 colModel 长度不等!"},formatter:{integer:{thousandsSeparator:",",defaultValue:"0"},number:{decimalSeparator:".",thousandsSeparator:",",decimalPlaces:2,defaultValue:"0.00"},currency:{decimalSeparator:".",thousandsSeparator:",",decimalPlaces:2,prefix:"",suffix:"",defaultValue:"0.00"},date:{dayNames:["日","一","二","三","四","五","六","星期日","星期一","星期二","星期三","星期四","星期五","星期六"],monthNames:["一","二","三","四","五","六","七","八","九","十","十一","十二","一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],AmPm:["am","pm","上午","下午"],S:function(a){return a<11||a>13?["st","nd","rd","th"][Math.min((a-1)%10,3)]:"th"},srcformat:"Y-m-d",newformat:"Y-m-d",parseRe:/[#%\\\/:_;.,\t\s-]/,masks:{ISO8601Long:"Y-m-d H:i:s",ISO8601Short:"Y-m-d",ShortDate:"n/j/Y",LongDate:"l, F d, Y",FullDateTime:"l, F d, Y g:i:s A",MonthDay:"F d",ShortTime:"g:i A",LongTime:"g:i:s A",SortableDateTime:"Y-m-d\\TH:i:s",UniversalSortableDateTime:"Y-m-d H:i:sO",YearMonth:"F, Y"},reformatAfterEdit:!1,userLocalTime:!1},baseLinkUrl:"",showAction:"",target:"",checkbox:{disabled:!0},idName:"id"},colmenu:{sortasc:"升序排序",sortdesc:"降序排序",columns:"列",filter:"筛选",grouping:"分类",ungrouping:"取消分类",searchTitle:"查找:",freeze:"冻结",unfreeze:"取消冻结",reorder:"重新排序"}}}); -------------------------------------------------------------------------------- /jsdemo/old/lib/jquery-ui-custom.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery UI CSS Framework 1.8.23 3 | * 4 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 5 | * Dual licensed under the MIT or GPL Version 2 licenses. 6 | * http://jquery.org/license 7 | * 8 | * http://docs.jquery.com/UI/Theming/API 9 | */ 10 | 11 | /* Layout helpers 12 | ----------------------------------*/ 13 | .ui-helper-hidden { display: none; } 14 | .ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } 15 | .ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } 16 | .ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; } 17 | .ui-helper-clearfix:after { clear: both; } 18 | .ui-helper-clearfix { zoom: 1; } 19 | .ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } 20 | 21 | 22 | /* Interaction Cues 23 | ----------------------------------*/ 24 | .ui-state-disabled { cursor: default !important; } 25 | 26 | 27 | /* Icons 28 | ----------------------------------*/ 29 | 30 | /* states and images */ 31 | .ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } 32 | 33 | 34 | /* Misc visuals 35 | ----------------------------------*/ 36 | 37 | /* Overlays */ 38 | .ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } 39 | 40 | 41 | /*! 42 | * jQuery UI CSS Framework 1.8.23 43 | * 44 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 45 | * Dual licensed under the MIT or GPL Version 2 licenses. 46 | * http://jquery.org/license 47 | * 48 | * http://docs.jquery.com/UI/Theming/API 49 | * 50 | * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Lucida%20Grande,%20Lucida%20Sans,%20Arial,%20sans-serif&fwDefault=bold&fsDefault=1.1em&cornerRadius=5px&bgColorHeader=5c9ccc&bgTextureHeader=12_gloss_wave.png&bgImgOpacityHeader=55&borderColorHeader=4297d7&fcHeader=ffffff&iconColorHeader=d8e7f3&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=a6c9e2&fcContent=222222&iconColorContent=469bdd&bgColorDefault=dfeffc&bgTextureDefault=02_glass.png&bgImgOpacityDefault=85&borderColorDefault=c5dbec&fcDefault=2e6e9e&iconColorDefault=6da8d5&bgColorHover=d0e5f5&bgTextureHover=02_glass.png&bgImgOpacityHover=75&borderColorHover=79b7e7&fcHover=1d5987&iconColorHover=217bc0&bgColorActive=f5f8f9&bgTextureActive=06_inset_hard.png&bgImgOpacityActive=100&borderColorActive=79b7e7&fcActive=e17009&iconColorActive=f9bd01&bgColorHighlight=fbec88&bgTextureHighlight=01_flat.png&bgImgOpacityHighlight=55&borderColorHighlight=fad42e&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=02_glass.png&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=01_flat.png&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px 51 | */ 52 | 53 | 54 | /* Component containers 55 | ----------------------------------*/ 56 | .ui-widget { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1.1em; } 57 | .ui-widget .ui-widget { font-size: 1em; } 58 | .ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Lucida Grande, Lucida Sans, Arial, sans-serif; font-size: 1em; } 59 | .ui-widget-content { border: 1px solid #a6c9e2; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; } 60 | .ui-widget-content a { color: #222222; } 61 | .ui-widget-header { border: 1px solid #4297d7; background: #5c9ccc url(images/ui-bg_gloss-wave_55_5c9ccc_500x100.png) 50% 50% repeat-x; color: #ffffff; font-weight: bold; } 62 | .ui-widget-header a { color: #ffffff; } 63 | 64 | /* Interaction states 65 | ----------------------------------*/ 66 | .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #c5dbec; background: #dfeffc url(images/ui-bg_glass_85_dfeffc_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #2e6e9e; } 67 | .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #2e6e9e; text-decoration: none; } 68 | .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #79b7e7; background: #d0e5f5 url(images/ui-bg_glass_75_d0e5f5_1x400.png) 50% 50% repeat-x; font-weight: bold; color: #1d5987; } 69 | .ui-state-hover a, .ui-state-hover a:hover { color: #1d5987; text-decoration: none; } 70 | .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #79b7e7; background: #f5f8f9 url(images/ui-bg_inset-hard_100_f5f8f9_1x100.png) 50% 50% repeat-x; font-weight: bold; color: #e17009; } 71 | .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #e17009; text-decoration: none; } 72 | .ui-widget :active { outline: none; } 73 | 74 | /* Interaction Cues 75 | ----------------------------------*/ 76 | .ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fad42e; background: #fbec88 url(images/ui-bg_flat_55_fbec88_40x100.png) 50% 50% repeat-x; color: #363636; } 77 | .ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636; } 78 | .ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(images/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; } 79 | .ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a; } 80 | .ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a; } 81 | .ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } 82 | .ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } 83 | .ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } 84 | 85 | /* Icons 86 | ----------------------------------*/ 87 | 88 | /* states and images */ 89 | .ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_469bdd_256x240.png); } 90 | .ui-widget-content .ui-icon {background-image: url(images/ui-icons_469bdd_256x240.png); } 91 | .ui-widget-header .ui-icon {background-image: url(images/ui-icons_d8e7f3_256x240.png); } 92 | .ui-state-default .ui-icon { background-image: url(images/ui-icons_6da8d5_256x240.png); } 93 | .ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_217bc0_256x240.png); } 94 | .ui-state-active .ui-icon {background-image: url(images/ui-icons_f9bd01_256x240.png); } 95 | .ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png); } 96 | .ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png); } 97 | 98 | /* positioning */ 99 | .ui-icon-carat-1-n { background-position: 0 0; } 100 | .ui-icon-carat-1-ne { background-position: -16px 0; } 101 | .ui-icon-carat-1-e { background-position: -32px 0; } 102 | .ui-icon-carat-1-se { background-position: -48px 0; } 103 | .ui-icon-carat-1-s { background-position: -64px 0; } 104 | .ui-icon-carat-1-sw { background-position: -80px 0; } 105 | .ui-icon-carat-1-w { background-position: -96px 0; } 106 | .ui-icon-carat-1-nw { background-position: -112px 0; } 107 | .ui-icon-carat-2-n-s { background-position: -128px 0; } 108 | .ui-icon-carat-2-e-w { background-position: -144px 0; } 109 | .ui-icon-triangle-1-n { background-position: 0 -16px; } 110 | .ui-icon-triangle-1-ne { background-position: -16px -16px; } 111 | .ui-icon-triangle-1-e { background-position: -32px -16px; } 112 | .ui-icon-triangle-1-se { background-position: -48px -16px; } 113 | .ui-icon-triangle-1-s { background-position: -64px -16px; } 114 | .ui-icon-triangle-1-sw { background-position: -80px -16px; } 115 | .ui-icon-triangle-1-w { background-position: -96px -16px; } 116 | .ui-icon-triangle-1-nw { background-position: -112px -16px; } 117 | .ui-icon-triangle-2-n-s { background-position: -128px -16px; } 118 | .ui-icon-triangle-2-e-w { background-position: -144px -16px; } 119 | .ui-icon-arrow-1-n { background-position: 0 -32px; } 120 | .ui-icon-arrow-1-ne { background-position: -16px -32px; } 121 | .ui-icon-arrow-1-e { background-position: -32px -32px; } 122 | .ui-icon-arrow-1-se { background-position: -48px -32px; } 123 | .ui-icon-arrow-1-s { background-position: -64px -32px; } 124 | .ui-icon-arrow-1-sw { background-position: -80px -32px; } 125 | .ui-icon-arrow-1-w { background-position: -96px -32px; } 126 | .ui-icon-arrow-1-nw { background-position: -112px -32px; } 127 | .ui-icon-arrow-2-n-s { background-position: -128px -32px; } 128 | .ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } 129 | .ui-icon-arrow-2-e-w { background-position: -160px -32px; } 130 | .ui-icon-arrow-2-se-nw { background-position: -176px -32px; } 131 | .ui-icon-arrowstop-1-n { background-position: -192px -32px; } 132 | .ui-icon-arrowstop-1-e { background-position: -208px -32px; } 133 | .ui-icon-arrowstop-1-s { background-position: -224px -32px; } 134 | .ui-icon-arrowstop-1-w { background-position: -240px -32px; } 135 | .ui-icon-arrowthick-1-n { background-position: 0 -48px; } 136 | .ui-icon-arrowthick-1-ne { background-position: -16px -48px; } 137 | .ui-icon-arrowthick-1-e { background-position: -32px -48px; } 138 | .ui-icon-arrowthick-1-se { background-position: -48px -48px; } 139 | .ui-icon-arrowthick-1-s { background-position: -64px -48px; } 140 | .ui-icon-arrowthick-1-sw { background-position: -80px -48px; } 141 | .ui-icon-arrowthick-1-w { background-position: -96px -48px; } 142 | .ui-icon-arrowthick-1-nw { background-position: -112px -48px; } 143 | .ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } 144 | .ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } 145 | .ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } 146 | .ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } 147 | .ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } 148 | .ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } 149 | .ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } 150 | .ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } 151 | .ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } 152 | .ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } 153 | .ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } 154 | .ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } 155 | .ui-icon-arrowreturn-1-w { background-position: -64px -64px; } 156 | .ui-icon-arrowreturn-1-n { background-position: -80px -64px; } 157 | .ui-icon-arrowreturn-1-e { background-position: -96px -64px; } 158 | .ui-icon-arrowreturn-1-s { background-position: -112px -64px; } 159 | .ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } 160 | .ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } 161 | .ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } 162 | .ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } 163 | .ui-icon-arrow-4 { background-position: 0 -80px; } 164 | .ui-icon-arrow-4-diag { background-position: -16px -80px; } 165 | .ui-icon-extlink { background-position: -32px -80px; } 166 | .ui-icon-newwin { background-position: -48px -80px; } 167 | .ui-icon-refresh { background-position: -64px -80px; } 168 | .ui-icon-shuffle { background-position: -80px -80px; } 169 | .ui-icon-transfer-e-w { background-position: -96px -80px; } 170 | .ui-icon-transferthick-e-w { background-position: -112px -80px; } 171 | .ui-icon-folder-collapsed { background-position: 0 -96px; } 172 | .ui-icon-folder-open { background-position: -16px -96px; } 173 | .ui-icon-document { background-position: -32px -96px; } 174 | .ui-icon-document-b { background-position: -48px -96px; } 175 | .ui-icon-note { background-position: -64px -96px; } 176 | .ui-icon-mail-closed { background-position: -80px -96px; } 177 | .ui-icon-mail-open { background-position: -96px -96px; } 178 | .ui-icon-suitcase { background-position: -112px -96px; } 179 | .ui-icon-comment { background-position: -128px -96px; } 180 | .ui-icon-person { background-position: -144px -96px; } 181 | .ui-icon-print { background-position: -160px -96px; } 182 | .ui-icon-trash { background-position: -176px -96px; } 183 | .ui-icon-locked { background-position: -192px -96px; } 184 | .ui-icon-unlocked { background-position: -208px -96px; } 185 | .ui-icon-bookmark { background-position: -224px -96px; } 186 | .ui-icon-tag { background-position: -240px -96px; } 187 | .ui-icon-home { background-position: 0 -112px; } 188 | .ui-icon-flag { background-position: -16px -112px; } 189 | .ui-icon-calendar { background-position: -32px -112px; } 190 | .ui-icon-cart { background-position: -48px -112px; } 191 | .ui-icon-pencil { background-position: -64px -112px; } 192 | .ui-icon-clock { background-position: -80px -112px; } 193 | .ui-icon-disk { background-position: -96px -112px; } 194 | .ui-icon-calculator { background-position: -112px -112px; } 195 | .ui-icon-zoomin { background-position: -128px -112px; } 196 | .ui-icon-zoomout { background-position: -144px -112px; } 197 | .ui-icon-search { background-position: -160px -112px; } 198 | .ui-icon-wrench { background-position: -176px -112px; } 199 | .ui-icon-gear { background-position: -192px -112px; } 200 | .ui-icon-heart { background-position: -208px -112px; } 201 | .ui-icon-star { background-position: -224px -112px; } 202 | .ui-icon-link { background-position: -240px -112px; } 203 | .ui-icon-cancel { background-position: 0 -128px; } 204 | .ui-icon-plus { background-position: -16px -128px; } 205 | .ui-icon-plusthick { background-position: -32px -128px; } 206 | .ui-icon-minus { background-position: -48px -128px; } 207 | .ui-icon-minusthick { background-position: -64px -128px; } 208 | .ui-icon-close { background-position: -80px -128px; } 209 | .ui-icon-closethick { background-position: -96px -128px; } 210 | .ui-icon-key { background-position: -112px -128px; } 211 | .ui-icon-lightbulb { background-position: -128px -128px; } 212 | .ui-icon-scissors { background-position: -144px -128px; } 213 | .ui-icon-clipboard { background-position: -160px -128px; } 214 | .ui-icon-copy { background-position: -176px -128px; } 215 | .ui-icon-contact { background-position: -192px -128px; } 216 | .ui-icon-image { background-position: -208px -128px; } 217 | .ui-icon-video { background-position: -224px -128px; } 218 | .ui-icon-script { background-position: -240px -128px; } 219 | .ui-icon-alert { background-position: 0 -144px; } 220 | .ui-icon-info { background-position: -16px -144px; } 221 | .ui-icon-notice { background-position: -32px -144px; } 222 | .ui-icon-help { background-position: -48px -144px; } 223 | .ui-icon-check { background-position: -64px -144px; } 224 | .ui-icon-bullet { background-position: -80px -144px; } 225 | .ui-icon-radio-off { background-position: -96px -144px; } 226 | .ui-icon-radio-on { background-position: -112px -144px; } 227 | .ui-icon-pin-w { background-position: -128px -144px; } 228 | .ui-icon-pin-s { background-position: -144px -144px; } 229 | .ui-icon-play { background-position: 0 -160px; } 230 | .ui-icon-pause { background-position: -16px -160px; } 231 | .ui-icon-seek-next { background-position: -32px -160px; } 232 | .ui-icon-seek-prev { background-position: -48px -160px; } 233 | .ui-icon-seek-end { background-position: -64px -160px; } 234 | .ui-icon-seek-start { background-position: -80px -160px; } 235 | /* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ 236 | .ui-icon-seek-first { background-position: -80px -160px; } 237 | .ui-icon-stop { background-position: -96px -160px; } 238 | .ui-icon-eject { background-position: -112px -160px; } 239 | .ui-icon-volume-off { background-position: -128px -160px; } 240 | .ui-icon-volume-on { background-position: -144px -160px; } 241 | .ui-icon-power { background-position: 0 -176px; } 242 | .ui-icon-signal-diag { background-position: -16px -176px; } 243 | .ui-icon-signal { background-position: -32px -176px; } 244 | .ui-icon-battery-0 { background-position: -48px -176px; } 245 | .ui-icon-battery-1 { background-position: -64px -176px; } 246 | .ui-icon-battery-2 { background-position: -80px -176px; } 247 | .ui-icon-battery-3 { background-position: -96px -176px; } 248 | .ui-icon-circle-plus { background-position: 0 -192px; } 249 | .ui-icon-circle-minus { background-position: -16px -192px; } 250 | .ui-icon-circle-close { background-position: -32px -192px; } 251 | .ui-icon-circle-triangle-e { background-position: -48px -192px; } 252 | .ui-icon-circle-triangle-s { background-position: -64px -192px; } 253 | .ui-icon-circle-triangle-w { background-position: -80px -192px; } 254 | .ui-icon-circle-triangle-n { background-position: -96px -192px; } 255 | .ui-icon-circle-arrow-e { background-position: -112px -192px; } 256 | .ui-icon-circle-arrow-s { background-position: -128px -192px; } 257 | .ui-icon-circle-arrow-w { background-position: -144px -192px; } 258 | .ui-icon-circle-arrow-n { background-position: -160px -192px; } 259 | .ui-icon-circle-zoomin { background-position: -176px -192px; } 260 | .ui-icon-circle-zoomout { background-position: -192px -192px; } 261 | .ui-icon-circle-check { background-position: -208px -192px; } 262 | .ui-icon-circlesmall-plus { background-position: 0 -208px; } 263 | .ui-icon-circlesmall-minus { background-position: -16px -208px; } 264 | .ui-icon-circlesmall-close { background-position: -32px -208px; } 265 | .ui-icon-squaresmall-plus { background-position: -48px -208px; } 266 | .ui-icon-squaresmall-minus { background-position: -64px -208px; } 267 | .ui-icon-squaresmall-close { background-position: -80px -208px; } 268 | .ui-icon-grip-dotted-vertical { background-position: 0 -224px; } 269 | .ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } 270 | .ui-icon-grip-solid-vertical { background-position: -32px -224px; } 271 | .ui-icon-grip-solid-horizontal { background-position: -48px -224px; } 272 | .ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } 273 | .ui-icon-grip-diagonal-se { background-position: -80px -224px; } 274 | 275 | 276 | /* Misc visuals 277 | ----------------------------------*/ 278 | 279 | /* Corner radius */ 280 | .ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 5px; -webkit-border-top-left-radius: 5px; -khtml-border-top-left-radius: 5px; border-top-left-radius: 5px; } 281 | .ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 5px; -webkit-border-top-right-radius: 5px; -khtml-border-top-right-radius: 5px; border-top-right-radius: 5px; } 282 | .ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 5px; -webkit-border-bottom-left-radius: 5px; -khtml-border-bottom-left-radius: 5px; border-bottom-left-radius: 5px; } 283 | .ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 5px; -webkit-border-bottom-right-radius: 5px; -khtml-border-bottom-right-radius: 5px; border-bottom-right-radius: 5px; } 284 | 285 | /* Overlays */ 286 | .ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); } 287 | .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(images/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -khtml-border-radius: 8px; -webkit-border-radius: 8px; border-radius: 8px; }/*! 288 | * jQuery UI Resizable 1.8.23 289 | * 290 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 291 | * Dual licensed under the MIT or GPL Version 2 licenses. 292 | * http://jquery.org/license 293 | * 294 | * http://docs.jquery.com/UI/Resizable#theming 295 | */ 296 | .ui-resizable { position: relative;} 297 | .ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; } 298 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } 299 | .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } 300 | .ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } 301 | .ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } 302 | .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } 303 | .ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } 304 | .ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } 305 | .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } 306 | .ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*! 307 | * jQuery UI Selectable 1.8.23 308 | * 309 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 310 | * Dual licensed under the MIT or GPL Version 2 licenses. 311 | * http://jquery.org/license 312 | * 313 | * http://docs.jquery.com/UI/Selectable#theming 314 | */ 315 | .ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } 316 | /*! 317 | * jQuery UI Accordion 1.8.23 318 | * 319 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 320 | * Dual licensed under the MIT or GPL Version 2 licenses. 321 | * http://jquery.org/license 322 | * 323 | * http://docs.jquery.com/UI/Accordion#theming 324 | */ 325 | /* IE/Win - Fix animation bug - #4615 */ 326 | .ui-accordion { width: 100%; } 327 | .ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } 328 | .ui-accordion .ui-accordion-li-fix { display: inline; } 329 | .ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } 330 | .ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } 331 | .ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } 332 | .ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } 333 | .ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } 334 | .ui-accordion .ui-accordion-content-active { display: block; } 335 | /*! 336 | * jQuery UI Autocomplete 1.8.23 337 | * 338 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 339 | * Dual licensed under the MIT or GPL Version 2 licenses. 340 | * http://jquery.org/license 341 | * 342 | * http://docs.jquery.com/UI/Autocomplete#theming 343 | */ 344 | .ui-autocomplete { position: absolute; cursor: default; } 345 | 346 | /* workarounds */ 347 | * html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ 348 | 349 | /* 350 | * jQuery UI Menu 1.8.23 351 | * 352 | * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 353 | * Dual licensed under the MIT or GPL Version 2 licenses. 354 | * http://jquery.org/license 355 | * 356 | * http://docs.jquery.com/UI/Menu#theming 357 | */ 358 | .ui-menu { 359 | list-style:none; 360 | padding: 2px; 361 | margin: 0; 362 | display:block; 363 | float: left; 364 | } 365 | .ui-menu .ui-menu { 366 | margin-top: -3px; 367 | } 368 | .ui-menu .ui-menu-item { 369 | margin:0; 370 | padding: 0; 371 | zoom: 1; 372 | float: left; 373 | clear: left; 374 | width: 100%; 375 | } 376 | .ui-menu .ui-menu-item a { 377 | text-decoration:none; 378 | display:block; 379 | padding:.2em .4em; 380 | line-height:1.5; 381 | zoom:1; 382 | } 383 | .ui-menu .ui-menu-item a.ui-state-hover, 384 | .ui-menu .ui-menu-item a.ui-state-active { 385 | font-weight: normal; 386 | margin: -1px; 387 | } 388 | /*! 389 | * jQuery UI Button 1.8.23 390 | * 391 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 392 | * Dual licensed under the MIT or GPL Version 2 licenses. 393 | * http://jquery.org/license 394 | * 395 | * http://docs.jquery.com/UI/Button#theming 396 | */ 397 | .ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ 398 | .ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ 399 | button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ 400 | .ui-button-icons-only { width: 3.4em; } 401 | button.ui-button-icons-only { width: 3.7em; } 402 | 403 | /*button text element */ 404 | .ui-button .ui-button-text { display: block; line-height: 1.4; } 405 | .ui-button-text-only .ui-button-text { padding: .4em 1em; } 406 | .ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } 407 | .ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } 408 | .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } 409 | .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } 410 | /* no icon support for input elements, provide padding by default */ 411 | input.ui-button { padding: .4em 1em; } 412 | 413 | /*button icon element(s) */ 414 | .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } 415 | .ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } 416 | .ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } 417 | .ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 418 | .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } 419 | 420 | /*button sets*/ 421 | .ui-buttonset { margin-right: 7px; } 422 | .ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } 423 | 424 | /* workarounds */ 425 | button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ 426 | /*! 427 | * jQuery UI Dialog 1.8.23 428 | * 429 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 430 | * Dual licensed under the MIT or GPL Version 2 licenses. 431 | * http://jquery.org/license 432 | * 433 | * http://docs.jquery.com/UI/Dialog#theming 434 | */ 435 | .ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } 436 | .ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } 437 | .ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } 438 | .ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } 439 | .ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } 440 | .ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } 441 | .ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } 442 | .ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } 443 | .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } 444 | .ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } 445 | .ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } 446 | .ui-draggable .ui-dialog-titlebar { cursor: move; } 447 | /*! 448 | * jQuery UI Slider 1.8.23 449 | * 450 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 451 | * Dual licensed under the MIT or GPL Version 2 licenses. 452 | * http://jquery.org/license 453 | * 454 | * http://docs.jquery.com/UI/Slider#theming 455 | */ 456 | .ui-slider { position: relative; text-align: left; } 457 | .ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } 458 | .ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } 459 | 460 | .ui-slider-horizontal { height: .8em; } 461 | .ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } 462 | .ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } 463 | .ui-slider-horizontal .ui-slider-range-min { left: 0; } 464 | .ui-slider-horizontal .ui-slider-range-max { right: 0; } 465 | 466 | .ui-slider-vertical { width: .8em; height: 100px; } 467 | .ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } 468 | .ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } 469 | .ui-slider-vertical .ui-slider-range-min { bottom: 0; } 470 | .ui-slider-vertical .ui-slider-range-max { top: 0; }/*! 471 | * jQuery UI Tabs 1.8.23 472 | * 473 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 474 | * Dual licensed under the MIT or GPL Version 2 licenses. 475 | * http://jquery.org/license 476 | * 477 | * http://docs.jquery.com/UI/Tabs#theming 478 | */ 479 | .ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ 480 | .ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } 481 | .ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } 482 | .ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } 483 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } 484 | .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } 485 | .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ 486 | .ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } 487 | .ui-tabs .ui-tabs-hide { display: none !important; } 488 | /*! 489 | * jQuery UI Datepicker 1.8.23 490 | * 491 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 492 | * Dual licensed under the MIT or GPL Version 2 licenses. 493 | * http://jquery.org/license 494 | * 495 | * http://docs.jquery.com/UI/Datepicker#theming 496 | */ 497 | .ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } 498 | .ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } 499 | .ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } 500 | .ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } 501 | .ui-datepicker .ui-datepicker-prev { left:2px; } 502 | .ui-datepicker .ui-datepicker-next { right:2px; } 503 | .ui-datepicker .ui-datepicker-prev-hover { left:1px; } 504 | .ui-datepicker .ui-datepicker-next-hover { right:1px; } 505 | .ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } 506 | .ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } 507 | .ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } 508 | .ui-datepicker select.ui-datepicker-month-year {width: 100%;} 509 | .ui-datepicker select.ui-datepicker-month, 510 | .ui-datepicker select.ui-datepicker-year { width: 49%;} 511 | .ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } 512 | .ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } 513 | .ui-datepicker td { border: 0; padding: 1px; } 514 | .ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } 515 | .ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } 516 | .ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } 517 | .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } 518 | 519 | /* with multiple calendars */ 520 | .ui-datepicker.ui-datepicker-multi { width:auto; } 521 | .ui-datepicker-multi .ui-datepicker-group { float:left; } 522 | .ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } 523 | .ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } 524 | .ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } 525 | .ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } 526 | .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } 527 | .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } 528 | .ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } 529 | .ui-datepicker-row-break { clear:both; width:100%; font-size:0em; } 530 | 531 | /* RTL support */ 532 | .ui-datepicker-rtl { direction: rtl; } 533 | .ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } 534 | .ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } 535 | .ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } 536 | .ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } 537 | .ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } 538 | .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } 539 | .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } 540 | .ui-datepicker-rtl .ui-datepicker-group { float:right; } 541 | .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } 542 | .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } 543 | 544 | /* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ 545 | .ui-datepicker-cover { 546 | position: absolute; /*must have*/ 547 | z-index: -1; /*must have*/ 548 | filter: mask(); /*must have*/ 549 | top: -4px; /*must have*/ 550 | left: -4px; /*must have*/ 551 | width: 200px; /*must have*/ 552 | height: 200px; /*must have*/ 553 | }/*! 554 | * jQuery UI Progressbar 1.8.23 555 | * 556 | * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about) 557 | * Dual licensed under the MIT or GPL Version 2 licenses. 558 | * http://jquery.org/license 559 | * 560 | * http://docs.jquery.com/UI/Progressbar#theming 561 | */ 562 | .ui-progressbar { height:2em; text-align: left; overflow: hidden; } 563 | .ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } -------------------------------------------------------------------------------- /jsdemo/old/lib/ui.jqgrid.css: -------------------------------------------------------------------------------- 1 | /*Grid*/ 2 | .ui-jqgrid { 3 | position: relative; 4 | -moz-box-sizing: content-box; 5 | -webkit-box-sizing: content-box; 6 | box-sizing: content-box; 7 | font-size:11px; 8 | } 9 | .ui-jqgrid .ui-jqgrid-view { 10 | position: relative; 11 | left:0; 12 | top: 0; 13 | padding: 0; 14 | /*font-size:11px;*/ 15 | z-index:100; 16 | } 17 | .ui-jqgrid .ui-common-table {border-width: 0px; border-style: none; border-spacing: 0px; padding: 0;} 18 | /* caption*/ 19 | .ui-jqgrid .ui-jqgrid-titlebar {height:19px; padding: .3em .2em .2em .3em; position: relative; font-size: 12px; border-left: 0 none;border-right: 0 none; border-top: 0 none;} 20 | .ui-jqgrid .ui-jqgrid-caption {text-align: left;} 21 | .ui-jqgrid .ui-jqgrid-title { 22 | padding: .2em .1em .1em; 23 | position: absolute; 24 | } 25 | .ui-jqgrid .ui-jqgrid-titlebar-close { position: absolute;top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height:18px; cursor:pointer;} 26 | .ui-jqgrid .ui-jqgrid-titlebar-close span { display: block; margin: 1px; } 27 | .ui-jqgrid .ui-jqgrid-titlebar-close:hover { padding: 0; } 28 | .ui-jqgrid .menubar:hover { border: 0 none;} 29 | /* header*/ 30 | .ui-jqgrid .ui-jqgrid-hdiv {position: relative; margin: 0;padding: 0; overflow: hidden; border-left: 0 none !important; border-top : 0 none !important; border-right : 0 none !important;} 31 | .ui-jqgrid .ui-jqgrid-hbox {float: left; padding-right: 20px;} 32 | .ui-jqgrid .ui-jqgrid-htable {table-layout:fixed;margin:0;border-collapse: separate;} 33 | .ui-jqgrid .ui-jqgrid-htable th { height: 27px; padding: 0 2px 0 2px;} 34 | .ui-jqgrid .ui-jqgrid-htable th div {overflow: hidden; position:relative;margin: .1em 0em .1em 0em;} 35 | .ui-th-column, .ui-jqgrid .ui-jqgrid-htable th.ui-th-column {overflow: hidden;white-space: nowrap;text-align:center;border-top : 0 none;border-bottom : 0 none;} 36 | .ui-th-column-header, .ui-jqgrid .ui-jqgrid-htable th.ui-th-column-header {overflow: hidden;white-space: nowrap;text-align:center;border-top : 0 none; height: 26px;} 37 | .ui-th-ltr, .ui-jqgrid .ui-jqgrid-htable th.ui-th-ltr {border-left : 0 none;} 38 | .ui-th-rtl, .ui-jqgrid .ui-jqgrid-htable th.ui-th-rtl {border-right : 0 none;} 39 | .ui-first-th-ltr {border-right: 1px solid; } 40 | .ui-first-th-rtl {border-left: 1px solid; } 41 | .ui-jqgrid .ui-th-div-ie {white-space: nowrap; zoom :1; height:17px;} 42 | .ui-jqgrid .ui-jqgrid-resize {height:20px !important;position: relative; cursor :e-resize;display: inline;overflow: hidden;} 43 | .ui-jqgrid .ui-grid-ico-sort {overflow:hidden;position:absolute;display:inline; cursor: pointer !important;} 44 | .ui-jqgrid .ui-icon-asc {margin-top:-3px; height:12px;} 45 | .ui-jqgrid .ui-icon-desc {margin-top:3px;margin-left:-1px;height:12px;} 46 | .ui-jqgrid .ui-i-asc {margin-top:0;height:18px;} 47 | .ui-jqgrid .ui-i-desc {margin-top:0;margin-left:12px;height:18px;} 48 | .ui-jqgrid .ui-single-sort-asc {margin-top:0;height:18px;} 49 | .ui-jqgrid .ui-single-sort-desc {margin-top:-1px;height:18px;} 50 | .ui-jqgrid .ui-jqgrid-sortable {cursor:pointer;height:14px} 51 | .ui-jqgrid tr.ui-search-toolbar th { } 52 | .ui-jqgrid .ui-search-table td.ui-search-clear { width:25px;} 53 | .ui-jqgrid tr.ui-search-toolbar td > input { padding-right: 0px; width: 95%;} 54 | .ui-jqgrid tr.ui-search-toolbar select {} 55 | /* body */ 56 | .ui-jqgrid .ui-jqgrid-bdiv {position: relative; margin: 0; padding:0; overflow: auto; text-align:left;z-index: 101;} 57 | .ui-jqgrid .ui-jqgrid-btable {table-layout:fixed; margin:0; outline-style: none; border-collapse: separate;} 58 | .ui-jqgrid tr.jqgrow, 59 | .ui-jqgrid tr.jqgroup 60 | { outline-style: none; } 61 | .ui-jqgrid tr.jqgrow td, 62 | .ui-jqgrid tr.jqgroup td 63 | {font-weight: normal; overflow: hidden; white-space: pre; height: 23px;padding: 1px 2px 1px 2px;border-bottom-width: 1px; border-bottom-color: inherit; border-bottom-style: solid;} 64 | .ui-jqgrid tr.jqgfirstrow td {padding: 0 2px 0 2px;border-right-width: 1px; border-right-style: solid; height:auto;} 65 | .ui-jqgrid tr.jqfoot td {font-weight: bold; overflow: hidden; white-space: pre; height: 22px;padding: 0 2px 0 2px;border-bottom-width: 1px; border-bottom-color: inherit; border-bottom-style: solid;} 66 | .ui-jqgrid tr.ui-row-ltr td {text-align:left;border-right-width: 1px; border-right-color: inherit; border-right-style: solid;} 67 | .ui-jqgrid tr.ui-row-rtl td {text-align:right;border-left-width: 1px; border-left-color: inherit; border-left-style: solid;} 68 | .ui-jqgrid td.jqgrid-rownum { padding: 0 2px 0 2px; margin: 0; border: 0 none;} 69 | .ui-jqgrid .ui-jqgrid-resize-mark { width:2px; left:0; background-color:#777; cursor: e-resize; cursor: col-resize; position:absolute; top:0; height:100px; overflow:hidden; display:none; border:0 none; z-index: 99999;} 70 | .ui-jqgrid-table-striped > tbody > tr:nth-of-type(odd) { 71 | opacity: .7; 72 | font-weight: normal; 73 | } 74 | /* footer */ 75 | .ui-jqgrid .ui-jqgrid-sdiv {position: relative; margin: 0;padding: 0; overflow: hidden; border-left: 0 none !important; border-top : 0 none !important; border-right : 0 none !important;} 76 | .ui-jqgrid .ui-jqgrid-ftable {table-layout:fixed; margin-bottom:0;border-collapse: separate;} 77 | .ui-jqgrid tr.footrow td {font-weight: bold; overflow: hidden; white-space:nowrap; height: 23px;padding: 1px 2px 1px 2px;border-top-width: 1px; border-top-color: inherit; border-top-style: solid;border-bottom: 0 none;} 78 | .ui-jqgrid tr.footrow-ltr td {text-align:left;border-right-width: 1px; border-right-color: inherit; border-right-style: solid;} 79 | .ui-jqgrid tr.footrow-rtl td {text-align:right;border-left-width: 1px; border-left-color: inherit; border-left-style: solid;} 80 | /* Pager*/ 81 | .ui-jqgrid .ui-jqgrid-pager { 82 | border-left: 0 none !important; 83 | border-right: 0 none !important; 84 | border-bottom: 0 none !important; 85 | border-top: 0 none; 86 | margin: 0 !important; 87 | padding: 0 !important; 88 | position: relative; 89 | height: auto; 90 | min-height: 28px; 91 | white-space: nowrap; 92 | overflow: hidden; 93 | /*font-size:11px; */ 94 | z-index:100 95 | } 96 | .ui-jqgrid .ui-jqgrid-toppager .ui-pager-control, .ui-jqgrid .ui-jqgrid-pager .ui-pager-control {position: relative;border-left: 0;border-bottom: 0;border-top: 0; height: 28px;} 97 | .ui-jqgrid .ui-pg-table {position: relative; padding: 1px 0; width:auto; margin: 0;} 98 | .ui-jqgrid .ui-pg-table td {font-weight:normal; vertical-align:middle; padding:0px 1px;} 99 | .ui-jqgrid .ui-pg-button { height:auto} 100 | .ui-jqgrid .ui-pg-button span { display: block; margin: 2px; float:left;} 101 | .ui-jqgrid .ui-pg-button:hover { padding: 0;} 102 | .ui-jqgrid .ui-state-disabled:hover {padding:0px;} 103 | .ui-jqgrid .ui-pg-input,.ui-jqgrid .ui-jqgrid-toppager .ui-pg-input { height:14px;width: auto;font-size:.9em; margin:0;line-height: inherit;border: none; padding: 3px 2px} 104 | .ui-jqgrid .ui-pg-selbox, .ui-jqgrid .ui-jqgrid-toppager .ui-pg-selbox {font-size:.9em; line-height:inherit; display:block; height:19px; margin: 0; padding: 3px 0px; border:none;} 105 | .ui-jqgrid .ui-separator {height: 18px; border-left: 2px solid #ccc ;} 106 | .ui-separator-li {height: 2px; border : none;border-top: 2px solid #ccc ; margin: 0; padding: 0; width:100%} 107 | .ui-jqgrid .dropdownmenu { 108 | padding: 3px 0 3px 0; 109 | margin-left: 4px; 110 | } 111 | .ui-jqgrid .ui-jqgrid-pager .ui-pg-div, 112 | .ui-jqgrid .ui-jqgrid-toppager .ui-pg-div 113 | {padding:1px 0;float:left;position:relative; line-height: 20px;} 114 | .ui-jqgrid .ui-jqgrid-pager .ui-pg-button, 115 | .ui-jqgrid .ui-jqgrid-toppager .ui-pg-button 116 | { cursor:pointer; } 117 | .ui-jqgrid .ui-jqgrid-pager .ui-pg-div span.ui-icon, 118 | .ui-jqgrid .ui-jqgrid-toppager .ui-pg-div span.ui-icon 119 | {float:left;margin: 2px; width:18px;} 120 | .ui-jqgrid td input, .ui-jqgrid td select, .ui-jqgrid td textarea { margin: 0; padding-top:5px;padding-bottom: 5px;} 121 | .ui-jqgrid td textarea {width:auto;height:auto;} 122 | .ui-jqgrid .ui-jqgrid-toppager {border-left: 0 none !important;border-right: 0 none !important; border-top: 0 none !important; margin: 0 !important; padding: 0 !important; position: relative;white-space: nowrap;overflow: hidden;} 123 | .ui-jqgrid .ui-jqgrid-pager .ui-pager-table, 124 | .ui-jqgrid .ui-jqgrid-toppager .ui-pager-table 125 | { 126 | width:100%; 127 | table-layout:fixed; 128 | height:100%; 129 | } 130 | .ui-jqgrid .ui-jqgrid-pager .ui-paging-info, 131 | .ui-jqgrid .ui-jqgrid-toppager .ui-paging-info 132 | { 133 | font-weight: normal; 134 | height:auto; 135 | margin-top:3px; 136 | margin-right:4px; 137 | display: inline; 138 | } 139 | .ui-jqgrid .ui-jqgrid-pager .ui-paging-pager, 140 | .ui-jqgrid .ui-jqgrid-toppager .ui-paging-pager 141 | { 142 | table-layout:auto; 143 | height:100%; 144 | } 145 | .ui-jqgrid .ui-jqgrid-pager .navtable, 146 | .ui-jqgrid .ui-jqgrid-toppager .navtable 147 | { 148 | float:left; 149 | table-layout:auto; 150 | height:100%; 151 | } 152 | 153 | /*.ui-jqgrid .ui-jqgrid-toppager .ui-pg-div {padding:1px 0;float:left;position:relative; line-height: 20px; margin-right:3px;} 154 | .ui-jqgrid .ui-jqgrid-toppager .ui-pg-button { cursor:pointer; } 155 | .ui-jqgrid .ui-jqgrid-toppager .ui-pg-div span.ui-icon {float:left;margin: 2px; width:18px;} 156 | */ 157 | 158 | /*subgrid*/ 159 | .ui-jqgrid .ui-jqgrid-btable .ui-sgcollapsed span {display: block;} 160 | .ui-jqgrid .ui-subgrid {margin:0;padding:0; width:100%;} 161 | .ui-jqgrid .ui-subgrid table {table-layout: fixed;} 162 | .ui-jqgrid .ui-subgrid tr.ui-subtblcell td {height:18px;border-right-width: 1px; border-right-color: inherit; border-right-style: solid;border-bottom-width: 1px; border-bottom-color: inherit; border-bottom-style: solid;} 163 | .ui-jqgrid .ui-subgrid td.subgrid-data {border-top: 0 none !important; border-left: 0 none !important;} 164 | .ui-jqgrid .ui-subgrid td.subgrid-cell {border-width: 0 1px 1px 0;} 165 | .ui-jqgrid .ui-th-subgrid {height:20px;} 166 | /* loading */ 167 | .ui-jqgrid .loading, 168 | .loading_pivot { 169 | position: absolute; 170 | top: 45%; 171 | left: 45%; 172 | width: auto; 173 | z-index:101; 174 | padding: 6px; 175 | margin: 5px; 176 | text-align: center; 177 | font-weight: bold; 178 | display: none; 179 | border-width: 2px !important; 180 | /*font-size:11px;*/ 181 | } 182 | .ui-jqgrid .jqgrid-overlay {display:none;} 183 | /* IE * html .jqgrid-overlay {width: expression(this.parentNode.offsetWidth+'px');height: expression(this.parentNode.offsetHeight+'px');} */ 184 | * .jqgrid-overlay iframe {position:absolute;top:0;left:0;z-index:-1;} 185 | /* IE width: expression(this.parentNode.offsetWidth+'px');height: expression(this.parentNode.offsetHeight+'px');}*/ 186 | /* end loading div */ 187 | /* toolbar */ 188 | .ui-jqgrid .ui-userdata {border-left: 0 none; border-right: 0 none; height : 27px;overflow: hidden; } 189 | /*Modal Window */ 190 | .ui-jqdialog { 191 | /*font-size:11px !important; */ 192 | } 193 | .ui-jqdialog { 194 | display: none; 195 | width: 300px; 196 | position: absolute; 197 | padding: .2em; 198 | /*font-size:11px;*/ 199 | overflow:visible; 200 | } 201 | .ui-jqdialog .ui-jqdialog-titlebar { padding: .3em .2em; position: relative; height:20px;} 202 | .ui-jqdialog .ui-jqdialog-title { margin: .3em .2em .2em .2em;} 203 | .ui-jqdialog .ui-jqdialog-titlebar-close { position: absolute; top: 50%; width: 19px; margin: -12px 0 0 0; padding: 1px; height: 18px; cursor:pointer;} 204 | 205 | .ui-jqdialog .ui-jqdialog-titlebar-close span { display: block; margin: 1px; } 206 | .ui-jqdialog .ui-jqdialog-titlebar-close:hover, .ui-jqdialog .ui-jqdialog-titlebar-close:focus { padding: 0; } 207 | .ui-jqdialog-content, .ui-jqdialog .ui-jqdialog-content { border: 0; padding: .3em .2em; background: none; height:auto;} 208 | .ui-jqdialog .ui-jqconfirm {padding: .4em 1em; border-width:3px;position:absolute;bottom:10px;right:10px;overflow:visible;display:none;height:80px;width:220px;text-align:center;} 209 | .ui-jqdialog>.ui-resizable-se { 210 | bottom: 2px; 211 | right: 2px; 212 | background-position: -64px -224px; 213 | } 214 | .ui-jqgrid>.ui-resizable-se { bottom: -3px; right: -3px } 215 | .jqgrid-overlay-modal { display : none; } 216 | /* end Modal window*/ 217 | /* Form edit */ 218 | .ui-jqdialog-content .FormGrid {margin: 0; overflow:auto;position:relative;} 219 | .ui-jqdialog-content .EditTable { width: 100%; margin-bottom:0;} 220 | .ui-jqdialog-content .DelTable { width: 100%; margin-bottom:0;} 221 | .EditTable td input, .EditTable td select, .EditTable td textarea {margin: 0;} 222 | .EditTable td textarea { width:auto; height:auto;} 223 | .ui-jqdialog-content td.EditButton {text-align: right;border-top: 0 none;border-left: 0 none;border-right: 0 none; padding-bottom:5px; padding-top:5px;} 224 | .ui-jqdialog-content td.navButton {text-align: center; border-left: 0 none;border-top: 0 none;border-right: 0 none; padding-bottom:5px; padding-top:5px;} 225 | .ui-jqdialog-content input.FormElement { 226 | padding: .5em .3em; 227 | margin-bottom: 3px; 228 | font-size: inherit; 229 | } 230 | .ui-jqdialog-content select.FormElement { 231 | padding:.3em; 232 | margin-bottom: 3px; 233 | font-size: inherit; 234 | } 235 | .ui-jqdialog-content .data-line {padding-top:.1em;border: 0 none;} 236 | 237 | .ui-jqdialog-content .CaptionTD {vertical-align: middle;border: 0 none; padding: 2px;white-space: nowrap;} 238 | .ui-jqdialog-content .DataTD { 239 | padding: 2px; 240 | border: 0 none; 241 | vertical-align: middle; 242 | } 243 | .ui-jqdialog-content .form-view-data {white-space:pre} 244 | .fm-button { height: 18px; display: inline-block; margin:2px 4px 0 0; padding: .6em .5em .2em .5em; text-decoration:none !important; cursor:pointer; position: relative; text-align: center; zoom: 1; } 245 | .fm-button-icon-left { padding-left: 1.9em; } 246 | .fm-button-icon-right { padding-right: 1.9em; } 247 | .fm-button-icon-left .ui-icon { right: auto; left: .2em; margin-left: 0; position: absolute; top: 50%; margin-top: -8px; } 248 | .fm-button-icon-right .ui-icon { left: auto; right: .2em; margin-left: 0; position: absolute; top: 50%; margin-top: -8px;} 249 | #nData, #pData { float: left; margin:3px;padding: 0; width: 15px; } 250 | .ViewTable { 251 | border-width: 0; 252 | border-style: none; 253 | border-spacing: 1px; 254 | padding: 4px; 255 | table-layout: fixed; 256 | } 257 | .ViewTable .CaptionTD, .ViewTable .DataTD {padding : 4px;} 258 | /* End Eorm edit */ 259 | /*cell edit*/ 260 | .ui-jqgrid .edit-cell { 261 | padding: 4px 0px 4px 4px; 262 | } 263 | .ui-jqgrid .selected-row, div.ui-jqgrid .selected-row td {font-style : normal;border-left: 0 none;} 264 | /* inline edit actions button*/ 265 | .ui-inline-del.ui-state-hover span, .ui-inline-edit.ui-state-hover span, 266 | .ui-inline-save.ui-state-hover span, .ui-inline-cancel.ui-state-hover span { 267 | margin: -1px; 268 | } 269 | .ui-inline-del, .ui-inline-cancel { 270 | margin-left: 8px; 271 | } 272 | 273 | .ui-jqgrid .inline-edit-cell { 274 | padding: 4px 0px 4px 4px; 275 | } 276 | /* Tree Grid */ 277 | .ui-jqgrid .tree-wrap {float: left; position: relative;height: 18px;white-space: nowrap;overflow: hidden;} 278 | .ui-jqgrid .tree-minus {position: absolute; height: 18px; width: 18px; overflow: hidden;} 279 | .ui-jqgrid .tree-plus {position: absolute; height: 18px; width: 18px; overflow: hidden;} 280 | .ui-jqgrid .tree-leaf {position: absolute; height: 18px; width: 18px;overflow: hidden;} 281 | .ui-jqgrid .treeclick {cursor: pointer;} 282 | /* moda dialog */ 283 | * iframe.jqm {position:absolute;top:0;left:0;z-index:-1;} 284 | /* width: expression(this.parentNode.offsetWidth+'px');height: expression(this.parentNode.offsetHeight+'px');}*/ 285 | .ui-jqgrid-dnd tr td {border-right-width: 1px; border-right-color: inherit; border-right-style: solid; height:20px} 286 | /* RTL Support */ 287 | .ui-jqgrid .ui-jqgrid-caption-rtl {text-align: right;} 288 | .ui-jqgrid .ui-jqgrid-hbox-rtl {float: right;} 289 | .ui-jqgrid .ui-jqgrid-resize-ltr {float: right;margin: -2px -2px -2px 0;} 290 | .ui-jqgrid .ui-jqgrid-resize-rtl {float: left;margin: -2px 0 -1px -3px;} 291 | .ui-jqgrid .ui-sort-rtl {left:0;} 292 | .ui-jqgrid .tree-wrap-ltr {float: left;} 293 | .ui-jqgrid .tree-wrap-rtl {float: right;} 294 | .ui-jqgrid .ui-ellipsis {-moz-text-overflow:ellipsis;text-overflow:ellipsis;} 295 | 296 | /* Toolbar Search Menu , Nav menu*/ 297 | .ui-search-menu, 298 | .ui-nav-menu { 299 | position: absolute; 300 | padding: 2px 5px; 301 | z-index:99999; 302 | -webkit-box-shadow: 7px 7px 5px 0px rgba(50, 50, 50, 0.75); 303 | -moz-box-shadow: 7px 7px 5px 0px rgba(50, 50, 50, 0.75); 304 | box-shadow: 7px 7px 5px 0px rgba(50, 50, 50, 0.75); 305 | } 306 | .ui-search-menu.ui-menu .ui-menu-item, 307 | .ui-nav-menu.ui-menu .ui-menu-item 308 | { 309 | list-style-image: none; 310 | padding-right: 0; 311 | padding-left: 0; 312 | } 313 | .ui-search-menu.ui-menu .ui-menu-item a, 314 | .ui-nav-menu.ui-menu .ui-menu-item a 315 | { 316 | display: block; 317 | } 318 | .ui-search-menu.ui-menu .ui-menu-item a.g-menu-item:hover, 319 | .ui-nav-menu.ui-menu .ui-menu-item a.g-menu-item:hover 320 | { 321 | margin: -1px; 322 | font-weight: normal; 323 | } 324 | .ui-jqgrid .ui-search-table { padding: 0; border: 0 none; height:20px; width:100%;} 325 | .ui-jqgrid .ui-search-table .ui-search-oper { width:20px; } 326 | a.g-menu-item, a.soptclass, a.clearsearchclass { cursor: pointer; } 327 | .ui-jqgrid .ui-jqgrid-view input, 328 | .ui-jqgrid .ui-jqgrid-view select, 329 | .ui-jqgrid .ui-jqgrid-view textarea, 330 | .ui-jqgrid .ui-jqgrid-view button { 331 | font-size: inherit; 332 | } 333 | 334 | .ui-jqgrid .ui-scroll-popup {width: 95px;} 335 | .ui-search-table select, 336 | .ui-search-table input 337 | { 338 | padding: 4px 3px; 339 | } 340 | 341 | .ui-jqgrid .ui-pg-table .ui-pg-button.ui-state-disabled:hover > .ui-separator { 342 | margin-left: 3px; 343 | margin-right: 3px; 344 | } 345 | 346 | .ui-jqgrid .ui-pg-table .ui-pg-button.ui-state-disabled:hover > .ui-pg-div > .ui-icon { 347 | margin-left: 3px; 348 | margin-right: 3px; 349 | } 350 | /* Column menu */ 351 | .ui-jqgrid .ui-jqgrid-htable .colmenu { 352 | position:absolute; 353 | right:1px; 354 | height:100%; 355 | color : black; 356 | } 357 | .ui-jqgrid .ui-jqgrid-htable .colmenu-rtl { 358 | right: auto; 359 | left : 1px; 360 | position:absolute; 361 | height:100%; 362 | color : black; 363 | } 364 | .ui-jqgrid .ui-jqgrid-htable .colmenuspan { 365 | display:inline-block; 366 | } 367 | 368 | .ui-jqgrid .ui-jqgrid-htable .ui-th-div { 369 | height:17px; 370 | margin-top:5px; 371 | } 372 | .column-menu, .ui-search-menu { 373 | padding: 10px 10px; 374 | } 375 | .column-menu .divider { 376 | background-color: #e5e5e5; 377 | height: 1px; 378 | padding:0 0; 379 | margin: 5px 0; 380 | overflow: hidden; 381 | } 382 | .ui-menu-item .ui-common-table .menu_icon { 383 | white-space: pre; 384 | padding-right: 4px; 385 | padding-left: 4px; 386 | width : auto; 387 | } 388 | .ui-menu-item .ui-common-table .menu_icon .ui-icon { 389 | display : inline-block; 390 | position: relative; 391 | } 392 | td.menu_text { 393 | width: auto; 394 | white-space: nowrap; 395 | } 396 | .ui-search-menu .ui-menu-item { 397 | padding : 0 0; 398 | } 399 | .ui-col-menu .ui-menu-item td.menu_text{ 400 | padding-top: 0; 401 | padding-bottom: 0; 402 | padding-left : 1px; 403 | } 404 | .ui-col-menu .ui-menu-item td.menu_icon{ 405 | padding-top: 0; 406 | padding-bottom: 0; 407 | vertical-align: middle; 408 | } 409 | .ui-col-menu .ui-menu-item td.menu_icon input{ 410 | margin: 2px 0; 411 | 412 | } 413 | #search_menu .ui-menu-item div { 414 | margin: 3px 0; 415 | white-space: nowrap; 416 | } 417 | 418 | #search_menu .ui-menu-item div input, 419 | #search_menu .ui-menu-item div select 420 | { 421 | padding: 3px 2px; 422 | } 423 | #search_menu .search_buttons { 424 | display:inline-block; 425 | width:50%; 426 | } 427 | #column_menu.ui-menu .ui-menu-item { 428 | position :static; 429 | } 430 | /*menubar*/ 431 | .ui-jqgrid .ui-jqgrid-menubar { 432 | margin: 0px 5px; 433 | width:19px; 434 | height:18px; 435 | } 436 | .ui-jqgrid .ui-jqgrid-menubar:hover { 437 | border: 0 none; 438 | } 439 | .ui-jqgrid .menubar-rtl { 440 | float : right; 441 | } 442 | 443 | .ui-jqgrid .menubar-ltr { 444 | float : left; 445 | } 446 | /*printing*/ 447 | 448 | .jqgridprint { 449 | width : 100%; 450 | font-size: 13px; 451 | } 452 | 453 | .jqgridprint th, 454 | .jqgridprint td { 455 | padding: 4px 4px 4px 4px ; 456 | text-align: center ; 457 | } 458 | .jqgridprint th { 459 | border-bottom: 2px solid #333333 ; 460 | } 461 | .jqgridprint td { 462 | border-bottom: 1px dotted #999999 ; 463 | } 464 | 465 | .jqgridprint tfoot td { 466 | border-bottom-width: 0px ; 467 | border-top: 2px solid #333333 ; 468 | padding-top: 20px ; 469 | } 470 | 471 | .ui-jqgrid .jqgrid-caption-menu 472 | { 473 | left:0; 474 | top:30px; 475 | position:absolute; 476 | display:none; 477 | font-size: inherit; 478 | width : auto; 479 | } 480 | .ui-jqgrid .jqgrid-column-menu 481 | { 482 | font-size:inherit; 483 | width:auto; 484 | position: absolute; 485 | } 486 | -------------------------------------------------------------------------------- /jsdemo/view.js: -------------------------------------------------------------------------------- 1 | // GA1.js的结果集和显示所需的结果之间的转换 2 | /** 染色体(教室时间片)集合,对应值为教学班编号; 四维矩阵 A={ChromosomeNO, ClassRoom, Day, Time} -> ClassNO */ 3 | var chromosomeMatrix ; 4 | /** 教学班对应的时间片/排课结果 三维矩阵 B={ChromosomeNO, Class,Times} -> roomNO+dayNO+timeNO */ 5 | var classMatrixes; 6 | 7 | const lessonFormat = `[lessonIndex]id+name/lesson.teacher/lesson.zone/course.onceHour/lesson.studentNum/roomId`; 8 | function lessonToString(lessonIndex,geneOrder){ 9 | let lesson = lessons[lessonIndex]; 10 | let course = coursesMap[lessons[lessonIndex].course]; 11 | let roomId = classRooms[indexUtil.getRoom(geneOrder[lessonIndex])].id 12 | return "["+lessonIndex+"]"+lesson.id+course.name+"/"+lesson.teacher+"/"+lesson.zone 13 | +"/"+course.onceHour+"/"+lesson.studentNum +"/"+roomId; 14 | } 15 | function roomToString (roomId) { 16 | let room = classroomsMap[roomId]; 17 | return room?"教室:"+room.id+" 校区:"+buildingsMap[room.building].zone+" 容量:"+ room.capacity+" 类型:"+room.roomType:""; 18 | } --------------------------------------------------------------------------------