• Nenhum resultado encontrado

4ª Lista de Exercícios de Paradigmas de Linguagens Computacionais Professor: Fernando Castor Monitores: Agay Borges (abn), Cleivson Siqueira (csa3), Dennis Silveira (dwas), Eduardo Rocha (ejfrf), Lívia Vilaça (lcjbv), Lucas Inojosa (licf), Luís Gabriel Li

N/A
N/A
Protected

Academic year: 2019

Share "4ª Lista de Exercícios de Paradigmas de Linguagens Computacionais Professor: Fernando Castor Monitores: Agay Borges (abn), Cleivson Siqueira (csa3), Dennis Silveira (dwas), Eduardo Rocha (ejfrf), Lívia Vilaça (lcjbv), Lucas Inojosa (licf), Luís Gabriel Li"

Copied!
6
0
0

Texto

(1)

4ª  Lista  de  Exercícios  de  Paradigmas  de  Linguagens  Computacionais     Professor:  Fernando  Castor    

Monitores:   Agay  Borges  (abn),   Cleivson  Siqueira  (csa3),  

Dennis  Silveira  (dwas),   Eduardo  Rocha  (ejfrf),  

Lívia  Vilaça  (lcjbv),   Lucas  Inojosa  (licf),   Luís  Gabriel  Lima  (lgnfl),  

Walter  Ferreira  (wflf),   Wellington  Oliveira  (woj)

CIn-­‐UFPE  –  2011.1     Disponível  desde:  09/06/2011    

Entrega:  28/06/2011    

A  lista  deverá  ser  respondida  em  dupla.  A  falha  em  entregar  a  lista  até  a  data  estipulada  implicará  na   perda  de  0,25  ponto  na  média  da  disciplina  para  os  membros  da  dupla.  Considera-­‐se  que  uma  lista   na  qual  menos  que  5  questões  foram  respondidas  corretamente  não  foi  entregue.  A  entrega  da  lista   com  pelo  menos  7  questões  corretamente  respondidas  implica  em  um  acréscimo  de  0,125  ponto  na   média   da   disciplina   para   os   membros   da   dupla.   Se  qualquer   situação   de   cópia   de   respostas   for   identificada,  os  membros  de  todas  as  duplas  envolvidas  perderão  0,5  ponto  na  média  da  disciplina.   O   mesmo   vale   para   respostas   obtidas   a   partir   da   Internet.   As   respostas   deverão   ser   entregues   exclusivamente  em  formato  texto  ASCII  (nada  de  .pdf,  .doc,  .docx  ou  .odt)  e  deverão  ser  enviadas   para  o  monitor  responsável  por  sua  dupla,  sem  cópia  para  o  professor.  Tanto  podem  ser  organizadas   em  arquivos  separados,  um  por  questão  (e,  neste  caso,  deverão  ser  zipadas),  quanto  em  um  único   arquivo  texto  onde  a  resposta  de  cada  questão  está  devidamente  identificada  e  é  auto-­‐contida  (uma   parte   da   resposta   de   uma   questão   que   seja   útil   para   responder   uma   outra   deve   estar   duplicada   neste  última).  As  questões  9  e  10  são  obrigatórias.    

 

1) Desenvolva  novamente  a  solução  da  multiplicação  paralela  da  última  lista  sem  a  utilização  do   operador   de   multiplicação,   agora   em   Haskell.   A   solução   utilizará   paralelismo   semi-­‐explícito.   Defina   uma  constante  'n'  que  definirá  o  número  de  tarefas  paralelas  a  serem  executadas  pela  multiplicação,   no   mesmo   estilo   da   questão   da   lista   anterior,   e   uma   função  multiplicar :: Integer -> Integer -> Integer.  

Provavelmente  a  sua  questão  utilizará  solução  recursiva  pura,  então  não  use  valores  tão  altos  para  o   fator  de  multiplicação  como  a  questão  da  lista  anterior,  com  risco  de  demorar  demais  e/ou  estourar   a  pilha.  

   

