Skip to content

Instantly share code, notes, and snippets.

@quirinpa
Last active January 27, 2016 02:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save quirinpa/d57536bb505359621279 to your computer and use it in GitHub Desktop.
Save quirinpa/d57536bb505359621279 to your computer and use it in GitHub Desktop.

Trabalho Prático de Paradigmas de Programação 07/01/2016 - Relatório

Introdução

Este relatório está organizado por tópicos que julgo servirem para para uma introdução rápida ao algoritmo, e foi construído com o intuito de ser fácil de consultar.

Pouco convencional

um relato de insegurança acerca deste relatório

Três fases

fala brevemente sobre as três fases essenciais do simulador

Estrutura e análise de eventos

sobre o amadurecimento da ideia do algoritmo e as implicações que isso tem nas estruturas escolhidas, em particular, na análise de eventos.

Design do código

sobre as minhas tentativas de tornar o código perceptível e eficiente

Dados

informações sobre os dados que a simulação devolve

Tipos de dados

A explicitação dos diferentes tipos de dados utilizados na simulação, incluíndo as suas descrições e respectivo código-fonte

Exemplo de utilização

Exemplo de um resultado para determinados parâmetros.

Implementação

O código-fonte do pacote principal

Conclusão

Pouco convencional

Talvez o meu uso de eventos não seja o mais convencional, mas possibilita-me uma simulação mais interessante. Talvez não devesse mostrar tantos dados, ou o formato que escolhi para este relatório não seja o mais adequado. Talvez seja tarde demais para estar a trabalhar nele. De qualquer forma, aqui está.

Três fases

O Simulador é composto por três fases:

  • Gerar e inserir eventos na cap, e guardar dados necessários;
  • Actualizar dados conforme o estado dos balcões no final da primeira fase;
  • Apresentar resultados;

Este funcionamento é simbiótico com outros detalhes de implementação do projeto.

Estrutura de análise de eventos

Depois de fazer várias experiências diferentes, pareceu-me que a melhor forma de lidar com alguns dos requisitos deste projeto seria passar os utentes para o próximo evento, e que só precisava de passar mais um dado para categorizar o evento: se se tratara de um evento de entrada (push) ou de saída (pop). Como tal, a minha lógica de anàlise de eventos circula à volta dessa ideia.

Design de código

Nem toda a gente acha que se deva escrever código da mesma maneira. Porém, algo se tem que decidir, os hábitos de programação ajudam a desenvolver código não só eficiente mas também legível.

Aqui estão alguns exemplos das escolhas que fiz:

Utilizo módulos apenas quando necessário para aumentar a legibilidade do código.

Defino funções onde dá mais jeito, para que tenham acesso a variàveis que modificam colateralmente. Por exemplo, PushEvt, PopEvt e NewEvt, cujos comportamentos são necessários em várias partes do código, precisam de variàveis que só estão definidas dentro da função sim. Por exemplo, NewEvt acede a tec, que é uma das variàveis passadas à função sim, e não quero estar a passar tec como argumento à função porque não é prático e aumenta a complexidade do código de forma desnecessária. Também utiliza PushEvt, por isso está definida depois. PushEvt e PopEvt modificam a cap directamente, para que não seja necessário estar a envocar:

cap = PushEvt[cap, t, utente];

Passa a ser mais fácil entender o código quando os conceitos são simples e bem estabelecidos.

Dados

Conhecendo o projecto aos poucos

Nomes

Durante o desenvolvimento do projeto, utilizei nomes aleatórios para facilitar a depuração. Linhas como:

(* Print["DEBUG: ",et,"m ",utente," entra na repartição."]; *)

Ajudam-me a identificar problemas com o algoritmo, através do Print de cada uma das ações e do Shift-Enter seguido de Ctrl+F para analisar o percurso de determinado utente durante a simulação.

Acabei por utilizar nomes para outras coisas, que achei que apesar de pouco relevantes, são interessantes. Apesar de saber que têm impacto na performance, achei que removê-los seria muito fácil, e que portanto não teriam grande impacto na minha nota.

Gráfico

É apresentado, para cada balcão, um gráfico que representa as mudanças de número de utentes ao longo do tempo para cada fila de espera (baixa e alta prioridade) que esse balcão possua. Para tal, não utilizei o ListPlot, mas sim PlotBalcao, uma função personalizada baseada na travessia de uma lista de eventos de uma fila, implementada por PlotL:

PlotL[l_, dt_] := Module[{i, nu},
	i = 1; nu = 0;
	While[i <= Length[l] && l[[i, 1]] <= dt,
		If[l[[i, 2]], nu++, nu--];
		i++
	];
	nu
];

O resultado assemelha-se ao seguinte:

g2.png

Para estabelecer os limites superiores e inferiores do Plot, precisei de obter os máximos de utentes em cada fila de cada balcão. Portanto decidi apresentar estes dados para todos os balcões.

Como quase todas as coisas neste projeto, a possibilidade de criar gráficos que variam ao longo do tempo foi criada para depuração, e acabou por ficar.

