软件工程

作业

7-1 求迷宫最短通道

java1

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

class Node{
    public int x;
    public int y;
    public int step;
    public Node(int x,int y,int step){
        this.x=x;
        this.y=y;
        this.step=step;
    }
}

public class Main {
    private static int[][] stepArr = new int[][] { {-1,0},{1,0},{0,-1},{0,1} };

    public static int bfs(int[][] map,int[][] visit,int n) {
        Node node = new Node(1, 1, 0);
        Queue<Node> q = new LinkedList<>();
        q.add(node);
        while (q.size() != 0)
        {
            node = q.remove();
            if (node.x == n - 2 && node.y == n - 2)
            {
                return node.step;
            }
            visit[node.x][node.y] = 1;
            for (int i = 0; i < 4; i++)
            {
                int x = node.x + stepArr[i][0];
                int y = node.y + stepArr[i][1];
                if (x >= 0 && y >= 0 && x < n&&y < n&&visit[x][y] == 0 && map[x][y] == 0)
                {
                    visit[x][y] = 1;
                    Node next = new Node(x, y, node.step + 1);
                    q.add(next);
                }
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[][] map = new int[n][n];
        int[][] visit = new int[n][n];
        for (int i = 0;i<n;++i){
            for (int j = 0;j<n;++j){
                map[i][j]=scanner.nextInt();
                visit[i][j]=0;
            }
        }
        int r = bfs(map,visit,n);
        System.out.println(r==-1?"No solution":r);
    }
}

java2

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

class Node{
    public int x;
    public int y;
    public int step;
    public Node(int x,int y,int step){
        this.x=x;
        this.y=y;
        this.step=step;
    }
}

public class Main {
    private static int[][] stepArr = new int[][] { {-1,0},{1,0},{0,-1},{0,1} };

    private static int bfs(int[][] map, int n) {
        Node node = new Node(1, 1, 0);
        Queue<Node> q = new LinkedList<>();
        q.add(node);
        while (q.size() != 0)
        {
            node = q.remove();
            if (node.x == n - 2 && node.y == n - 2)
            {
                return node.step;
            }
            map[node.x][node.y] = 2;
            for (int i = 0; i < 4; i++)
            {
                int x = node.x + stepArr[i][0];
                int y = node.y + stepArr[i][1];
                if (x >= 0 && y >= 0 && x < n&&y < n&& map[x][y] == 0)
                {
                    map[x][y] = 2;
                    Node next = new Node(x, y, node.step + 1);
                    q.add(next);
                }
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int[][] map = new int[n][n];
        for (int i = 0;i<n;++i){
            for (int j = 0;j<n;++j){
                map[i][j]=scanner.nextInt();
            }
        }
        int r = bfs(map,n);
        System.out.println(r==-1?"No solution":r);
    }
}

java3

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;

class Node implements Comparable
{
    public Node parent;
    public int x, y;
    public double g;
    public double h;
    Node(Node parent, int xpos, int ypos, double g, double h) {
        this.parent = parent;
        this.x = xpos;
        this.y = ypos;
        this.g = g;
        this.h = h;
    }
    @Override
    public int compareTo(Object o) {
        Node that = (Node) o;
        return (int)((this.g + this.h) - (that.g + that.h));
    }
}

class Astar {
    private final List<Node> open;
    private final List<Node> closed;
    private final List<Node> path;
    private final int[][] maze;
    private Node now;
    private final int xstart;
    private final int ystart;
    private int xend, yend;
    private final boolean diag;

    Astar(int[][] maze, int xstart, int ystart, boolean diag) {
        this.open = new ArrayList<>();
        this.closed = new ArrayList<>();
        this.path = new ArrayList<>();
        this.maze = maze;
        this.now = new Node(null, xstart, ystart, 0, 0);
        this.xstart = xstart;
        this.ystart = ystart;
        this.diag = diag;
    }

    public List<Node> findPathTo(int xend, int yend) {
        this.xend = xend;
        this.yend = yend;
        this.closed.add(this.now);
        addNeigborsToOpenList();
        while (this.now.x != this.xend || this.now.y != this.yend) {
            if (this.open.isEmpty()) return null;
            this.now = this.open.get(0);
            this.open.remove(0);
            this.closed.add(this.now);
            addNeigborsToOpenList();
        }
        this.path.add(0, this.now);
        while (this.now.x != this.xstart || this.now.y != this.ystart) {
            this.now = this.now.parent;
            this.path.add(0, this.now);
        }
        return this.path;
    }

    private static boolean findNeighborInList(List<Node> array, Node node) {
        return array.stream().anyMatch((n) -> (n.x == node.x && n.y == node.y));
    }

    private double distance(int dx, int dy) {
        if (this.diag) {
            return Math.hypot(this.now.x + dx - this.xend, this.now.y + dy - this.yend);
        } else {
            return Math.abs(this.now.x + dx - this.xend) + Math.abs(this.now.y + dy - this.yend);
        }
    }
    private void addNeigborsToOpenList() {
        Node node;
        for (int x = -1; x <= 1; x++) {
            for (int y = -1; y <= 1; y++) {
                if (!this.diag && x != 0 && y != 0) {
                    continue;
                }
                node = new Node(this.now, this.now.x + x, this.now.y + y, this.now.g, this.distance(x, y));
                if ((x == 1 && y == 0 || x == 0 && y == 1 || x == -1 && y == 0 || x == 0 && y == -1)
                        && this.now.x + x >= 0 && this.now.x + x < this.maze[0].length
                        && this.now.y + y >= 0 && this.now.y + y < this.maze.length
                        && this.maze[this.now.y + y][this.now.x + x] != -1
                        && !findNeighborInList(this.open, node) && !findNeighborInList(this.closed, node)) {
                    node.g = node.parent.g + 1.;
                    node.g += maze[this.now.y + y][this.now.x + x];
                    this.open.add(node);
                }
            }
        }
        Collections.sort(this.open);
    }
}

public class Main
{
    public static void main(String[] args)
    {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int[][] maze = new int[N][N];
        for (int x = 0; x < N; ++x) {
            for (int y = 0; y < N; ++y) {
                maze[x][y] = scanner.nextInt() * 100;
            }
        }
        Astar as = new Astar(maze, 1, 1, true);
        List<Node> path = as.findPathTo(N-2, N-2);
        if (path == null || path.get(path.size() - 1).g >= 100){
            System.out.println("No solution");
        } else {
            System.out.printf("%.0f\n", path.get(path.size() - 1).g);
        }
    }
}

数独

Launcher/main.cpp

// ReSharper disable CommentTypo
#include <cstdio>
#include <vector>
#include <string>

using Row = array<int>;
using Map = array<Row^>;
using Maps = array<Map^>;

// ReSharper disable once CppInconsistentNaming
using Row_ = std::vector<int>;
// ReSharper disable once CppInconsistentNaming
using Map_ = std::vector<Row_>;
// ReSharper disable once CppInconsistentNaming
using Maps_ = std::vector<Map_>;

// ReSharper disable once CppDeclaratorNeverUsed
static Maps^ ToMaps(const Maps_& maps)
{
	auto res = gcnew Maps(static_cast<int>(maps.size()));
	for (auto i = 0; i < maps.size(); ++i)
	{
		res[i] = gcnew Map(static_cast<int>(maps[i].size()));
		for (auto j = 0; j < maps[i].size(); ++j)
		{
			res[i][j] = gcnew Row(static_cast<int>(maps[i][j].size()));
			for (auto k = 0; k < maps[i][j].size(); ++k)
			{
				res[i][j][k] = maps[i][j][k];
			}
		}
	}
	return res;
}

int main(const int argc, char* argv[])
{
//#define Debug
#ifndef Debug
	try
	{
		if (argc != 9)
		{
			throw gcnew System::Exception("parameter error");
		}

		auto parameter = gcnew System::Collections::Generic::Dictionary<System::String^, System::String^>();

		for (auto i = 1; i < argc; i += 2)
		{
			parameter->Add(gcnew System::String(argv[i]), gcnew System::String(argv[i + 1]));
		}

		Sudoku sudoku(System::Int32::Parse(parameter["-m"]), System::Int32::Parse(parameter["-n"]));
		sudoku.InitializeWithFilePath(parameter["-i"]);
		System::IO::File::WriteAllText(parameter["-o"], Sudoku::ToString(sudoku.Solve()));
	}
	catch (System::Exception^ exception)
	{
		fprintf(stderr, "Usage:\n  %s -m 宫格阶级 -n 待解答盘面数目 -i 指定输入文件 -o 指定程序的输出文件\n", argv[0]);
		System::Console::Error->WriteLine(exception->ToString());
		return EXIT_FAILURE;
	}
#else
	Sudoku sudoku(9, 1);
	//sudoku.InitializeWithArray(ToMaps(
	//{
	//	{
	//		{0,0,0, 0,0,0, 0,0,0},
	//		{0,0,0, 0,0,3, 0,8,5},
	//		{0,0,1, 0,2,0, 0,0,0},
	//			    	   
	//		{0,0,0, 5,0,7, 0,0,0},
	//		{0,0,4, 0,0,0, 1,0,0},
	//		{0,9,0, 0,0,0, 0,0,0},
	//			    	   
	//		{5,0,0, 0,0,0, 0,7,3},
	//		{0,0,2, 0,1,0, 0,0,0},
	//		{0,0,0, 0,4,0, 0,0,9}
	//	}
	//}));
	//sudoku.InitializeWithArray(ToMaps(
	//	{
	//		{
	//			{1,2,3},
	//			{2,3,0},
	//			{3,1,2}
	//		}
	//	}));
	//sudoku.InitializeWithArray(ToMaps(
	//	{
	//		{
	//			{0,0,6,5,0,0},
	//			{5,0,4,0,0,1},
	//			{0,3,0,1,2,0},
	//			{0,0,0,0,0,6},
	//			{1,0,0,0,0,0},
	//			{0,0,0,0,0,0}
	//		}
	//	}));
	Maps_ mp
	{
		{
			{ 2,0,0 },
			{ 0,0,1 },
			{ 0,0,0 }
		},
		{
			{ 0,0,1 },
			{ 0,0,0 },
			{ 2,0,0 }
		},
		{
			{ 0,0,0 },
			{ 0,0,0 },
			{ 3,3,0 }
		}
	};
	sudoku.InitializeWithArray(ToMaps(mp));
	System::Console::WriteLine(Sudoku::ToString(sudoku.Solve()));
	system("pause");
	//sudoku.InitializeWithArray(ToMaps(
	//{
	//	{
	//		{0,0,5,3,0,0,0,0,0},
	//		{8,0,0,0,0,0,0,2,0},
	//		{0,7,0,0,1,0,5,0,0},
	//
	//		{4,0,0,0,0,5,3,0,0},
	//		{0,1,0,0,7,0,0,0,6},
	//		{0,0,3,2,0,0,0,8,0},
	//
	//		{0,6,0,5,0,0,0,0,9},
	//		{0,0,4,0,0,0,0,3,0},
	//		{0,0,0,0,0,9,7,0,0}
	//	}
	//}));
	//sudoku.InitializeWithArray(ToMaps(
	//{
	//	{
	//		{0,0,0, 0,0,0, 0,0,0},
	//		{0,0,0, 0,0,3, 0,8,5},
	//		{0,0,1, 0,2,0, 0,0,0},
	//			    	   
	//		{0,0,0, 5,0,7, 0,0,0},
	//		{0,0,4, 0,0,0, 1,0,0},
	//		{0,9,0, 0,0,0, 0,0,0},
	//			    	   
	//		{5,0,0, 0,0,0, 0,7,3},
	//		{0,0,2, 0,1,0, 0,0,0},
	//		{0,0,0, 0,4,0, 0,0,9}
	//	}
	//}));
	//sudoku.InitializeWithFilePath("z:\\input.txt");
	//auto res = sudoku.Solve();
	//System::Console::WriteLine(Sudoku::ToString(res));
	//for (int i = 0; i < res->Length; ++i)
	//{
	//	if (res[i] == nullptr)
	//	{
	//		printf("\nNo solve\n");
	//		continue;
	//	}
	//	for (int row = 0; row < res[i]->Length; ++row)
	//	{
	//		for (int col = 0; col < res[i][row]->Length; ++col)
	//		{
	//			printf("%d", res[i][row][col]);
	//			if ((col + 1) % 3 == 0)
	//			{
	//				printf("|");
	//			}
	//		}
	//		printf("\n");
	//		if ((row + 1) % 3 == 0)
	//		{
	//			printf("---+---+---\n");
	//		}
	//	}
	//	printf("\n");
	//}
	//system("pause");
#endif
}

Sudoku/Sudoku.h

#pragma once
#include "pch.h"

public ref class Sudoku sealed
{
public:
	using Map = array<array<int>^>;
	using Maps = array<Map^>;
	
	Sudoku(int m, int n);

	void InitializeWithFilePath(System::String^ path);
	void InitializeWithArray(Maps^ maps);
	Maps^ Solve();

	static System::String^ ToString(Maps^ maps);

private:
	using CspTerm = Microsoft::SolverFoundation::Solvers::CspTerm;

	Maps^ maps;
	int m;
	int n;
	
	static array<CspTerm^>^ GetSlice(array<array<CspTerm^>^>^ lp, int m, int ra, int rb, int ca, int cb);
};


Sudoku/Sudoku.cpp

#include "pch.h"
#include "Sudoku.h"

static int M = 0;

ref class Func sealed
{
public:
	static array<array<int>^>^ ChunkBySize(array<int>^ arr)
	{
		return Microsoft::FSharp::Collections::ArrayModule::ChunkBySize(M, arr);
	}
};

Sudoku::Sudoku(const int m, const int n) :m(m), n(n)
{
	if (m < 3 || m > 9)
	{
		throw gcnew System::Exception("M must be 3 to 9");
	}
	if (n < 0)
	{
		throw gcnew System::Exception("N must be greater than -1");
	}
}

void Sudoku::InitializeWithFilePath(System::String^ path)
{
	using namespace Microsoft::FSharp::Core;
	using ArrayModule = Microsoft::FSharp::Collections::ArrayModule;
	const auto mapSp = gcnew array<System::String^>(1) { " " };

	M = m;
	maps =
		ArrayModule::Take(
			n,
			ArrayModule::Map(
				FSharpFuncUtil::FSharpFuncUtil::ToFSharpFunc(gcnew System::Func<array<int>^, array<array<int>^>^>(Func::ChunkBySize)),
				ArrayModule::ChunkBySize(
					m * m,
					ArrayModule::Map(
						FSharpFuncUtil::FSharpFuncUtil::ToFSharpFunc(gcnew System::Func<System::String^, int>(System::Int32::Parse)),
						System::IO::File::ReadAllText(path)
							->Replace("\n", " ")
							->Replace("\r", " ")
							->Split(mapSp, System::StringSplitOptions::RemoveEmptyEntries)))));
if (maps->Length != n)
{
	throw gcnew System::Exception("N size no matches");
}
for (auto i = 0; i < maps->Length; ++i)
{
	if (maps[i]->Length != m)
	{
		throw gcnew System::Exception("M size no matches");
	}
	for (auto j = 0; j < m; ++j)
	{
		if (maps[i][j]->Length != m)
		{
			throw gcnew System::Exception("M size no matches");
		}
	}
}
}

void Sudoku::InitializeWithArray(Maps^ maps)
{
	this->maps = maps;
}

Sudoku::Maps^ Sudoku::Solve()
{
	using namespace Microsoft::SolverFoundation::Solvers;

	// 创建一个三维数组存放结果
	auto res = gcnew Maps(maps->Length);
	for (auto i = 0; i < maps->Length; ++i)
	{
		res[i] = gcnew Map(maps[i]->Length);
		for (auto j = 0; j < maps[i]->Length; ++j)
		{
			res[i][j] = gcnew array<int>(maps[i][j]->Length);
		}
	}

	// 并行遍历每个盘面,同时调度两个线程
	// #pragma omp parallel for schedule(dynamic, 2)
	for (auto i = 0; i < maps->Length; ++i)
	{
		// 初始化
		auto map = maps[i];
		auto s = ConstraintSystem::CreateSolver();
		const auto z = s->CreateIntegerInterval(1, maps[i]->Length);
		auto lp = s->CreateVariableArray(z, "n", maps[i]->Length, maps[i]->Length);

		// 为每行和已知条件添加约束
		for (auto row = 0; row < maps[i]->Length; ++row)
		{
			for (auto col = 0; col < maps[i]->Length; ++col)
			{
				if (map[row][col] > 0)
				{
					s->AddConstraints(s->Equal(map[row][col], lp[row][col]));
				}
			}
			s->AddConstraints(s->Unequal(GetSlice(lp, maps[i]->Length, row, row, 0, maps[i]->Length - 1)));
		}
		
		// 为每列添加约束
		for (auto col = 0; col < maps[i]->Length; ++col)
		{
			s->AddConstraints(s->Unequal(GetSlice(lp, maps[i]->Length, 0, maps[i]->Length - 1, col, col)));
		}

		// 为不同盘面阶数设置宫格大小
		auto stepRow = 0;
		auto stepCol = 0;
		switch (maps[i]->Length)
		{
		case 4:
			stepRow = 2;
			stepCol = 2;
			break;
		case 6:
			stepRow = 2;
			stepCol = 3;
			break;
		case 8:
			stepRow = 4;
			stepCol = 2;
			break;
		case 9:
			stepRow = 3;
			stepCol = 3;
			break;
		default:;
		}

		// 如果当前盘面阶数存在宫格,则为每个宫格添加约束
		if (stepRow != 0 && stepCol != 0)
		{
			for (auto row = 0; row < maps[i]->Length; row += stepRow)
			{
				for (auto col = 0; col < maps[i]->Length; col += stepCol)
				{
					s->AddConstraints(
						s->Unequal(
							GetSlice(lp, stepCol * stepRow, row, row + stepRow - 1, col, col + stepCol - 1)));
				}
			}
		}

		// 求解
		auto sol = s->Solve();
		try
		{
			for (auto row = 0; row < maps[i]->Length; ++row)
			{
				for (auto col = 0; col < maps[i]->Length; ++col)
				{
					res[i][row][col] = sol->GetIntegerValue(lp[row][col]);
				}
			}
		}
		catch (System::Exception^) // 无解情况返回nullptr
		{
			res[i] = nullptr;
		}
	}
	return res;
}

System::String^ Sudoku::ToString(Maps^ maps)
{
	auto sb = gcnew System::Text::StringBuilder();
	for (auto i = 0; i < maps->Length; ++i)
	{
		if (maps[i] == nullptr)
		{
			sb->Append("Unsolvable!\n\n");
			continue;
		}
		for (auto j = 0; j < maps[i]->Length; ++j)
		{
			for (auto k = 0; k < maps[i][j]->Length; ++k)
			{
				sb->Append(maps[i][j][k].ToString());
				if (k < maps[i][j]->Length - 1)
				{
					sb->Append(" ");
				}
			}
			sb->Append("\n");
		}
		if (i < maps->Length - 1)
		{
			sb->Append("\n");
		}
	}
	return sb->ToString();
}

array<Sudoku::CspTerm^>^ Sudoku::GetSlice(
	array<array<CspTerm^>^>^ lp,
	const int m,
	const int ra,
	const int rb,
	const int ca,
	const int cb)
{
	auto slice = gcnew array<CspTerm^>(m);
	auto i = 0;
	for (auto row = ra; row <= rb; ++row)
	{
		for (auto col = ca; col <= cb; ++col)
		{
			slice[i++] = lp[row][col];
		}
	}
	return slice;
}

Sudoku/pch.h

// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。

#ifndef PCH_H
// ReSharper disable once CppInconsistentNaming
#define PCH_H

// 添加要在此处预编译的标头
#include <vector>

#endif //PCH_H

Sudoku/pch.cpp

// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

FSharpFuncUtil/Library1.fs

namespace FSharpFuncUtil

open System.Runtime.CompilerServices

[<Extension>]
type public FSharpFuncUtil = 

    [<Extension>] 
    static member ToFSharpFunc<'a,'b> (func:System.Converter<'a,'b>) = fun x -> func.Invoke(x)

    [<Extension>] 
    static member ToFSharpFunc<'a,'b> (func:System.Func<'a,'b>) = fun x -> func.Invoke(x)

    [<Extension>] 
    static member ToFSharpFunc<'a,'b,'c> (func:System.Func<'a,'b,'c>) = fun x y -> func.Invoke(x,y)

    [<Extension>] 
    static member ToFSharpFunc<'a,'b,'c,'d> (func:System.Func<'a,'b,'c,'d>) = fun x y z -> func.Invoke(x,y,z)

    static member Create<'a,'b> (func:System.Func<'a,'b>) = FSharpFuncUtil.ToFSharpFunc func

    static member Create<'a,'b,'c> (func:System.Func<'a,'b,'c>) = FSharpFuncUtil.ToFSharpFunc func

    static member Create<'a,'b,'c,'d> (func:System.Func<'a,'b,'c,'d>) = FSharpFuncUtil.ToFSharpFunc func

UnitTest/Test.cpp

using namespace Microsoft::VisualStudio::TestTools::UnitTesting;

#include <vector>

[TestClass]
public ref class SudokuTests sealed
{
	using Row = array<int>;
	using Map = array<Row^>;
	using Maps = array<Map^>;

	// ReSharper disable once CppInconsistentNaming
	using Row_ = std::vector<int>;
	// ReSharper disable once CppInconsistentNaming
	using Map_ = std::vector<Row_>;
	// ReSharper disable once CppInconsistentNaming
	using Maps_ = std::vector<Map_>;
	
	static Maps^ ToMaps(const Maps_& maps)
	{
#pragma warning(disable:4267)
		auto res = gcnew Maps(static_cast<int>(maps.size()));
		for (size_t i = 0; i < maps.size(); ++i)
		{
			res[i] = gcnew Map(static_cast<int>(maps[i].size()));
			for (size_t j = 0; j < maps[i].size(); ++j)
			{
				res[i][j] = gcnew Row(static_cast<int>(maps[i][j].size()));
				for (size_t k = 0; k < maps[i][j].size(); ++k)
				{
					res[i][j][k] = maps[i][j][k];
				}
			}
		}
		return res;
	}
public:
	[TestMethod]
	void Sudoku3X3()
	{
		const Maps_ mp
		{
			{
				{ 2,0,0 },
				{ 0,0,1 },
				{ 0,0,0 }
			},
			{
				{ 0,0,1 },
				{ 0,0,0 },
				{ 2,0,0 }
			},
			{
				{ 1,1,0 },
				{ 0,0,0 },
				{ 0,0,0 }
			}
		};

		auto s = gcnew Sudoku(3, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(2 1 3
3 2 1
1 3 2

3 2 1
1 3 2
2 1 3

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}

	[TestMethod]
	void Sudoku4X4()
	{
		const Maps_ mp
		{
			{
				{ 0,0, 2,0 },
				{ 0,0, 0,4 },
				
				{ 3,0, 0,1 },
				{ 0,1, 0,0 }
			},		   
			{		   
				{ 2,0, 0,0 },
				{ 0,1, 0,0 },
				
				{ 0,2, 3,0 },
				{ 0,0, 0,4 }
			},		   
			{		   
				{ 1,1, 0,0 },
				{ 0,0, 0,0 },
				
				{ 0,0, 0,0 },
				{ 0,0, 0,0 }
			}
		};

		auto s = gcnew Sudoku(4, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(1 4 2 3
2 3 1 4
3 2 4 1
4 1 3 2

2 4 1 3
3 1 4 2
4 2 3 1
1 3 2 4

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}

	[TestMethod]
	void Sudoku5X5()
	{
		const Maps_ mp
		{
			{
				{ 0,1,0,0,0 },
				{ 0,0,0,3,0 },
				{ 0,0,4,1,2 },
				{ 1,0,0,5,0 },
				{ 3,0,0,0,4 }
			},
			{
				{ 3,0,2,0,0 },
				{ 4,2,0,0,0 },
				{ 0,0,1,0,5 },
				{ 0,0,3,0,1 },
				{ 0,0,0,0,0 }
			},
			{
				{ 1,1,0,0,0 },
				{ 0,0,0,0,0 },
				{ 0,0,0,0,0 },
				{ 0,0,0,0,0 },
				{ 0,0,0,0,0 }
			}
		};

		auto s = gcnew Sudoku(5, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(2 1 3 4 5
4 2 5 3 1
5 3 4 1 2
1 4 2 5 3
3 5 1 2 4

3 1 2 5 4
4 2 5 1 3
2 3 1 4 5
5 4 3 2 1
1 5 4 3 2

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}

	[TestMethod]
	void Sudoku6X6()
	{
		const Maps_ mp
		{
			{
				{ 5,0,0, 0,4,0 },
				{ 0,0,0, 0,0,3 },
				
				{ 0,6,4, 2,0,0 },
				{ 2,0,0, 0,6,1 },
				
				{ 6,0,0, 3,0,0 },
				{ 0,0,2, 6,0,4 }
			},			 
			{			 
				{ 0,0,0, 2,0,0 },
				{ 2,0,0, 0,3,0 },
				
				{ 0,5,0, 3,0,2 },
				{ 3,2,6, 0,1,0 },
				
				{ 0,0,1, 0,0,3 },
				{ 0,0,0, 5,4,0 }
			},			 
			{			 
				{ 1,1,0, 0,0,0 },
				{ 0,0,0, 0,0,0 },
				
				{ 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0 },
				
				{ 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0 }
			}
		};

		auto s = gcnew Sudoku(6, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(5 2 3 1 4 6
4 1 6 5 2 3
1 6 4 2 3 5
2 3 5 4 6 1
6 4 1 3 5 2
3 5 2 6 1 4

4 1 3 2 5 6
2 6 5 1 3 4
1 5 4 3 6 2
3 2 6 4 1 5
5 4 1 6 2 3
6 3 2 5 4 1

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}

	[TestMethod]
	void Sudoku7X7()
	{
		const Maps_ mp
		{
				{
					{ 1,0,0,0,0,0,0 },
					{ 0,0,2,0,0,0,0 },
					{ 0,0,0,0,0,5,0 },
					{ 0,3,0,4,0,0,0 },
					{ 0,0,0,0,0,0,0 },
					{ 0,0,0,0,0,6,0 },
					{ 0,0,0,0,0,0,7 }
				},
				{
					{ 1,0,0,0,6,0,5 },
					{ 6,2,0,3,0,1,0 },
					{ 0,4,0,0,0,0,0 },
					{ 0,6,5,0,0,2,0 },
					{ 5,0,1,0,4,0,7 },
					{ 7,0,0,0,0,0,2 },
					{ 0,0,6,0,0,0,0 }
				},
				{
					{ 1,1,0,0,0,0,0 },
					{ 0,0,0,0,0,0,0 },
					{ 0,0,0,0,0,0,0 },
					{ 0,0,0,0,0,0,0 },
					{ 0,0,0,0,0,0,0 },
					{ 0,0,0,0,0,0,0 },
					{ 0,0,0,0,0,0,0 }
				}
		};

		auto s = gcnew Sudoku(7, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(1 5 3 6 2 7 4
5 6 2 7 1 4 3
2 1 4 3 7 5 6
7 3 6 4 5 1 2
4 2 7 1 6 3 5
3 7 5 2 4 6 1
6 4 1 5 3 2 7

1 7 2 4 6 3 5
6 2 7 3 5 1 4
2 4 3 5 1 7 6
4 6 5 1 7 2 3
5 3 1 2 4 6 7
7 1 4 6 3 5 2
3 5 6 7 2 4 1

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}

	[TestMethod]
	void Sudoku8X8()
	{
		const Maps_ mp
		{
			{
				{ 8,0, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 6,0 },
				{ 0,0, 0,3, 0,0, 0,0 },
				{ 0,0, 5,0, 0,0, 0,0 },

				{ 0,0, 0,0, 0,4, 0,0 },
				{ 0,0, 0,0, 2,0, 0,0 },
				{ 0,7, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,1 }
			},
			{
				{ 0,5, 0,0, 0,7, 4,0 },
				{ 0,0, 4,3, 2,0, 0,0 },
				{ 0,0, 1,0, 0,0, 0,0 },
				{ 7,0, 0,0, 4,0, 0,0 },

				{ 0,0, 0,0, 0,5, 0,2 },
				{ 0,0, 0,0, 6,0, 0,0 },
				{ 4,0, 3,0, 1,0, 0,0 },
				{ 6,0, 0,8, 0,0, 3,0 }
			},
			{
				{ 1,1, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,0 },
				
				{ 0,0, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,0 },
				{ 0,0, 0,0, 0,0, 0,0 }
			}
		};

		auto s = gcnew Sudoku(8, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(8 4 6 1 3 2 7 5
1 5 4 2 7 8 6 3
6 2 7 3 1 5 8 4
7 3 5 8 4 6 1 2
5 1 3 6 8 4 2 7
4 8 1 5 2 7 3 6
3 7 2 4 6 1 5 8
2 6 8 7 5 3 4 1

2 5 8 6 3 7 4 1
1 6 4 3 2 8 5 7
8 4 1 7 5 6 2 3
7 3 5 2 4 1 6 8
3 7 6 4 8 5 1 2
5 2 7 1 6 3 8 4
4 8 3 5 1 2 7 6
6 1 2 8 7 4 3 5

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}
	
	[TestMethod]
	void Sudoku9X9()
	{
		const Maps_ mp
		{
			{
				{ 0,0,5, 3,0,0, 0,0,0 },
				{ 8,0,0, 0,0,0, 0,2,0 },
				{ 0,7,0, 0,1,0, 5,0,0 },

				{ 4,0,0, 0,0,5, 3,0,0 },
				{ 0,1,0, 0,7,0, 0,0,6 },
				{ 0,0,3, 2,0,0, 0,8,0 },

				{ 0,6,0, 5,0,0, 0,0,9 },
				{ 0,0,4, 0,0,0, 0,3,0 },
				{ 0,0,0, 0,0,9, 7,0,0 }
			},
			{
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,3, 0,8,5 },
				{ 0,0,1, 0,2,0, 0,0,0 },

				{ 0,0,0, 5,0,7, 0,0,0 },
				{ 0,0,4, 0,0,0, 1,0,0 },
				{ 0,9,0, 0,0,0, 0,0,0 },

				{ 5,0,0, 0,0,0, 0,7,3 },
				{ 0,0,2, 0,1,0, 0,0,0 },
				{ 0,0,0, 0,4,0, 0,0,9 }
			},
			{
				{ 1,1,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 }
			}
		};

		auto s = gcnew Sudoku(9, 3);
		s->InitializeWithArray(ToMaps(mp));
		Assert::AreEqual(R"(1 4 5 3 2 7 6 9 8
8 3 9 6 5 4 1 2 7
6 7 2 9 1 8 5 4 3
4 9 6 1 8 5 3 7 2
2 1 8 4 7 3 9 5 6
7 5 3 2 9 6 4 8 1
3 6 7 5 4 2 8 1 9
9 8 4 7 6 1 2 3 5
5 2 1 8 3 9 7 6 4

9 8 7 6 5 4 3 2 1
2 4 6 1 7 3 9 8 5
3 5 1 9 2 8 7 4 6
1 2 8 5 3 7 6 9 4
6 3 4 8 9 2 1 5 7
7 9 5 4 6 1 8 3 2
5 1 9 2 8 6 4 7 3
4 7 2 3 1 9 5 6 8
8 6 3 7 4 5 2 1 9

Unsolvable!

)", Sudoku::ToString(s->Solve()));
	}

	[TestMethod]
	void FileInput()
	{
		const Maps_ mp
		{
			{
				{ 0,0,5, 3,0,0, 0,0,0 },
				{ 8,0,0, 0,0,0, 0,2,0 },
				{ 0,7,0, 0,1,0, 5,0,0 },

				{ 4,0,0, 0,0,5, 3,0,0 },
				{ 0,1,0, 0,7,0, 0,0,6 },
				{ 0,0,3, 2,0,0, 0,8,0 },

				{ 0,6,0, 5,0,0, 0,0,9 },
				{ 0,0,4, 0,0,0, 0,3,0 },
				{ 0,0,0, 0,0,9, 7,0,0 }
			},
			{
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,3, 0,8,5 },
				{ 0,0,1, 0,2,0, 0,0,0 },

				{ 0,0,0, 5,0,7, 0,0,0 },
				{ 0,0,4, 0,0,0, 1,0,0 },
				{ 0,9,0, 0,0,0, 0,0,0 },

				{ 5,0,0, 0,0,0, 0,7,3 },
				{ 0,0,2, 0,1,0, 0,0,0 },
				{ 0,0,0, 0,4,0, 0,0,9 }
			},
			{
				{ 1,1,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },

				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },

				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 },
				{ 0,0,0, 0,0,0, 0,0,0 }
			}
		};
		const auto filename = gcnew System::String("FileInput.txt");
		System::IO::File::WriteAllText(filename, Sudoku::ToString(ToMaps(mp)));
		auto sudokuArray = gcnew Sudoku(9, 3);
		sudokuArray->InitializeWithArray(ToMaps(mp));
		auto sudokuFile = gcnew Sudoku(9, 3);
		sudokuFile->InitializeWithFilePath(filename);
		Assert::AreEqual(Sudoku::ToString(sudokuArray->Solve()), Sudoku::ToString(sudokuFile->Solve()));
		System::IO::File::Delete(filename);
	}

	[TestMethod]
	void MOutOfRange()
	{
		
		try
		{
			gcnew Sudoku(10, 1);
		}
		catch (System::Exception^ exception)
		{
			Assert::IsTrue(exception->ToString()->Contains("M must be 3 to 9"));
		}
		try
		{
			gcnew Sudoku(2, 1);
		}
		catch (System::Exception^ exception)
		{
			Assert::IsTrue(exception->ToString()->Contains("M must be 3 to 9"));
		}
	}

	[TestMethod]
	void NOutOfRange()
	{
		try
		{
			gcnew Sudoku(9, -1);
		}
		catch (System::Exception^ exception)
		{
			Assert::IsTrue(exception->ToString()->Contains("N must be greater than -1"));
		}
	}
	
	[TestMethod]
	void NSizeNoMatch()
	{
		const Maps_ mp
		{
			{
				{ 0,0,5, 3,0,0, 0,0,0 },
				{ 8,0,0, 0,0,0, 0,2,0 },
				{ 0,7,0, 0,1,0, 5,0,0 },

				{ 4,0,0, 0,0,5, 3,0,0 },
				{ 0,1,0, 0,7,0, 0,0,6 },
				{ 0,0,3, 2,0,0, 0,8,0 },

				{ 0,6,0, 5,0,0, 0,0,9 },
				{ 0,0,4, 0,0,0, 0,3,0 },
				{ 0,0,0, 0,0,9, 7,0,0 }
			}
		};
		const auto filename = gcnew System::String("FileInput.txt");
		System::IO::File::WriteAllText(filename, Sudoku::ToString(ToMaps(mp)));
		auto sudokuFile = gcnew Sudoku(9, 2);
		try
		{
			sudokuFile->InitializeWithFilePath(filename);
		}
		catch (System::Exception^ exception)
		{
			Assert::IsTrue(exception->ToString()->Contains("System.InvalidOperationException"));
		}
		
		System::IO::File::Delete(filename);
	}

	[TestMethod]
	void MSizeNoMatch()
	{
		const Maps_ mp
		{
			{
				{ 0,0,5, 3,0,0, 0,0,0 },
				{ 8,0,0, 0,0,0, 0,2,0 },
				{ 0,7,0, 0,1,0, 5,0,0 },

				{ 4,0,0, 0,0,5, 3,0,0 },
				{ 0,1,0, 0,7,0, 0,0,6 },
				{ 0,0,3, 2,0,0, 0,8,0 },

				{ 0,6,0, 5,0,0, 0,0,9 },
				{ 0,0,4, 0,0,0, 0,3,0 },
				{ 0,0,0, 0,0,9, 7,0,0 }
			}
		};
		const auto filename = gcnew System::String("FileInput.txt");
		System::IO::File::WriteAllText(filename, Sudoku::ToString(ToMaps(mp))->Substring(2));
		auto sudokuFile = gcnew Sudoku(9, 1);
		try
		{
			sudokuFile->InitializeWithFilePath(filename);
		}
		catch (System::Exception^ exception)
		{
			Assert::IsTrue(exception->ToString()->Contains("M size no matches"));
		}

		System::IO::File::Delete(filename);
	}
};

input.txt

0 0 5 3 0 0 0 0 0
8 0 0 0 0 0 0 2 0
0 7 0 0 1 0 5 0 0
4 0 0 0 0 5 3 0 0
0 1 0 0 7 0 0 0 6
0 0 3 2 0 0 0 8 0
0 6 0 5 0 0 0 0 9
0 0 4 0 0 0 0 3 0
0 0 0 0 0 9 7 0 0

0 0 0 0 0 0 0 0 0
0 0 0 0 0 3 0 8 5
0 0 1 0 2 0 0 0 0
0 0 0 5 0 7 0 0 0
0 0 4 0 0 0 1 0 0
0 9 0 0 0 0 0 0 0
5 0 0 0 0 0 0 7 3
0 0 2 0 1 0 0 0 0
0 0 0 0 4 0 0 0 9

1 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0

5 3 0 0 7 0 0 0 0
6 0 0 1 9 5 0 0 0
0 9 8 0 0 0 0 6 0
8 0 0 0 6 0 0 0 3
4 0 0 8 0 3 0 0 1
7 0 0 0 2 0 0 0 6
0 6 0 0 0 0 2 8 0
0 0 0 4 1 9 0 0 5
0 0 0 0 8 0 0 7 9

0 0 0 0 4 0 0 0 0
1 2 0 0 0 0 0 7 3
0 3 0 0 0 8 0 0 0
0 0 4 0 0 0 6 0 0
0 0 0 2 0 3 0 0 0
0 0 5 0 0 0 0 0 0
0 0 6 0 9 0 5 0 0
0 7 0 0 0 0 0 2 0
0 0 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0
1 2 0 0 0 0 0 8 4
0 3 0 0 0 0 0 7 0
0 0 4 0 0 0 6 0 0
0 0 0 2 0 3 0 0 0
0 0 5 0 0 0 9 0 0
0 0 6 0 9 0 5 0 0
0 7 0 0 0 0 0 2 0
0 0 0 0 5 0 0 0 0

0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 2 3
0 0 4 0 0 5 0 0 0
0 0 0 1 0 0 0 0 0
0 0 0 0 3 0 6 0 0
0 0 7 0 0 0 5 8 0
0 0 0 0 6 7 0 0 0
0 1 0 0 0 4 0 0 0
5 2 0 0 0 0 0 0 0

0 0 0 2 1 0 0 0 0
0 0 7 3 0 0 0 0 0
0 5 8 0 0 0 0 0 0
4 3 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 8
0 0 0 0 0 0 0 7 6
0 0 0 0 0 0 2 5 0
0 0 0 0 0 7 3 0 0
0 0 0 0 9 8 0 0 0

7 8 9 1 2 3 4 5 6
3 0 0 0 4 0 0 0 8
5 0 0 0 9 0 0 0 1
8 0 0 0 3 0 0 0 4
1 2 3 4 5 6 7 8 9
6 0 0 0 7 0 0 0 2
9 0 0 0 1 0 0 0 5
2 0 0 0 6 0 0 0 7
1 5 6 7 8 9 1 2 3

1 0 0 2 0 0 3 0 0
2 0 0 3 0 0 4 0 0
3 0 0 4 0 0 5 0 0
4 0 0 5 0 0 6 0 0
0 0 0 0 0 0 0 0 0
0 0 3 0 0 4 0 0 5
0 0 4 0 0 5 0 0 6
0 0 5 0 0 6 0 0 7
0 0 6 0 0 7 0 0 8

0 0 0 0 0 0 0 1 0
0 0 0 0 0 2 0 0 3
0 0 0 4 0 0 0 0 0
0 0 0 0 0 0 5 0 0
4 0 1 6 0 0 0 0 0
0 0 7 1 0 0 0 0 0
0 5 0 0 0 0 2 0 0
0 0 0 0 8 0 0 4 0
0 3 0 9 1 0 0 0 0

0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 2 3
0 0 4 0 0 5 0 0 0
0 0 0 1 0 0 0 0 0
0 0 0 0 3 0 6 0 0 
0 0 7 0 0 0 5 8 0
0 0 0 0 6 7 0 0 0
0 1 0 0 0 4 0 0 0
5 2 0 0 0 0 0 0 0

0 0 0 0 0 0 0 0 0
0 9 0 0 1 0 0 3 0
0 0 6 0 2 0 7 0 0
0 0 0 3 0 4 0 0 0
2 1 0 0 0 0 0 9 8
0 0 0 0 0 0 0 0 0
0 0 2 5 0 6 4 0 0 
0 8 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0

0 0 0 9 0 2 0 0 0
0 4 0 0 0 0 0 5 0
0 0 2 0 0 0 3 0 0
2 0 0 0 0 0 0 0 7
0 0 0 4 5 6 0 0 0
6 0 0 0 0 0 0 0 9
0 0 7 0 0 0 8 0 0 
0 3 0 0 0 0 0 4 0
0 0 0 2 0 7 0 0 0

0 0 0 6 0 3 0 0 0
0 3 0 0 1 0 0 5 0
0 0 9 0 0 0 2 0 0
7 0 0 1 0 6 0 0 9
0 2 0 0 0 0 0 8 0
1 0 0 4 0 9 0 0 3
0 0 8 0 0 0 1 0 0
0 5 0 0 9 0 0 7 0
0 0 0 7 0 4 0 0 0

1 0 2 0 0 0 0 0 0
0 0 3 0 0 0 0 0 0
0 0 0 0 0 0 0 0 4
0 4 0 0 5 0 0 0 0
0 6 0 0 7 0 0 0 0
0 0 0 0 0 0 0 2 0
0 8 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 8 0 0

0 0 0 0 0 0 0 0 0
0 1 2 0 3 4 5 6 7
0 3 4 5 0 6 1 8 2
0 0 1 0 5 8 2 0 6
0 0 8 6 0 0 0 0 1
0 2 0 0 0 7 0 5 0
0 0 3 7 0 5 0 2 8
0 8 0 0 6 0 7 0 0
2 0 7 0 8 3 6 1 5

0 0 6 7 0 3 5 0 0
0 0 0 0 4 0 0 0 0
5 0 0 0 0 0 0 0 2
9 0 0 0 0 0 0 0 7
0 3 0 0 0 0 0 4 0
8 0 0 0 0 0 0 0 1
1 0 0 0 0 0 0 0 4
0 0 0 0 0 0 0 0 0
0 5 9 2 6 7 3 1 0

1 4 0 9 3 0 0 0 0
9 3 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
5 6 0 0 0 0 0 0 4
8 9 0 0 0 0 0 2 7
0 0 0 0 0 0 0 0 0
0 0 0 2 5 0 0 1 8
0 0 0 7 4 0 0 3 2

0 0 0 5 6 0 0 3 4
0 0 0 7 8 0 0 5 6
0 0 0 0 0 0 0 0 0
1 2 0 0 0 0 0 0 0
3 4 0 0 0 0 0 6 7
0 0 0 0 0 0 0 8 9
0 0 0 0 0 0 0 0 0
4 5 0 0 2 3 0 0 0
6 7 0 0 4 5 0 0 0

师门树

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Parse Tree</title>
    <meta name="description" content="A collapsible tree layout with all of the leaf nodes at the same layer." />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://unpkg.com/gojs/release/go.js"></script>
    <script id="code">
        function init() {
            var $ = go.GraphObject.make;
            myDiagram =
                $(go.Diagram, "myDiagramDiv",
                    {
                        allowCopy: false,
                        allowDelete: false,
                        allowMove: false,
                        initialAutoScale: go.Diagram.Uniform,
                        layout:
                            $(FlatTreeLayout,
                                {
                                    angle: 90,
                                    compaction: go.TreeLayout.CompactionNone
                                }),
                        "undoManager.isEnabled": true
                    });
            myDiagram.nodeTemplate =
                $(go.Node, "Vertical",
                    { selectionObjectName: "BODY" },
                    $(go.Panel, "Auto", { name: "BODY" },
                        $(go.Shape, "RoundedRectangle",
                            new go.Binding("fill"),
                            new go.Binding("stroke")),
                        $(go.TextBlock,
                            { font: "bold 12pt Arial, sans-serif", margin: new go.Margin(4, 2, 2, 2) },
                            new go.Binding("text"))
                    ),
                    $(go.Panel,
                        { height: 17 },
                        $("TreeExpanderButton")
                    )
                );
            myDiagram.linkTemplate =
                $(go.Link,
                    $(go.Shape, { strokeWidth: 1.5 }));

            const color0 = 'rgba(87,124,151,90)';
            const color1 = 'rgba(164,193,209,90)';
            const color2 = 'rgba(178,76,71,90)';
            const color3 = 'rgba(252,167,160,90)';

            // 初始化
            let nodeDataArray = [
                { key: 1, text: "师生树", fill: color0, stroke: color0 }
            ];
            let inArrArr = document.getElementById('input').value.split('\n\n');
            let id = 1;
            // 对每组进行处理
            inArrArr.forEach(x => {
                let inArr = x.split('\n');
                nodeDataArray.push({ key: ++id, text: inArr[0].split(':')[1], fill: color1, stroke: color1, parent: 1 }); // 添加导师
                let rootId = id;
                // 对每行进行处理
                inArr.slice(1).sort().forEach(y => {
                    let data = y.split(':');
                    nodeDataArray.push({ key: ++id, text: data[0], fill: color2, stroke: color2, parent: rootId }); // 添加级数
                    let subId = id;
                    data[1].split('、').forEach(z => nodeDataArray.push({ key: ++id, text: z, fill: color3, stroke: color3, parent: subId })); // 添加学生
                });
            });

            myDiagram.model =
                $(go.TreeModel,
                    { nodeDataArray: nodeDataArray });
        }

        // Customize the TreeLayout to position all of the leaf nodes at the same vertical Y position.
        function FlatTreeLayout() {
            go.TreeLayout.call(this);
        }
        go.Diagram.inherit(FlatTreeLayout, go.TreeLayout);
        FlatTreeLayout.prototype.commitLayout = function() {
            go.TreeLayout.prototype.commitLayout.call(this);
            var y = -Infinity;
            this.network.vertexes.each(function(v) {
                y = Math.max(y, v.node.position.y);
            });
            this.network.vertexes.each(function(v) {
                if (v.destinationEdges.count === 0) {
                    v.node.position = new go.Point(v.node.position.x, y);
                    v.node.toEndSegmentLength = Math.abs(v.centerY - y);
                } else {
                    v.node.toEndSegmentLength = 10;
                }
            });
        };
    </script>
</head>
<body>
<div id="sample">
    <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:500px"></div>
    <label for="input"><textarea id="input" style="width: 100%; height: 25%"></textarea></label><br/>
    <button onclick="init()">提交</button>
</div>
</body>
</html>

tests.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>QUnit Example</title>
    <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.9.2.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.9.2.js"></script>
<script>
    function Main(x) {
        let nodeDataArray = [{ key: 1, text: "师生树" }];
        let inArrArr = x.split('\n\n');
        let id = 1;
        inArrArr.forEach(x => {
            let inArr = x.split('\n');
            nodeDataArray.push({ key: ++id, text: inArr[0].split(':')[1], parent: 1 });
            let rootId = id;
            inArr.slice(1).sort().forEach(y => {
                let data = y.split(':');
                nodeDataArray.push({ key: ++id, text: data[0], parent: rootId });
                let subId = id;
                data[1].split('、').forEach(z => nodeDataArray.push({ key: ++id, text: z, parent: subId })); // 添加学生
            });
        });
        return nodeDataArray;
    }

    QUnit.test("standard1", assert => {
        const str = "导师:许三\n" +
            "2009级博士生:赵大、李六、赵三\n" +
            "2010级硕士生:张三、钱四、刘二\n" +
            "2009级硕士生:孙一、孙三、钱二\n" +
            "2011级博士生:孔三、刘四、吴九";
        const res = "[{\"key\":1,\"text\":\"师生树\"},{\"key\":2,\"text\":\"许三\",\"parent\":1},{\"key\":3,\"text\":\"2009级博士生\",\"parent\":2},{\"key\":4,\"text\":\"赵大\",\"parent\":3},{\"key\":5,\"text\":\"李六\",\"parent\":3},{\"key\":6,\"text\":\"赵三\",\"parent\":3},{\"key\":7,\"text\":\"2009级硕士生\",\"parent\":2},{\"key\":8,\"text\":\"孙一\",\"parent\":7},{\"key\":9,\"text\":\"孙三\",\"parent\":7},{\"key\":10,\"text\":\"钱二\",\"parent\":7},{\"key\":11,\"text\":\"2010级硕士生\",\"parent\":2},{\"key\":12,\"text\":\"张三\",\"parent\":11},{\"key\":13,\"text\":\"钱四\",\"parent\":11},{\"key\":14,\"text\":\"刘二\",\"parent\":11},{\"key\":15,\"text\":\"2011级博士生\",\"parent\":2},{\"key\":16,\"text\":\"孔三\",\"parent\":15},{\"key\":17,\"text\":\"刘四\",\"parent\":15},{\"key\":18,\"text\":\"吴九\",\"parent\":15}]";
        assert.equal(JSON.stringify(Main(str)), res, "Passed!");
    });

    QUnit.test("standard2", assert => {
        const str = "导师:张三\n" +
            "2016级博士生:天一、王二、吴五\n" +
            "2015级硕士生:李四、王五、许六\n" +
            "2016级硕士生:刘一、李二、李三\n" +
            "2017级本科生:刘六、琪七、司四";
        const res = "[{\"key\":1,\"text\":\"师生树\"},{\"key\":2,\"text\":\"张三\",\"parent\":1},{\"key\":3,\"text\":\"2015级硕士生\",\"parent\":2},{\"key\":4,\"text\":\"李四\",\"parent\":3},{\"key\":5,\"text\":\"王五\",\"parent\":3},{\"key\":6,\"text\":\"许六\",\"parent\":3},{\"key\":7,\"text\":\"2016级博士生\",\"parent\":2},{\"key\":8,\"text\":\"天一\",\"parent\":7},{\"key\":9,\"text\":\"王二\",\"parent\":7},{\"key\":10,\"text\":\"吴五\",\"parent\":7},{\"key\":11,\"text\":\"2016级硕士生\",\"parent\":2},{\"key\":12,\"text\":\"刘一\",\"parent\":11},{\"key\":13,\"text\":\"李二\",\"parent\":11},{\"key\":14,\"text\":\"李三\",\"parent\":11},{\"key\":15,\"text\":\"2017级本科生\",\"parent\":2},{\"key\":16,\"text\":\"刘六\",\"parent\":15},{\"key\":17,\"text\":\"琪七\",\"parent\":15},{\"key\":18,\"text\":\"司四\",\"parent\":15}]";
        assert.equal(JSON.stringify(Main(str)), res, "Passed!");
    });

    QUnit.test("multi", assert => {
        const str = "导师:许六\n" +
            "2009级博士生:赵大、李六、赵三\n" +
            "2010级硕士生:张三、钱四、刘二\n" +
            "2009级硕士生:孙一、孙三、钱二\n" +
            "2011级博士生:孔三、刘四、吴九\n" +
            "\n" +
            "导师:张三\n" +
            "2016级博士生:天一、王二、吴五\n" +
            "2015级硕士生:李四、王五、许六\n" +
            "2016级硕士生:刘一、李二、李三\n" +
            "2017级本科生:刘六、琪七、司四";
        const res = "[{\"key\":1,\"text\":\"师生树\"},{\"key\":2,\"text\":\"许六\",\"parent\":1},{\"key\":3,\"text\":\"2009级博士生\",\"parent\":2},{\"key\":4,\"text\":\"赵大\",\"parent\":3},{\"key\":5,\"text\":\"李六\",\"parent\":3},{\"key\":6,\"text\":\"赵三\",\"parent\":3},{\"key\":7,\"text\":\"2009级硕士生\",\"parent\":2},{\"key\":8,\"text\":\"孙一\",\"parent\":7},{\"key\":9,\"text\":\"孙三\",\"parent\":7},{\"key\":10,\"text\":\"钱二\",\"parent\":7},{\"key\":11,\"text\":\"2010级硕士生\",\"parent\":2},{\"key\":12,\"text\":\"张三\",\"parent\":11},{\"key\":13,\"text\":\"钱四\",\"parent\":11},{\"key\":14,\"text\":\"刘二\",\"parent\":11},{\"key\":15,\"text\":\"2011级博士生\",\"parent\":2},{\"key\":16,\"text\":\"孔三\",\"parent\":15},{\"key\":17,\"text\":\"刘四\",\"parent\":15},{\"key\":18,\"text\":\"吴九\",\"parent\":15},{\"key\":19,\"text\":\"张三\",\"parent\":1},{\"key\":20,\"text\":\"2015级硕士生\",\"parent\":19},{\"key\":21,\"text\":\"李四\",\"parent\":20},{\"key\":22,\"text\":\"王五\",\"parent\":20},{\"key\":23,\"text\":\"许六\",\"parent\":20},{\"key\":24,\"text\":\"2016级博士生\",\"parent\":19},{\"key\":25,\"text\":\"天一\",\"parent\":24},{\"key\":26,\"text\":\"王二\",\"parent\":24},{\"key\":27,\"text\":\"吴五\",\"parent\":24},{\"key\":28,\"text\":\"2016级硕士生\",\"parent\":19},{\"key\":29,\"text\":\"刘一\",\"parent\":28},{\"key\":30,\"text\":\"李二\",\"parent\":28},{\"key\":31,\"text\":\"李三\",\"parent\":28},{\"key\":32,\"text\":\"2017级本科生\",\"parent\":19},{\"key\":33,\"text\":\"刘六\",\"parent\":32},{\"key\":34,\"text\":\"琪七\",\"parent\":32},{\"key\":35,\"text\":\"司四\",\"parent\":32}]"
        assert.equal(JSON.stringify(Main(str)), res, "Passed!");
    });
</script>
</body>
</html>

C.b^fMKph/MKse1Jpc{7h.html

<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
		<script src="js/mui.min.js"></script>
		<link href="css/mui.min.css" rel="stylesheet" />
		<link rel="stylesheet" href="css/font-awesome.css">
		<link rel="stylesheet" href="css/calender.css">
		<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
		<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
		<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
		<script type="text/javascript" charset="utf-8">
			mui.init();
		</script>
		<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1,user-scalable=no">
		<meta name="apple-mobile-web-app-capable" content="yes">
		<meta name="apple-mobile-web-app-status-bar-style" content="black">

	</head>
	<style>
		.mui-content{
		background-color: #FFFFFF;

	}
	.mui-bar-nav {
	    box-shadow: 0 1px 6px #ffffff;
	}
	.head{
		box-shadow: 1px 1px 3px #6a6a6a;
	}
	#calicon{
		font-size:24px;
		color:#F58A8A;
		position:absolute;
		right:20px;
		top:3px;
	}
</style>
	<body>
		<nav class="mui-bar mui-bar-tab">
			<a class="mui-tab-item" href="#tabbar">
				<i class="fa fa-check-square" style="font-size:30px;color:#F58A8A"></i>
			</a>
			<a class="mui-tab-item mui-active" href="#tabbar-with-calender">
				<i class="fa fa-calendar-o" style="font-size:30px;color:#CBC9C9"></i>
			</a>
			<a class="mui-tab-item" href="#tabbar-with-check">
				<i class="fa fa-calendar-check-o" style="font-size:30px;color:#CBC9C9"></i>
			</a>
			<a class="mui-tab-item" href="#tabbar-with-perCenter">
				<i class="fa fa-user" style="font-size:30px;color:#CBC9C9"></i>
			</a>
		</nav>
		<header class="mui-bar mui-bar-nav">
			<h3 class="mui-left" style="margin-top: 10;">ToDoList</h3>
			<i id="calicon" class="fa fa-plus" style="margin-top: 10px;"></i>
		</header>
		<style>
			.fkButton {
				border: 0px;
				padding: 0px;
				margin: 0px;
			}
		</style>
		<script>
			let TodoList = [
				{name:'番茄钟UI设计',	time:'今天,12:50-13:30',		fin:false},
				{name:'待办2',      	 	time:'6月10日,12:00-13:00',	fin:false},
				{name:'图像处理',		time:'昨天,10:30-11:50',		fin:true},
			]; //get data from fucking database

			let fkButtonSquare = (b) => {
				// You just DO WHAT THE FUCK YOU WANT TO.

				let name = b.parent().contents().text().trim();
				let index = TodoList.findIndex(x => x.name == name);
				TodoList[index].fin = (!TodoList[index].fin);
				UpdateList();
			};

			let UpdateList = () => {
				let tl = $('#fkTodoList'), dl = $('#fkDoneList'), tlh = '', dlh = '';
				tl.html('');
				dl.html('');
				TodoList.forEach(x => {
					if(x.fin) {
						dlh += '<div name="fkDoneItem" class="mui-table">' +
							'<div class="mui-table-cell mui-col-xs-10">' +
								'<h4 class="mui-ellipsis" style="margin-left:15px;margin-bottom: 0;"><button class="fkButton" onclick="fkButtonSquare($(this))"><i class="fa fa-check-square"></i></button> ' + x.name + ' </h4>' +
								'<span class="mui-h5" style="font-size:12px;color:#808080;margin-left:37px;"> ' + x.time + ' </span>' +
							'</div>' +
						'</div>';
					} else {
						tlh += '<li class="mui-table-view-cell">' +
					'<div name="fkTodoItem" class="mui-table">' +
						'<div class="mui-table-cell mui-col-xs-10">' +
							'<h4 class="mui-ellipsis"><button class="fkButton" onclick="fkButtonSquare($(this))"><i class="fa fa-square-o"></i></button> ' + x.name + ' </h4>' +
							'<span class="mui-h5" style="font-size:12px;color:#F58A8A;margin-left:20px;"> ' + x.time + ' </span>' +
						'</div>' +
						'<button type="button" class="mui-btn mui-btn-danger mui-btn-outlined">' +
							'<span class="mui-h5" style="font-size:20px;color:#F58A8A;">开始</span>' +
						'</button>' +
					'</div>' +
				'</li>';
					}
				});
				tl.html(tlh);
				dl.html(dlh);
			};

		</script>
		<div class="mui-content" style="padding-bottom:0;">
			<ul id="fkTodoList" class="mui-table-view" style="margin-top:3px;">

				<li class="mui-table-view-cell">
					<div name='fkTodoItem' class="mui-table">
						<div class="mui-table-cell mui-col-xs-10">
							<h4 class="mui-ellipsis"><button class="fkButton" onclick="fkButtonSquare($(this))"><i class="fa fa-square-o"></i></button> 番茄钟UI设计</h4>
							<span class="mui-h5" style="font-size:12px;color:#F58A8A;margin-left:20px;">今天,12:50-13:30</span>
						</div>
						<button type="button" class="mui-btn mui-btn-danger mui-btn-outlined">
							<span class="mui-h5" style="font-size:20px;color:#F58A8A;">开始</span>
						</button>
					</div>
				</li>

				<li class="mui-table-view-cell">
					<div name='fkTodoItem' class="mui-table">
						<div class="mui-table-cell mui-col-xs-10">
							<h4 class="mui-ellipsis"><i class="fa fa-square-o"></i> 待办2</h4>
							<span class="mui-h5" style="font-size:12px;color:#808080;margin-left:20px;">6月10日,12:00-13:00</span>
						</div>
						<button type="button" class="mui-btn mui-btn-danger mui-btn-outlined">
							<span class="mui-h5" style="font-size:20px;color:#F58A8A">开始</span>
						</button>
					</div>
				</li>

			</ul>
			</li>
			<ul class="mui-table-view">
				<li class="mui-table-view-cell mui-collapse" style="background-color:#f5dada;">
					<a class="mui-navigate-right" href="#" style="height:18px;padding-top:2;color:#525050">已完成</a>
					<div id="fkDoneList" class="mui-collapse-content">

						<div name="fkDoneItem" class="mui-table">
							<div class="mui-table-cell mui-col-xs-10">
								<h4 class="mui-ellipsis" style="margin-left:15px;margin-bottom: 0;"><i class="fa fa-check-square"></i> 图像处理</h4>
								<span class="mui-h5" style="font-size:12px;color:#808080;margin-left:37px;"> 昨天,10:30-11:50</span>
							</div>
						</div>
					</div>
				</li>
			</ul>
		</div>
	</body>
</html>

C.b^fMKph/MK8[(L_h.html

<!DOCTYPE html>
<html>
<head style="height: 100%;">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
    <title></title>
    <script src="js/mui.min.js"></script>
    <link href="css/mui.min.css" rel="stylesheet"/>
	<link rel="stylesheet" href="css/font-awesome.css">
	<link rel="stylesheet" href="css/calender.css">
	<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">  
	<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
	<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
    <script type="text/javascript" charset="utf-8">
      	mui.init();
    </script>
</head>

<body style="height: 100%;">
	<div style="position: absolute; height: 100%; width: 100%; display: grid; grid-template-rows: 10% 50% 40%; height: 1hv;">
		<div style="font-size: 1.5rem; display: grid; grid-template-columns: 1fr auto 1fr;">
			<div style="margin-right: 1rem; color: rgb(247,200,200); vertical-align: top; font-size: 8rem; text-align: right;">“</div>
			<div style="transform: translateY(2.75rem); text-align: center;">开始专注咯,现在放下你的手机吧</div>
			<div style="margin-left: 1rem; color: rgb(247,200,200); transform: translateY(1.25rem); font-size: 8rem;">”</div>
		</div>

		<div style="background-color: rgb(255,241,241); color: rgb(247,200,200); margin: 5%; display: grid; text-align: center; grid-template-rows: 1fr 1fr;">
			<div style="transform: translateY(1rem); margin: auto auto; font-size:8rem"><u>00 : 00</u></div>

			<div style="margin: auto auto; font-size:2rem">
				<div>lesion设计</div>
				<div>进行中</div>
			</div>

		</div>

		<div style="text-align: center;">
			<button style="border: 0px; visibility: visible;" class="btn btn-lg" data-toggle="modal" data-target="#pauseModal" id='pauseBtnmd' type="button">
				<i style="color: rgb(247,200,200); font-size: 6rem;" class="fa fa-pause-circle-o"></i>
			</button>
			<button style="border: 0px;" class="btn btn-lg" data-toggle="modal" data-target="#stopModal" id='stopBtn' type="button">
				<i style="color: rgb(247,200,200); font-size: 6rem;" class="fa fa-stop-circle-o"></i>
			</button>
			<script>
				var paused = false;
			</script>
		</div>

		<style type="text/css">
			.modal-header.fxx {
				background-color: rgb(246,151,151);
				 color: #FFFFFF;
				 border-radius: 0.3rem 0.3rem 0 0;
			}
		</style>

		<div class="modal fade" id="abortModal" tabindex="-1" role="dialog" aria-labelledby="abortModalLabel" aria-hidden="true">
			<div class="modal-dialog">
				<div class="modal-content">
					<div class="modal-header fxx bg-danger">
						<h4 class="modal-title" id="myModalLabel" style="text-align: center;">放弃后的任务不会标记为已完成</h4>
					</div>
					<div style="text-align: center;" class="modal-footer">
						<button type="button" class="btn" data-dismiss="modal">确认放弃</button><br />
						<button type="button" class="btn" data-dismiss="modal">取消</button>
					</div>
				</div><!-- /.modal-content -->
			</div><!-- /.modal-dialog -->
		</div>

		<div class="modal fade" id="pausedModal" tabindex="-1" role="dialog" aria-labelledby="pausedModalLabel" aria-hidden="true">
			<div class="modal-dialog">
				<div class="modal-content">
					<div class="modal-header fxx bg-danger">
						<h4 style="text-align: center;" class="modal-title" id="myModalLabel">请选择您的操作</h4>
					</div>
					<div style="text-align: center;" class="modal-footer">
						<button type="button" class="btn" data-dismiss="modal" onclick="(()=>{
								paused = false;
								document.getElementById('pauseBtnmd').style.visibility = 'visible';
								document.getElementById('stopBtn').dataset.target ='#stopModal';
							})()">结束当前休息计时</button><br />
						<button type="button" class="btn" data-dismiss="modal">取消</button>
					</div>
				</div><!-- /.modal-content -->
			</div><!-- /.modal-dialog -->
		</div>

		<div class="modal fade" id="stopModal" tabindex="-1" role="dialog" aria-labelledby="stopModalLabel" aria-hidden="true">
			<div class="modal-dialog">
				<div class="modal-content">
					<div class="modal-header fxx bg-danger">
						<h4 style="text-align: center;" class="modal-title" id="myModalLabel">请选择您的操作</h4>
					</div>
					<div style="text-align: center;" class="modal-footer">						
						<button class="btn" data-toggle="modal" data-target="#abortModal" id='abortBtn' data-dismiss="modal" type="button">
							放弃计时
						</button><br />

						<button type="button" class="btn" data-dismiss="modal" onclick="(()=>{
								paused = true;
								document.getElementById('pauseBtnmd').style.visibility = 'hidden';
								document.getElementById('stopBtn').dataset.target = '#pausedModal';
							})()">终止计时</button><br />
						<button type="button" class="btn" data-dismiss="modal">取消</button>
					</div>
				</div><!-- /.modal-content -->
			</div><!-- /.modal-dialog -->
		</div>

	</div>
</body>
</html>

 

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注