2) Crie   um   contador   em   Haskell   utilizando   variáveis   compartilhadas   (MVars).   No   main,   você   deverá  criar  um  número  2n  de  threads,  n  incrementando  e  n  decrementando.  Cada  thread  deverá   realizar   sua   tarefa   (++   ou   -­‐-­‐)   1000000/n   vezes.   Você   terá   de   analisar,   para   alguns   n's,   o   tempo   de   execução  e  custo  do  programa  e  explicar  o  que  acontece  quando  n  tende  a  um  número  grande,  e  o   porquê  destes  fatos.  

 

Por  exemplo,  para  n  =  1:  

  thread  1:  incrementador;  até  1000000;     thread  2:  decrementador;  até  1000000.    

(2)

  thread  1:  incrementador;  até  500000;     thread  2:  decrementador;  até  500000;     thread  3:  incrementador;  até  500000;     thread  4:  decrementador;  até  500000.    

 

3) Considere   a   implementação   abaixo   da   função  criar_arvore_completa,   que   recebe   um   inteiro  n  e  cria  uma  árvore  binária  completa  com  n  nós.  

 

data  Tree  t  =  EmptyTree  |  Node  t  (Tree  t)  (Tree  t)  deriving  (Show)    

nivel  ::  Float  -­‐>  Int  -­‐>  Int   nivel  0  _  =  0  

nivel  n  x  =  if  n  >=  2  then  nivel  (n  /  2)  (x  +  1)    else  x    

criar_arvore  ::  Int  -­‐>  Int  -­‐>  Int  -­‐>  Tree  Int   criar_arvore  n  _  0  =  EmptyTree  

criar_arvore  n  x  y  

|  x  >  n  =  EmptyTree  

|  otherwise  =  Node  x  (criar_arvore  n  (x*2)  (y  -­‐  1))  (criar_arvore  n  ((x*2)   +  1)  (y  -­‐  1))  

 

criar_arvore_completa  ::  Int  -­‐>  Tree  Int   criar_arvore_completa  0  =  EmptyTree  

criar_arvore_completa   n   =   Node   1   (criar_arvore   n   2   (nivel   (fromIntegral   n)   0))   (criar_arvore  n  3  (nivel  (fromIntegral  n)  0))  

 

Exemplo:  

*Main>  criar_arvore_completa  10  

Node  1  (Node  2  (Node  4  (Node  8  EmptyTree  EmptyTree)  (Node  9  EmptyTree  EmptyTree))   (Node   5   (Node   10   EmptyTree   EmptyTree)   EmptyTree))   (Node   3   (Node   6   EmptyTree   EmptyTree)  (Node  7  EmptyTree  EmptyTree))  

 

Considere  também  a  implementação  abaixo  que  calcula  um  balance  especial  para  uma  dada  raiz.  O   balance  é  calculado  da  seguinte  forma:  calcula-­‐se  o  valor  do  peso  dos  filhos  da  esquerda,  calcula-­‐se   o  valor  do  peso  dos  filhos  da  direita,  e  em  seguida  realiza-­‐se  a  subtração  do  peso  do  lado  esquerdo   com  o  peso  do  lado  direito.  O  peso  é  calculado  somando  o  valor  módulo  11311  de  todos  os  nós  da   árvore.  

balanceRaiz  ::  Tree  Int  -­‐>  Int     balanceRaiz  EmptyTree  =  0  

balanceRaiz  (Node  t  a  b)  =  (valorPeso  a)  -­‐  (valorPeso  b)      

valorPeso  ::  Tree  Int  -­‐>  Int   valorPeso  EmptyTree  =  0  

valorPeso  (Node  t  a  b)  =  mod  ((mod  t  11311)  +  (valorPeso  a)  +  (valorPeso  b))  11311  

Desenvolva,   usando   paralelismo   semi-­‐explícito,   uma   versão   paralela   e   mais   eficiente   da   implementação  acima.  Deve-­‐se  utilizar  como  parâmetro  para  a  função  balanceRaiz  árvores  geradas   pela  função  criar_arvore_completa  para  valores  muito  grandes  (N  =  10000).  

   

(3)

import  System.Random    

data  Tree  a  =  NilT  |  Node  a  (Tree  a)  (Tree  a)  deriving  (Eq,  Show)    

randomList  ::  Int  -­‐>  [Int]  

randomList  l  =  randoms  (-­‐l,  l)  (mkStdGen  l)    

criaTree  ::  Int  -­‐>  Tree  Int   criaTree  0  =  NilT  

criaTree  h  =  foldr  (fillTree)  NilT  (take  (2^h-­‐1)  (randomList  50))    

fillTree  ::  Int  -­‐>  Tree  Int  -­‐>  Tree  Int   fillTree  n  NilT  =  Node  n  NilT  NilT   fillTree  n  (Node  x  left  right)  

  |  (left  ==  NilT)  &&  (right  ==  NilT)    =  (Node  x  (fillTree  n  left)  right)     |  (left  ==  NilT)  &&  (right  /=  NilT)  =  (Node  x  (fillTree  n  left)  right)     |  (left  /=  NilT)  &&  (right  ==  NilT)  =  (Node  x  left  (fillTree  n  right))     |  (left  /=  NilT)  &&  (right  /=  NilT)  &&  ((qtdFilhos  left)  >=  ((qtdFilhos     right)))  =  (Node  x  left  (fillTree  n  right))  

  |  otherwise  =  (Node  x  (fillTree  n  left)  right)    

qtdFilhos  ::  Tree  Int  -­‐>  Int   qtdFilhos  NilT  =  0  

qtdFilhos  (Node  x  left  right)    

  |  (left  ==  NilT)  &&  (right  ==  NilT)  =  0  

  |  (left  /=  NilT)  &&  (right  ==  NilT)  =  qtdFilhos  left  +  1     |  (left  ==  NilT)  &&  (right  /=  NilT)  =  qtdFilhos  right  +  1     |  otherwise  =  qtdFilhos  right  +  qtdFilhos  left  +  2  

   

somaTree  ::  Tree  Int  -­‐>  Int   somaTree  (NilT)  =  0  

somaTree  (Node  x  left  right)  =  x  +  (somaTree  left)  +  (somaTree  right)    

somatorio  ::  Int  -­‐>  Int  

somatorio  h  =  somaTree  (criaTree  h)    

Crie  outras  soluções  para  este  problema  utilizando  as  seguintes  abordagens:    

A)  Uma  solução  com  um  número  fixo  de  threads  (à  sua  escolha,  aconselha-­‐se  duas  ou  quatro);   B)  Uma  solução  sem  um  número  fixo  de  threads;  

   

Qual   a   altura   máxima,   nas   condições   de   sua   máquina,   que   esta   árvore   pode   atingir   para   que   as   soluções  A  e  B  sejam  mais  eficientes  que  a  solução  sequencial?  Explique.  

Qual   a   altura   máxima,   nas   condições   de   sua   máquina,   que   esta   árvore   pode   atingir   para   que   a   solução  B  seja  mais  eficiente  que  a  solução  A?  Explique.  

   

5) Considere  a  seguinte  implementação:    

collatzFunction  ::  Integer  -­‐>  Integer   collatzFunction  n  

  |  (mod  n  2)  ==  1  =  (3*n)+1     |  otherwise  =  div  n  2    

collatzSequence  ::  Integer  -­‐>  [Integer]   collatzSequence  n  

(4)

   

Desenvolva   uma   solução   utilizando   variáveis   compartilhadas   em   Haskell   para   a   implementação   sequencial  acima.  O  usuário  deverá  estabelecer  o  valor  n  a  ser  calculado  e  o  número  de  threads  a   serem   utilizadas,   através   do   console.   Faça   um   comparação   dos   resultados   apresentados   por   essa   solução   considerando   o   tempo   de   execução   para   diferentes   quantidades   de   threads   (cujos   valores   não  sejam  muito  próximos  entre  si)  e  para  implementação  sequencial.  Determine  em  qual  situação  a   solução  foi  processada  da  maneira  mais  eficiente  e  explique  o  porquê.  Utilize  valores  muito  grandes   para  n  (preferencialmente  com  cerca  de  10  casas  decimais  ou  mais).  

   

6) Implemente   uma   solução   em   Haskell   para   uma   variação   do   problema   do   Produtor   e   do   Consumidor   onde   há   vários   (dois   ou   mais,   conforme   definido   pelo   usuário)   produtores   e   cada   um   tem   seu   próprio   buffer   (um   por   produtor).   Os   buffers   de   todos   os   produtores   têm   a   mesma   capacidade   (pelo   menos   10   posições).   Além   disso,   o   sistema   tem   vários   consumidores   (mesma   quantidade   que   a   de   produtores).   Cada   produtor   coloca   itens   produzidos   apenas   em   seu   buffer   particular  mas  consumidores  podem  consumir  itens  de  qualquer  buffer.  Fora  isso,  as  mesmas  regras   do   Produtor-­‐Consumidor   básico   valem:   consumidores   não   podem   consumir   de   um   buffer   vazio,   embora   possam   tentar   consumir   de   outro   buffer,   e   produtores   não   podem   produzir   além   da   capacidade   de   seus   buffers.   Além   disso,   o   sistema   nunca   pode   entrar   em   deadlock.   Implemente   a   solução  utilizando  memória  transacional  e  avalie  o  desempenho  da  sua  solução  considerando  que  os   produtores   produzem,   cada   um,   pelo   menos   1.000.000   de   itens   (e   não   esperam   para   cada   item   produzido,  antes  de  produzir  o  próximo).  

   

7) Considere  o  seguinte  jogo:  No  início  é  determinado  o  tempo  de  duração  do  mesmo,  vários   jogadores   tem   um   número   identificador   e   o   objetivo   deles   é   ter   o   maior   número   de   posições   do   tabuleiro  com  o  seu  número.  Ao  final  do  tempo  pré-­‐determinado,  o  jogo  deve  terminar  bem  como  a   execução  do  programa.  As  regras  são  as  seguintes:  

 

• O  tabuleiro  do  jogo  é  uma  lista  de  tamanho  n;  

• O   tabuleiro   é   inicialmente   preenchido   de   forma   que   todos   os   jogadores   tenham   uma   quantidade  igual  de  posições,  caso  isso  seja  impossível,  a  distribuição  de  posições  deve  ser  a   mais  homogênea  possível;  

• Um  jogador  só  poderá  modificar  as  posições  que  contém  o  número  identificador  de  um  dos   jogadores  adjacentes  a  ele;  (no  caso  do  primeiro  jogador,  os  adjacentes  a  ele  são  o  último  e   o  segundo,  no  caso  do  último  jogador,  o  penúltimo  e  o  primeiro)  

• Enquanto   um   jogador   estiver   preenchendo   uma   determinada   posição   da   lista   com   o   seu   número  outro  jogador  não  pode  fazer  o  mesmo;  

• Se   ao   tentar   preencher   uma   determinada   posição   ela   já   estiver   em   uso,   o   jogador   deve   procurar  a  próxima  posição  válida;  

• Cada  um  dos  jogadores  deve  operar  numa  Thread  independente;  

• O   número   de   jogadores,   o   tamanho   do   tabuleiro   e   o   tempo   de   execução   devem   ser   determinados  pelo  usuário;  

• No   final   da   execução   deve   ser   exibido   na   tela   o   estado   final   do   tabuleiro   bem   como   a   identificação  de  qual  dos  jogadores  conseguiu  marcar  mais  posições,  se  houver  um  empate   então  deve  ser  exibida  uma  lista  com  todos  os  jogadores  que  empataram.  

• Não  deve  haver  deadlock.    

Utilize  threads  explicitas  em  Haskell  e  variáveis  compartilhadas  para  a  resolução  do  problema.    

(5)

 

Considere  a  seguinte  lista  como  um  exemplo  de  tabuleiro  já  com  sua  distribuição  inicial.   1   4   2   3   5   3   2   1   5   4  

 

Quando  o  jogador  1  tentar  executar  uma  ação,  ele  só  poderá  modificar  os  números  do  jogador  5  e  2.   Alguns  exemplos  de  como  poderiam  ser  as  suas  jogadas:  

 

Caso  em  que  o  jogador  1  pega  todas  as  casas  dos  jogadores  5  e  2.   1   4   1   3   1   3   1   1   1   4    

Caso  em  que  o  jogador  3  pegou  a  3ª  casa  e  o  jogador  1  só  teve  tempo  para  pegar  a  5ª  casa.   1   4   3   3   1   3   2   1   5   4  

 

Caso   em   que   o   jogador   1   não   conseguiu   pegar   casa   alguma   uma   vez   que   todas   estavam   sendo   usadas  no  momento  em  que  ele  tentou.  

1   4   3   3   4   3   3   1   4   4    

Caso  em  que  o  jogador  1  pegou  9ª  casa  e  a  thread  dele  foi  trocada.   1   4   2   3   5   3   2   1   1   4    

 

8) Carlos  tem  uma  namorada,  Roberta,  e  está  pensando  em  começar  um  relacionamento  triplo   adicionando  Laura  ao  namoro.  Ele  sabe  que  namorar  é  uma  coisa  complicada,  ainda  mais  com  duas   mulheres,  mas  não  tem  noção  se  pode  dar  certo  ou  não.  Para  ajudá-­‐lo  (ou  prejudicá-­‐lo,  depende  do   ponto  de  vista)  faça  um  programa  que  simule  o  relacionamento,  seguindo  as  seguintes  regras:  

 

• Se  em  um  mês  as  duas  namoradas  se  veem  mais  do  que  veem  Carlos,  as  duas  brigam  entre   si;  

• Se  Carlos  passar  dois  meses  vendo  a  mesma  namorada,  a  outra  briga  com  ele;  

• Se   Carlos   passar   três   meses   vendo   a   mesma   namorada   ela   perdoa   uma   briga   (a   outra   continua  brigando  com  ele  -­‐-­‐  conta  como  uma  briga  a  mais);  

• Se  os  3  conseguirem  ultrapassar  os  10  anos  de  namoro  triplo,  o  namoro  não  acaba  mais;   • Se  alguma  das  namoradas  atingir  60  brigas  ela  acaba  o  namoro;  

• Se  Carlos  atingir  80  brigas  ele  acaba  o  namoro  com  as  duas;  

• O  programa  deve  imprimir  o  numero  de  brigas  de  cada  um  e  o  tempo  passado,  em  meses,   ao  final  da  sua  execução;  

• O  programa  deve  ser  em  Haskell  usando  apenas  memoria  transacional.  

   

9)    Considere  que  uma  galinha  pode  ser  modelada  por  4  Threads.  Cada  thread  representa  a   função  de  uma  parte  do  corpo.    

Os  olhos  procuram  por  comida  e  informam  ao  cérebro  sempre  que  alguma  comida  for  

detectada.  Uma  vez  que  a  comida  é  localizada,  o  cérebro  avisa  aos  pés  para  se  moverem  uma  certa   distância  com  o  objetivo  de  alcançar  a  comida.  Uma  vez  que  os  pés  tiverem  movido  a  distância   indicada,  eles  informam  ao  cérebro,  e  esse,  por  sua  vez,  avisa  a  boca  para  começar  a  comer.    

Essas  galinhas  não  tem  memória,  a  qualquer  momento  os  olhos  podem  identificar  outra   comida.  Isso  vai  acarretar  em:  o  cérebro  avisar  aos  pés  para  parar  qualquer  caminhada  e   a  boca  parar  de  comer,  os  pés  começarem  a  se  mover  em  direção  a  nova  comida,  e  em  seguida  

(6)

Escreva  um  programa  que  simula  o  comportamento  dessa  galinha.  As  threads  devem  se   comportar  como  descrito  abaixo:  

 

-­‐  Olhos:  procuram  por  comida  a  cada  tempo  t  ms  (t  está  entre  1000  e  2000),  achando  comida   50%  das  vezes.  Cada  vez  que  os  olhos  buscam  por  comida  a  string  "procurando..."  é  imprimida.  

-­‐  Cérebro:  recebe  sinal  dos  olhos  indicando  que  foi  encontrado  comida.  Uma  vez  informado,  a   string  "comida!"  é  impressa  e  em  seguida  informa  a  boca  para  parar  de  comer,  aos  pés  para  pararem   qualquer  movimentação  e  se  moverem  em  direção  à  nova  comida  encontrada.  Uma  vez  que  

os  pés  indicam  que  o  destino  foi  alcançado  a  string  "yes!"  é  impressa  e  a  boca  é  avisada  para  

começar  a  comilança.  

-­‐  Pés:  recebem  sinal  do  cérebro  indicando  nova  comida.  Uma  vez  avisados,  param  qualquer  

movimento  que  esteja  sendo  feito,  e  movem-­‐se  um  número  randômico  de  passos  (entre  5  e  10)  en   direção  à  nova  comida.  São  gastos  400ms  por  passo.  Depois  de  cada  passo  deve-­‐se  imprimir  a  string   "passo".  Uma  vez  que  uma  sequência  de  passos  tenha  sido  finalizada,  o  cérebro  deve  ser  avisado   que  o  destino  já  foi  alcaçado.  

-­‐  Boca:  aguarda  por  um  sinal  do  cérebro  para  começar  a  comer.  Fica  comendo  até  que  seja   informada  para  parar.  Cada  bocanhada  leva  400ms  e  para  cada  uma  deve  ser  impresso  uma  string   "comendo".  

 

A  simulação  só  deve  terminar  quando  o  usuário  matar  o  processo.  Sua  implementação  

deve  usar  apenas  memória  transacional  e  variáveis  transacionais.  É  

probido

 usar  MVars  e  a  

função  par.  

   

Referências

Documentos relacionados

A brincadeira termina quando alguém estver com seu tonel cheio, após isso imprima a quantdade de litros (ou mililitros) que cada pessoa conseguiu encher, de forma ordenada.

14) Seu Zé possui uma fábrica de chocolates muito boa, só que infelizmente, a demanda da fábrica é muito grande e ele não consegue supri-la. Sabendo disso, Seu Zé,

Sua utilização como aditivo mineral em materiais à base de cimento Portland é possível por conter em sua composição, grande porcentagem de sílica, a qual

Ademais, nota-se que o Brasil evoluiu nos últimos anos, aumentando a indignação com atos de corrupção e pessoas decididas a irem às ruas para manifestar, apesar de eventos como a

In the acute phase and in the chronic forms of Chagas disease, the etiological diagnosis may be performed by detection of the parasite using direct or indirect parasitological

Assim, para esclarecer os dados obtidos na tabela 1, é de fundamental importância a apreciação do contexto histórico de ocupação do Estado de Goiás de modo

Admitindo que a remoção de crómio total se faz, predominantemente, por adsorção com permuta iónica, do crómio(III) formado durante o processo de redução do

Os cálculos referentes ao volume de cada amostra adicionado à célula assim como o volume de padrão de As-(V) adicionado e as concentrações de As-(V) e As-(III) presentes