Por vezes, pode notar-se que o gráfico não exibe uma determinada mudança nos dados (é fácil de notar quando o gráfico desenha linhas inclinadas). Isto acontece porque o Mathemática não percorre todos os pontos do gráfico (são infinitos). Em vez disso, utiliza um intervalo, quanto mais pequeno, mais preciso. Acabei por implementar uma nova função para desenho do gráfico, através de um Plot vazio e Lines, isso fez-me aperceber que poderia reduzir as fazes da aplicação e fazer algumas optimizações. A nova versão é o segundo ficheiro deste gist, lamento que não esteja indentada mas podem sempre copiar para o matemática. Quando tiver tempo melhoro o gist.

Pessoa que esteve mais tempo em espera em cada fila de cada balcão

Nos primórdios deste projeto, implementei esta funcionalidade apenas para a primeira fila dos balcões 1 e 2.

Mas já que ia mostrar mais dados do que era pedido, decidi também mostrar o nome da pessoa que esperou mais tempo e o número de pessoas máximo em cada fila de cada balcão. Para isso, precisei de guardar em cada utente o momento em que entrou no ultimo balcão,

Os números estimados contabilizam os utentes que não chegaram a sair (2ª fase da simulação).

Máximo e minimo de tempo na repartição

Já que vou mostrar o máximo, porque não o mínimo também? Foi o que pensei.

Pensando melhor, se calhar o mínimo é um dado irrelevante que vai sempre rondar o valor de tec + tabc.

Tipos de dados

Seguem-se as descrições de cada tipo de dados usado na simulação.

Cap

A Cap é uma lista de eventos ordenada por inst[evt]. Em vez de se ordenar a lista após inserir os eventos, e a favor do facto de, na ultima versão, estes serem gerados completamente sequencialmente, acr[cap, e] insere ordenadamente.

cap.m

BeginPackage["cap`"];

Needs["eventos`"];

cap::usage = "Tipo das cadeias de acontecimentos pendentes.";
nova::usage = "nova denota a cadeia vazia.";
acr::usage = "acr[c,e] acrescenta o evento e a cadeia c.";
proximo::usage = "proximo[c] retorna o evento na cadeia c de menor instante.";
retira::usage = "retira[c] retira da cadeia em c o evento de menor instante.";
cadeiaVaziaQ::usage = "cadeiaVaziaQ[c] indica se a cadeia em c está vazia.";

Begin["`Private`"];

nova = {};
acr[cap_, e_] := Module[{t, l, idx}, t = inst[e]; l = Length[cap]; idx = 1;
	While[idx <= l && t >= inst[cap[[idx]]], idx++];
	Insert[cap, e, idx]
];
proximo[cap_] := First[cap];
retira[cap_] := Rest[cap];
cadeiaVaziaQ[cap_] := cap == {};

End[];
EndPackage[];

Eventos

Os eventos são colocados na cap e analisados sequencialmente durante o ciclo principal da simulação.

Como referi na seção "Estrutura de análise de eventos", as suas categorias têm o seguinte formato:

{
	isPush ( Boolean ),
	utente ( Utente )
}

eventos.m

BeginPackage["eventos`"];

eventos::usage = "A implementacaoo de evento (as suas categorias são costumizaveis).";
evt::usage = "evt[t,c] denota o evento da categoria c que ocorre no instante t.";
inst::usage = "inst[e] retorna o instante em que ocorreu o evento e.";
cat::usage = "cat[e] retorna a categoria (objecto) do evento e.";

Begin["`Private`"];

evt[t_, e_] := {t, e};
inst[e_] := e[[1]];
cat[e_] := e[[2]];

End[];
EndPackage[];

Utentes

Segue-se uma pequena descrição do modo como o tipo de dados Utente é utilizado durante a simulação.

Os utentes moram em dois sítios, nesta simulação:

  • Como segundo elemento nas Listas cat[evt]
  • Nas filas de espera

Por exemplo, o momento em que um cliente chega à repartição é representado por um evento de categoria push (que é gerado quando um utente acabou de entrar, ou, no caso do primeiro utente, na inicialização). O utente é parte da "categoria" do evento.

Após o evento ser processado, caso lhe tenha sido atríbuido o balcão 2, e o mesmo esteja ocupado, passa a morar na sua respectiva fila de espera de prioridade 1.

Esse comportamento é descrito pelo ciclo principal da simulação, e é a razão para os utentes terem a seguinte estrutura interna:

{
	BalcaoInicial, (1 ou 2)
	UltimoBalcao (id do ultimo balcao),
	Entrou, (t de entrada na repartição)
	EntrouBalcao, (t de entrada no ultimo balcão)
	Nome  (string)
}

utentes.m

E a sua respectiva implementação.

BeginPackage["utentes`"];

utentes::usage = "Implementação do tipo utente.";
NovoUtente::usage = "NovoUtente[x, t] cria um utente aleatoriamente.";
BalcaoInicial::usage = "BalcaoInicial[utente] revela o número do balcão inicial do utente.";
UltimoBalcao::usage = "UltimoBalcao[utente] revela o último balcão no qual o utente foi atendido.";
Entrou::usage = "Entrou[utente] denota t em que o utente entrou na repartição.";
EntrouBalcao::usage = "EntrouBalcao[utente] denota t em que o utente entrou no balcão actual.";
Nome::usage = "Nome[utente] denota o nome do utente.";
AlteraUltimoBalcao::usage = "AlteraUltimoBalcao[utente,n], devolve utente com balcão igual a n.";
ProximoBalcao::usage = "ProximoBalcao[utente], calcula o próximo balcão do utente, com base no actual.";
ProximaPrioridade::usage = "ProximaPrioridade[utente], calcula a proxima prioridade do utente com base no último balcão.";

Begin["`Private`"];

firstNames = { "Noah","Emma","Liam","Olivia","Mason","Sophia","Amy","Jacob","Daniel","David","William","Harry","Ethan","Billy","Mia","Emily","Alexander","James","Abigail","Isabella","Michael","James","Benjamin","Logan","Evelyn","Aiden","Chloe","Jon","Emily","Randy","Peter","Zoey","Lucas","Samuel","Joseph","Anthony","Andrew","Gabriel","Victoria","Grace","Zoey","Natalie","Addison","Lillian","John","Hannah","Luke","Scarlett","Isaac","Oliver","Samantha","Henry","Owen","Claire","Skylar","Levi","Jack","Sarah","Aaron","Ellie","Thomas","Lucy","Alexis","Violet","Bella","Austin","Kevin" };
lastNames = { "Bush","Kirk","Li","Smith","Brown","Roy","Wilson","Leblanc","Williams","Jones","Moore","Johnson","Harris","Parker","Clark","Gonzalez","Barret","Lewis","Robinson","Walker","Young","Sanchez","Scott","King","Baker","Adams","Roberts","Carter","Green","Stewart","Flores","Morris","Murphy","Cook","Peterson","Bell","Reed","Price","Jenkins","Fisher","Perry","Davis","Anderson","White","Jackson","Hall","Lopez","Simmons","Butler","Griffin","Hayes","Ford","Wallace","Woods","West","Freeman","Webb","Black","Knight","Stone","Pierce","Fox","Lawrence","Sims","Chapman","Bishop","Lynch","Garret","Silva" };

RandomName[] := StringJoin[RandomChoice[firstNames], " ", RandomChoice[lastNames]];

NovoUtente[x_,t_] := { If[Random[]<= 1/x, 1,2], 0, t ,0, RandomName[] };
BalcaoInicial[utente_] := utente[[1]];
UltimoBalcao[utente_] := utente[[2]];
Entrou[utente_] := utente[[3]];
EntrouBalcao[utente_] := utente[[4]];
Nome[utente_] := utente[[5]];
AlteraUltimoBalcao[utente_,n_] := { BalcaoInicial[utente],n,Entrou[utente] , EntrouBalcao[utente],Nome[utente]};
ProximoBalcao[utente_] := If[UltimoBalcao[utente]==0||UltimoBalcao[utente]==3,BalcaoInicial[utente], 3];
ProximaPrioridade[utente_] := If[UltimoBalcao[utente]==3,2,1];

End[];
EndPackage[];

Balcões

Os balcões 1 e 2 têm duas filas de espera, a tesouraria apenas uma, mas ambos os comportamentos necessários podem ser descritos da mesma forma:

Quando um cliente sai do balcão, procura-se e atende-se o cliente à frente da fila de maior prioridade que exista.

Como o código de acesso aos balcões é expresso maioritariamente no ciclo principal da simulação, este é um tipo semi-implícito, cujo acesso à fila f é feito da forma b[[2, f]]. A razão para isto é que os acessos aos balcões são diferentes uns dos outros, o que não justifica, na minha opinião, a criação de funções adicionais.

Os balcões são listas com o formato:

{
	ocupado (Bool),
	Filas (Lista de filas ordenadas em ordem
		decrescente por prioridade de atendimento)
}

balcoes.m

BeginPackage["balcoes`"];

Needs["filas`"];

balcoes::usage = "Este tipo e semi-implicito no meu projeto, a maior parte da funcionalidade esta em simulaReparticao.m.";
Balcao::usage = "Balcão[n] cria um novo balcao com n filas vazias.";
NPrioridades::usage = "NPrioridades[b] retorna o numero de filas ou prioridades do balcão.";

Begin["`Private`"];

Balcao[n_] := { False,Table[inicial, {i, n}] };
NPrioridades[b_] := Length[b[[2]]];

End[];
EndPackage[];

Filas (de espera)

As filas estão implementadas como Listas. Nesta simulação são utilizadas essencialmente para armazenar utentes em espera.

Cada fila corresponde, como já referi, a uma certa prioridade e a um certo balcão.

fila.m

BeginPackage["fila`"];

fila::usage = "Tipo das filas (generico).";
inicial::usage = "Denota uma nova fila.";
entra::usage = "entra[f, n] acrescenta n ao fim da fila.";
primeiro::usage = "primeiro[f] dá o primeiro elemento da fila.";
sai::usage = "sai[f] devolve uma f sem o primeiro elemento.";
filaVaziaQ::usage = "filaVaziaQ[f] indica se a fila esta vazia.";

Begin["`Private`"];

inicial = {};
entra[f_, n_] := Append[f, n];
primeiro[f_] := First[f];
ultimo[f_] := Last[f];
sai[f_] := Rest[f];
filaVaziaQ[f_] := f == {};

End[];
EndPackage[];

Tipos implícitos

uma escolha que pode ser controversa

Por vezes poderá não ser necessário defenir níveis de abstração (funções) para tipos de dados que raramente sejam acedidos da mesma forma, como penso que seja o caso de algumas estruturas de dados deste projeto. No entanto, ao utilizar esta estratégia, corremos o risco de tornar o código menos legível. Esta seção visa, também, preencher essa lacuna o melhor possível.

cData

cData é uma variàvel que nos ajuda a guardar informações que precisamos para apresentar os dados.

É composta por listas correspondentes a cada balcão, compostas por listas correspondentes a cada fila desse balcão.

Os elementos das listas de filas têm o seguinte formato:

{
	{
		tempo máximo em espera,
		nome do utente que aguardou o máximo de tempo
	},
	lista de: {
		t de evento,
		push (boolean, tipo de evento)
	}
}

O código que gera o cData inicial é o que se segue:

cData = Map[Table[{{0, "Ninguém"}, {}}, {i, NPrioridades[#]}] &, bs];

minU e maxU

Estas variàveis auxiliares são utilizadas para guardar o mínimo e o máximo de tempo que algum utente ficou na repartição.

Têm o formato:

{
	maximo / minimo de tempo,
	nome do utente que esperou esse tempo
}

Exemplo de utilização

Segue-se um exemplo de utilização da função sim

Chamar a função sim da seguinte forma:

sim[320, 3, 3, 3, 4, 1];

Produz-se o resultado:

Simulação t 320 tec 3 x 3 tabc 3 y 4 tt 1

BALCÃO 1:

Utentes em espera:

g1.png

Fila 1 - maximo de pessoas à espera: 2, Emily Jones foi a pessoa que esperou mais (8.738m)

Fila 2 - maximo de pessoas à espera: 1, Harry Jones foi a pessoa que esperou mais (0.0313994m)

BALCÃO 2:

Utentes em espera:

g2.png

Fila 1 - maximo de pessoas à espera: 19, Alexis Robinson foi a pessoa que esperou mais (62.9665m)

Fila 2 - maximo de pessoas à espera: 3, Aaron Brown foi a pessoa que esperou mais (13.3551m)

TESOURARIA:

Utentes em espera:

g3.png

Fila 1 - maximo de pessoas à espera: 3, Emily Jones foi a pessoa que esperou mais (7.62683m)

DADOS GERAIS:

Tempo médio de permanência na repartição (sem contar com os utentes que não saíram): 27.7551m

O cliente que passou mais tempo na repartição foi Natalie Barret (101.133m)

O cliente que passou menos tempo na repartição foi Andrew Lewis (0.0310241m)

Implementação - simulaRepartição.m

Esta seção contêm a implementação do pacote que contem a função sim.

Construí o código de forma que, juntamente com este relatório, fosse perceptível o seu funcionamento. Tentei documentá-lo com comentários nos momentos mais apropriados, e deixei algumas das minhas linhas de debug comentadas, para o leitor poder experimentar fazer a análise do trajeto dos utentes..

Segue-se a parte mais maçuda deste relatório, boa sorte:

BeginPackage["simulaReparticao`"];

Needs["eventos`"];
Needs["cap`"];
Needs["fila`"];
Needs["utente`"];
Needs["balcoes`"];

simulaReparticao::usage =  "pacote que disponibiliza a função sim para a simulação de uma  repartição de finanças, conforme o enunciado.";
sim::usage = "sim[t,tec,x,tabc,y,tt] efectua a simulacao de utentes em espera numa repartição de finanças durante t minutos, demorando tec minutos  entre chegadas de clientes. um em cada x desses clientes vai para o  balcao 2, tabc é o tempo médio que demoram a ser atendidos em  qualquer balcão, 1 em cada y dos clientes vai à tesouraria após o  primeiro balcão, e demoram tt a viajar da tesouraria para os balcões  e vice versa.";

Begin["`Private`"];

obsExp[m_] := -m*Log[Random[]];
sim[t_, tec_, x_, tabc_, y_, tt_] := Module[{cap, PushEvt, PopEvt, NewEvt},
	cap = nova;

	Print[Style["Simulação", Bold], " t ", t, " tec ", tec, " x ", x, " tabc ", tabc, " y ", y, " tt ", tt];

	(* insere evento de entrada na cap *)
	PushEvt[i_, utente_] := Module[{e},
		e = evt[i, {True, utente}];
		cap = acr[cap, e];
		e
	];

	(* insere evento de saída na cap *)
	PopEvt[i_, utente_] := cap = acr[cap, evt[i, {False, utente}]];

	(* insere evento de novo utente na cap *)
	NewEvt[et_] := Module[{tick},
		tick = et + obsExp[tec];
		PushEvt[tick, NovoUtente[x, tick]]
	];

	Module[{bs, tSum, minU, maxU, nU, cData},
		(* inicialização *)

		bs = Append[Table[Balcao[2], {i, 2}], Balcao[1]];
		nU = tSum = 0;
		minU = {Infinity, "Ninguém"};
		maxU = {0, "Ninguém"};
		cData = Map[Table[{{0, "Ninguém"}, {}}, {i, NPrioridades[#]}] &, bs];

		(* geração e iteração da cap *)
		Module[{complete, e, et},
			e = NewEvt[0];
			et = inst[e];
			complete = et > t;
			cap = acr[nova, e];
			(* ciclo principal da simulação *)
			While[! complete,
				cap = retira[cap];
				Module[{ec, isPush, utente},
					ec = cat[e];
					isPush = ec[[1]];
					utente = ec[[2]];

					If[isPush,
						Module[{nb}, (* evento de ENTRADA *)

							(* acabou de chegar *)
							If[Entrou[utente] == et,
								(* Print["DEBUG: ",et,"m ",utente," entra na repartição."];*)
								NewEvt[et]
							];

							nb = ProximoBalcao[utente];

							If[! bs[[nb, 1]], (* LIVRE *)

								(* Print["DEBUG: ",et,"m A atender ",utente," em ", nb]; *)

								bs[[nb, 1]] = True;
								PopEvt[et + obsExp[tabc], AlteraUltimoBalcao[utente, nb]]
								,(* OCUPADO *)
								(* o utente espera pela sua vez *)
								Module[{np},
									np = ProximaPrioridade[utente];

									(* Print["DEBUG: ",et,"m A pôr ",utente," em espera em ", nb," p:", np]; *)

									cData[[nb, np, 2]] =
									 Append[cData[[nb, np, 2]], {et, True}];

									bs[[nb, 2, np]] = entra[bs[[nb, 2, np]], {
										BalcaoInicial[utente],
										UltimoBalcao[utente],
										Entrou[utente] ,
										et,
										Nome[utente]
									}];

								];
							];
						], Module[{b, idx}, (* evento de SAÍDA *)
							b = UltimoBalcao[utente];
							(* Print["DEBUG: ",et,"m ",utente, " foi atendido(a) em ",b,";"]; *)
							If[b == 3, (* se está a sair da tesouraria *)

								PushEvt[et + obsExp[tt], utente],
								(* senão, e se ainda não lá foi, talvez *)

								If[ProximaPrioridade[utente] == 1 && Random[] <= 1/y,
									(* vá para a secretaria *)
									PushEvt[et + obsExp[tt], utente],
									(* ou saia da repartição *)
									(* Print["DEBUG: ",et,"m ",utente," sai da repartição."]; *)
									Module[{dt},
										dt = et - Entrou[utente];
										If[dt < minU[[1]], minU = {dt, Nome[utente]}];
										If[dt > maxU[[1]], maxU = {dt, Nome[utente]}];
										tSum += dt;
										nU++;
									];
								];
							];

							(* pop de maior prioridade - ver seção "Balcões". *)
							idx = Length[bs[[b, 2]]];
							While[idx >= 1, Module[{f},
								f = bs[[b, 2, idx]];
								idx = If[filaVaziaQ[f],
								idx - 1,
								bs[[b, 1]] = True;
								bs[[b, 2, idx]] = sai[f];
								Module[{pUtente},
									pUtente = primeiro[f];
									(* Print["DEBUG: A atender proximo: ", pUtente]; *)
									cData[[b, idx, 2]] = Append[cData[[b, idx, 2]], {et, False}];
									Module[{dt},
										dt = et - EntrouBalcao[pUtente];
										If[dt > cData[[b, idx, 1, 1]], cData[[b, idx, 1]] = {dt, Nome[pUtente]}];
									];

									PopEvt[et + obsExp[tabc], AlteraUltimoBalcao[pUtente, b]];
								];
									-1
								];
							]];
							If[idx == 0, bs[[b, 1]] = False];
						];
					];
				];

				complete = Evaluate[cadeiaVaziaQ[cap] || (
					e = proximo[cap];
					et = inst[e];
					et > t
				)];

			];
		];
		(* 2ª fase da simulação *)
		Module[{np},
			np = 1;
			While[np <= Length[bs],
				Module[{nf},
					nf = 1;
					While[nf <= Length[bs[[np, 2]]],
						Module[{f},
							f = bs[[np, 2, nf]];
							If[! filaVaziaQ[f],
								Module[{utente, dt, dtt},
									utente = primeiro[f];
									dt = t - EntrouBalcao[utente];
									If[cData[[np, nf, 1, 1]] < dt, cData[[np, nf, 1]] = {dt, Nome[utente]}];
									dtt = t - Entrou[utente];
									If[maxU < dtt, maxU = {dtt, Nome[utente]}];
									If[minU > dtt, minU = {dtt, Nome[utente]}];
								]
							];
						];
						nf++;
					];
				];
				np++;
			];
		];
		
		(* 3ª e última fase: apresentar resultados *)

		(* Print[tSum," ", nU," ",minU," ", maxU," ",cData]; *)
		(* Print[cData[[1,1,2]]]; *)
		PlotL[l_, dt_] := Module[{i, nu},
			i = 1; nu = 0;
			While[i <= Length[l] && l[[i, 1]] <= dt,
				If[l[[i, 2]], nu++, nu--];
				i++
			];
			nu
		];

		(* Print[PlotL[cData[[1,1,2]],5]]; *)
		GetMaxL[l_] := Module[{i, nu, max},
			i = 1; max = nu = 0;
				While[i <= Length[l],
					If[l[[i, 2]], nu++, nu--];
					If[nu > max, max = nu];
					i++;
				];
			max
		];

		(* Print[GetMaxL[cData[[1,1,2]]]]; *)
		Module[{labels},
			labels = {"BALCÃO 1", "BALCÃO 2", "TESOURARIA"};
			PlotBalcao = Function[{bn},
				Print[labels[[bn]], ":"];
				Module[{b, maxes, fullmax},
					b = cData[[bn]];
					maxes = Map[GetMaxL[#[[2]]] &, b];
					fullmax = Module[{max, i},
						max = 0; i = 1;
						While[i <= Length[b],
							Module[{fmax},
								fmax = GetMaxL[b[[i, 2]]];
								If[maxes[[i]] > max, max = maxes[[i]]];
							];
							i++;
						];
						max
					];
					Print["Utentes em espera:"];
					Print[ (* Plot *)
						If[Length[b] > 1,
							Plot[
								Evaluate[Map[Hold[PlotL[#[[2]], z]] &, b]],
								{z, 0, t},
								PlotRange -> {0, fullmax},
								PlotLegends -> Table[StringJoin["fila", ToString[k]], {k, 1, Length[b]}],
								AxesLabel -> {"t", "n"},
								Evaluated -> True,
								WorkingPrecision -> t
							],
							Plot[
								PlotL[b[[1, 2]], z],
								{z, 0, t},
								PlotRange -> {0, fullmax},
								AxesLabel -> {"t", "n"},
								WorkingPrecision -> t
							]
						]
					];

					Module[{i},
						i = 1;
						While[i <= Length[b],
							Print["Fila ", i, " - maximo de pessoas à espera: ",  maxes[[i]], ", ", b[[i, 1, 2]],  " foi a pessoa que esperou mais (", b[[i, 1, 1]], "m)"];
							i++
						];
					];
				];
			];
			Map[PlotBalcao, Table[i, {i, 1, Length[cData]}]];
		];
		Print["DADOS GERAIS:"];
		Print["Tempo médio de permanência na repartição (sem contar com os utentes que não saíram): ", tSum/nU, "m" ];
		Print["O cliente que passou mais tempo na repartição foi ", maxU[[2]], " (", maxU[[1]], "m)"];
		Print["O cliente que passou menos tempo na repartição foi ", minU[[2]], " (", minU[[1]], "m)"];
	];
];
End[];
EndPackage[];

Conclusão

Conclusões há muitas, segundo conta a Históra, estão erradas a maioria das vezes. O que retiro deste projeto é, principalmente, mais experiência numa coisa que adoro fazer.

Tenho de admitir que uma implementação "correcta" deste projeto não é fácil, muito menos óbvia, há sempre maneiras melhores de resolver problemas, desde que se empenhe consideração suficiente. Dito isto, espero não ter cometido nenhum erro grave.

Submeto, desta forma, este meu processo de aprendizagem, e agora só me resta esperar pelo melhor.

Obrigado pela leitura.

DEBUG=1;
(*DEBUG=1; irá mostrar todos os eventos*)
(*DEBUG=2; imprime definições de funções geradas automáticamente*)

(*define.m*)

getMethodName[m_String|{m_String,___}]:=m;
getMethodName[m_Symbol|{m_Symbol,___}]:=SymbolName[m];
getMethodType[_String|_Symbol]="Expression";
getMethodType[{_,t_String}]:=t;
getMethodType[{_,t_Symbol}]:=SymbolName[t];

define[class_String, methods_List]:=Block[{
i=1,
n=Length[methods],
c=StringTake[class,1],
classExp=class<>"::usage=\""<>class<>"["},
Clear[class];

While[i<= n,Module[{
method=methods[[i]],is=ToString[i],
methodName,mn,methodType
},
methodName=getMethodName[method];
mn=StringTake[methodName,1];
methodType=getMethodType[method];

Clear[Evaluate[methodName]];

Block[{expStr=methodName<>"["<>c<>"_"<>class<>"]:="<>c<>"[["<>is<>"]];"},
If[DEBUG==2,Print[expStr]];
ToExpression[expStr];
];

Block[{expStr=methodName<>"["<>c<>"_"<>class<>",v_"<>methodType<>"]:=Block[{n"<>c<>"="<>c<>"},n"<>c<>"[["<>is<>"]]=v;n"<>c<>"];"},
If[DEBUG==2,Print[expStr]];
ToExpression[expStr];
];

Block[{expStr=methodName<>"::usage=\""<>methodName<>"["<>c<>"_"<>class<>"] obtêm "<>methodName<>" de "<>class<>" ("<>methodType<>").\n"<>methodName<>"["<>c<>"_"<>class<>", v_"<>methodType<>"] devolve clone de "<>class<>" com "<>methodName<>" -> v.\";"},
If[DEBUG==2,Print[expStr]];
ToExpression[expStr];
];
classExp=classExp<>methodName<>"_"<>methodType<>", ";
];
i++;
];

classExp=StringDrop[classExp,-2]<>"].\n Every parameter has getters and setters. Evaluate ?parameter.\";";
If[DEBUG==2,Print[classExp]];
ToExpression[classExp];
];

define[class_Symbol, methods_List]:=define[SymbolName[class],methods];

(*utentes.m*)

firstNames={"Noah","Emma", "Liam","Olivia","Mason","Sophia", "Amy","Jacob","Daniel", "David","William", "Harry", "Ethan", "Billy", "Mia", "Emily","Alexander","James","Abigail","Isabella","Michael","James","Benjamin","Logan","Evelyn","Aiden","Chloe","Jon", "Emily", "Randy", "Peter", "Zoey", "Lucas", "Samuel", "Joseph", "Anthony", "Andrew","Gabriel", "Victoria","Grace","Zoey","Natalie","Addison","Lillian","John", "Hannah", "Luke","Scarlett", "Isaac", "Oliver", "Samantha", "Henry", "Owen", "Claire", "Skylar", "Levi", "Jack", "Sarah","Aaron", "Ellie", "Thomas", "Lucy","Allexis", "Violet","Bella","Austin","Kevin","Martha", "Zack","Christopher"};

lastNames={"Bush", "Kirk","Li", "Smith","Brown","Roy","Wilson", "Leblanc","Williams", "Jones", "Moore", "Johnson", "Harris", "Parker", "Clark", "Gonzalez", "Barret", "Lewis", "Robinson", "Walker","Young", "Sanchez", "Scott", "King","Baker", "Adams", "Roberts", "Carter", "Green", "Stewart", "Flores", "Morris", "Murphy", "Cook", "Peterson", "Bell", "Reed", "Price", "Jenkins","Fisher", "Perry","Davis","Anderson","White","Jackson","Hall","Lopez","Simmons", "Butler","Griffin","Hayes","Ford","Wallace","Woods","West","Freeman","Webb","Black","Knight","Stone","Pierce","Fox","Lawrence","Sims","Chapman","Bishop","Lynch","Garret","Silva","Wayne","Hitchens"};

randomName:=StringJoin[RandomChoice[firstNames], " ", RandomChoice[lastNames]];

define[utente,{
{entrou,"?NumberQ"},
{bInicial,Integer},
{temReciboQ,"?BooleanQ"},
{bActual,Integer},
{bEntrou,"?NumberQ"},
{nome,String}}];

Clear[novoUtente];
novoUtente[x_,et_]:=With[{bi=If[Random[]<=1/x,1,2]},utente[et,bi,False,bi,et,randomName]];

Clear[esperando,proximoBalcao,aumentaBalcao,prioridadeQ];
esperando[u_utente, et_]:=bEntrou[u,et];
proximoBalcao[u_utente]:=If[bActual[u]==3,bInicial[u],3];
aumentaBalcao[u_utente]:=Module[
{proxb=proximoBalcao[u]},
bActual[If[temReciboQ[u],u,temReciboQ[u,proxb==3]],proxb]
];
prioridadeQ[u_utente]:=temReciboQ[u]&&bActual[u]!=3;

(*max.m*)
define[max,{{valor,"?NumberQ"},{mNome,String}}]

Clear[novoMax,calcula];
novoMax=max[0, "Ninguem"];
calcula[m_max,v_,u_utente]:=If[v>valor[m],max[v,nome[u]],m];
(*filas.m*)
define[fila,{
{utentes,List},
{maxN,Integer},
{fMaxT,max},
{linhas,":{___Line}"},
{let,"?NumberQ"}
}];

Clear[novaFila];
novaFila:= fila[{},0,novoMax, {},0,RandomReal[]];

Clear[proxLinhas];
proxLinhas[f_fila,et_,lnu_,nu_]:=Append[
If[lnu!=0,
Append[linhas[f],With[{lt=let[f]},
Line[{{lt,lnu},{et,lnu}}]]],
linhas[f]],
Line[{{et,lnu},{et,nu}}]
];

Clear[entra];
entra[f_fila,u_utente,et_]:=Module[{
lnu=Length[utentes[f]],nu,nf
},
nu=lnu+1;
let[
linhas[
maxN[
utentes[f,Append[utentes[f],u]],
With[{pnMax=maxN[f]},If[nu>pnMax,nu,pnMax]]],
proxLinhas[f,et,lnu,nu]],
et]];

Clear[primeiro];
primeiro[f_fila]:=First[utentes[f]];

Clear[sai];
sai[f_fila,pu_utente,et_]:=With[{lnu=Length[utentes[f]]},
let[
utentes[
fMaxT[
linhas[f,With[{nu=lnu-1},proxLinhas[f,et,lnu,nu]]],
calcula[fMaxT[f],et-bEntrou[pu],pu]],
Rest[utentes[f]]],
et]];

Clear[filaVaziaQ];
filaVaziaQ[f_fila/;utentes[f]=={}]=True;
filaVaziaQ[f_fila]=False;

(*balcoes.m*)
define[balcao,{
{ocupadoQ,"?BooleanQ"},
{filas,":{___fila}"},
{bMaxT,max}
}];

Clear[novoBalcao];
novoBalcao[nFilas_]:=balcao[False, Table[novaFila,nFilas],novoMax];

Clear[nFilas];
nFilas[b_]:=Length[filas[b]];

Clear[espera];
espera[b_balcao,u_utente,et_]:=
With[{np=If[prioridadeQ[u],2,1]},
Module[{fs=filas[b]},
filas[
ocupadoQ[b,True],
ReplacePart[fs, 
np->entra[fs[[np]],esperando[u,et],et]]]]];

Clear[serve];
serve[b_balcao]:=ocupadoQ[b,True];

(*eventos.m*)
define[evt,{
{inst,"?NumberQ"},
{isPushQ,"?BooleanQ"},
{eUtente,utente}
}];

(*cap.m*)

Clear[novaCap,acr,cadeiaVaziaQ];
(* needs eventos *)
novaCap={};
acr[cap_,e_]:=With[{
t=inst[e],
l=Length[cap]
},
Module[{idx=1},
While[idx<=l&&t>=inst[cap[[idx]]],idx++];
Insert[cap,e,idx]]];
cadeiaVaziaQ[{}]=True;
cadeiaVaziaQ[_List]=False;

(*sim.m*)

Clear[obsExp,sim];
(* needs utentes,max,filas,balcoes,eventos,cap *)
obsExp[m_] := -m*Log[Random[]];
sim[t_,tec_,x_,tabc_,y_,tt_]:=Module[
{cap=novaCap,pushEvt,popEvt,newEvt,start=AbsoluteTime[]},
pushEvt[et_,u_utente]:=Module[{e=evt[et,True,u]},cap=acr[cap,e];e];
popEvt[et_,u_utente]:=cap=acr[cap,evt[et,False,u]];
newEvt[et_]:=Module[{net=et+obsExp[tec]},pushEvt[net,novoUtente[x,net]]];
Module[{
bs=Append[Table[novoBalcao[2],2],novoBalcao[1]],
nU=0,tSum=0,maxU=novoMax
},
Module[{e=newEvt[0],et,complete},
complete=(et=inst[e])>t;

If[DEBUG==1,Print["A começar..."]];
While[!complete,
(*Print[isPush[e]," ",utente[e]];*)
cap=Rest[cap];
Module[{u=eUtente[e],bn,b},
bn=bActual[u];
b=bs[[bn]];
(*Print["BBB ",bn," ",b];*)
bs[[bn]]=If[
isPushQ[e],

If[entrou[u]==et,
If[DEBUG==1,Print[et,"m ",nome[u]," chega à repartição e dirige-se ao balcão ",bActual[u]]];
newEvt[et],
If[DEBUG==1,Print[et,"m ",nome[u]," dirige-se ao balcão ",bActual[u]]]];

If[!ocupadoQ[b],

popEvt[et+obsExp[tabc],u];
If[DEBUG==1,Print["É imediatamente atendido(a)."]];
serve[b],

If[DEBUG==1,Print["Aguarda pela sua vez."]];
If[prioridadeQ[u],If[DEBUG==1,Print["Como veio entregar o recibo, tem prioridade."]]];
espera[b,u,et]
],

If[DEBUG==1,Print[et,"m ",nome[u], " acaba de ser atendido(a) e sai do balcão ",bn,"."]];
(* se está a sair da tesouraria *)
If[bn==3,
If[DEBUG==1,Print["Dirige-se ao seu balcão inicial."]];
pushEvt[et+obsExp[tt],aumentaBalcao[u]],
(* senão, e se ainda não lá foi, talvez *)
If[!prioridadeQ[u]&&Random[]<1/y,
(* vá para a tesouraria *)
If[DEBUG==1,Print["Vai buscar recibo"]];
pushEvt[et+obsExp[tt],aumentaBalcao[u]],
(* ou saia da repartição *)
If[DEBUG==1,Print["Sai da repartição."]];
With[{dt=et-entrou[u]},
maxU=calcula[maxU,dt,u];
tSum+=dt;
nU++;
];
]];

Module[{fs=filas[b],idx},
idx=Length[fs];
Catch[
While[idx>=1,Module[{f=fs[[idx]]},
If[filaVaziaQ[f],
idx--,
Throw[Module[{pu=primeiro[f]},
If[DEBUG==1,Print[nome[pu], ", sendo o(a) seguinte na fila, é atendido(a)."]];
popEvt[et+obsExp[tabc],pu];
balcao[True,
ReplacePart[fs,idx->sai[f,pu,et]],
calcula[bMaxT[b],et-bEntrou[pu],pu]]]]]]];
ocupadoQ[b,False]]]
]];
complete=cadeiaVaziaQ[cap]||(e=First[cap];(et=inst[e])>t);
];
];
If[DEBUG==1,Print[t," - os balcões deixaram de atender."]];
Module[{nb=1},
nb=1;
While[nb<= Length[bs],
Module[{b=bs[[nb]],fs,c,nf=1},
fs=filas[b];
c=Length[fs];
While[nf<c,
Module[{f=fs[[nf]]},
If[!filaVaziaQ[f],
Module[{u=primeiro[f]},
bMaxT[b, calcula[bMaxT[b],t-entrou[u],u]];
calcula[fMaxT[f],t-bEntrou[u],u];
]]];
nf++];
bs[[nb]]=b;
];
nb++]];

Module[{
labels={"Balcão 1", "Balcão 2", "Tesouraria"},
filaPlotTransform={Orange, Purple},
bn=1},
Module[{
graphics={Opacity[0.5]},
cores={},
f=1,
fullmax=0
},
Print[Style[labels[[bn]],Bold]];

Module[{col=filaPlotTransform[[f]]},
graphics=Join[graphics,Prepend[linhas[#],col]];

Module[{maxn=maxN[#],maxt=fMaxT[#]},
If[maxn>fullmax,fullmax=maxn];
Print[Style["fila "<>ToString[f],col]," ",maxn,", ",mNome[maxt]," esperou ", valor[maxt],"m."];
];

f++]& /@filas[#];

Print[Show[Plot[-1,{i,0,t},PlotRange->{0,fullmax+0.5},AxesLabel->{"t","n"}],Graphics[graphics]]];

bn++;
]&/@bs];

Print["tempo médio na repartição: ",tSum/nU,"m" ];
Print["máximo de tempo: ", mNome[maxU]," - ",valor[maxU],"m"];
Print["o processamento da simulação levou ", AbsoluteTime[]-start,"s"];
];
];
sim[320,3,2,3,2,1]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment