O servidor Anagram é um aplicativo baseado em Node.js que expõe uma API baseada em REST para realizar pesquisas relacionadas a um anagrama contra um dicionário de palavras. Sua característica principal é encontrar anagramas conhecidos para uma determinada palavra.
Além disso, os conjuntos de anagrama (grupos de palavras que são anagramas um do outro) podem ser consultados pela cardinalidade (número de palavras no conjunto) ou comprimento da palavra. Também é possível consultar se um determinado conjunto de palavras compreende um conjunto de anagrama.
O dicionário de palavras contra as quais os anagramas podem ser consultados podem ser adicionados, excluídos ou totalmente limpos pela API. Quando configurado como um serviço somente de memória (ou seja, alterações não sendo persistidas entre o serviço Reiniciar), o Anagram Server pré-carrega um conjunto padrão de palavras em inglês na inicialização (consulte app.js
).
Finalmente, várias estatísticas sobre o dicionário carregado podem ser consultadas através da API.
Instale o Node.js, se necessário
Instale as dependências da NPM
npm install
npm start
Por padrão, o aplicativo serve solicitações sobre a porta 3000. Para substituir isso, você pode atualizar o script inicial no package.json
para passar um número de porta alternativo para o comando Node. Por exemplo:
"start": "node src/app.js -p 8080"
Pode ser necessário permitir explicitamente o tráfego recebido na porta efetiva. Por exemplo, para abrir a porta 3000 no Linux até a próxima reinicialização:
sudo iptables -A INPUT -m state --state NEW -m tcp -p tcp --dport 3000 -j ACCEPT
Instale o Docker, se necessário
Construa a imagem do Docker
sudo docker build -t anagram-server .
sudo docker run -p 3000:3000 anagram-server
Você pode preferir mapear para uma porta de host alternativa (por exemplo, -p 8080:3000
).
O servidor anagrama é enviado com scripts de teste de rubi.
Observe que, por padrão, as Anagram Server pré -carrega as palavras do dictionary.txt
na inicialização.
Os scripts de teste estão na subpasta test
do pacote de origem e podem ser executados individualmente assim:
ruby anagram_test.rb
ruby anagram_test_2.rb
O servidor Anagrama pode ser testado manualmente com cURL
ou uma ferramenta como o Postman. Por exemplo (da linha de comando em uma nova janela do terminal do host do aplicativo):
curl -i "http://localhost:3000/anagrams/shout.json"
Como, por padrão, o servidor anagrama pré -carrega as palavras do dictionary.txt
na startup, convém limpar o dicionário com o seguinte comando antes de testar:
curl -i -X DELETE "http://localhost:3000/words.json"
Para testes remotos, substitua "localhost" pelo IP do host do aplicativo em anagram_client.rb
e nos comandos de amostra deste documento.
Atualize também o número da porta em anagram_client.rb
e nos comandos de amostra se executando o servidor anagrama com uma porta que não seja o padrão (3000).
Uma palavra é considerada válida se contiver qualquer combinação de maiúsculas e letras alfabetinhas em inglês minúsculas ou um hífen. Uma palavra válida pode não iniciar ou terminar com um hífen.
As tentativas de obter ou excluir palavras inválidas resultam em 400 Bad Request
.
Tentativas de publicar palavras inválidas resultam em 204 No Content
.
Um substantivo adequado é considerado qualquer palavra que tenha todas as letras minúsculas, exceto a primeira letra (que deve ser maiúscula) e a primeira letra após um hífen (que pode ser maiúsculo ou minúscula).
Alguns exemplos são: inglês, zulu, jean-christophe
Os substantivos adequados são considerados distintos de suas versões minúsculas. Por exemplo, Abigail e Abigail são duas palavras distintas (e anagramas um do outro).
Os substantivos apropriados são sempre incluídos nos resultados, a menos que explicitamente excluído (consulte excludeProperNouns
parm de GET /anagrams/:word.json
).
Por conveniência, o servidor anagrama permite a correspondência de substantivos adequados em relação às versões minúsculas em alguns casos. Por exemplo, ao consultar anagramas:
$ curl -i "http://localhost:3000/anagrams/aaru.json?includeInput=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "anagrams": [
"Aaru",
"aura"]
}
A arquitetura do servidor anagrama consiste em 4 camadas (do nível mais baixo ao mais alto):
Um adaptador é uma classe que fornece as operações básicas de consulta específica da loja, iteração e CRUD usadas pelo servidor anagrama. Especificamente, um adaptador fornece semântica para associar uma sequência de teclas a um conjunto de valores, adicionando e excluindo do conjunto de valores por chave, consultando para um conjunto por chave e pares de tecla/set.
O adaptador abstrava as especificidades do mecanismo de armazenamento subjacente da lógica de serviço, a fim de facilitar a troca de uma tecnologia de armazenamento por outro. Um valor dessa abstração é fornecer um caminho de atualização fácil à medida que surgem alternativas de armazenamento mais favoráveis e permitir opções de escalabilidade flexíveis.
Por exemplo, o serviço pode ser lançado inicialmente como um único servidor de aplicativos com um adaptador que envolve uma instância do MySQL no mesmo servidor. À medida que a escalabilidade, o failover e o desempenho precisam aumentar, podemos trocar o adaptador por um que envolve uma instância Redis que persiste e replica seus dados em vários servidores. As especificidades de como os dados são armazenados, armazenados em cache e/ou replicados são transparentes para o serviço anagrama.
O servidor anagrama é enviado com o MemoryAdapter ( adapters/MemoryAdapter.js
), que usa o mapa do JavaScript para armazenar e consultar dados. Esse adaptador possui um aplicativo limitado, pois não fornece o benefício da persistência nos reinicializações do servidor, mas serve como uma boa base para testar e exibir recursos do servidor anagrama.
O projeto define uma interface para implementar adaptadores nos adapters/adapter-template.js
. Este arquivo pode ser usado como caldeira na definição de novos adaptadores.
A interface do adaptador é baseada em promessas, pois as APIs para tecnologias de armazenamento tendem a ser assíncronas. Teoricamente, isso adiciona tempo de resposta, pois as promessas são resolvidas através da fila de eventos, mas esse efeito é insignificante dentro do escopo de uma solicitação de rede.
Transações
Os métodos add()
e delete()
do adaptador exigem que o armazenamento subjacente suporte transações, uma vez que sua lógica envolve a consulta dos dados e depois operando na loja com base nos resultados da consulta.
Resultados da clonagem
MemoryAdapter get()
e each()
métodos retornam matrizes de valor do mapa diretamente para o anagramerservice. Isso requer diligência em nome do Código de Serviços de Anagrama para evitar mutação acidental dos resultados fornecidos por esses métodos.
A clonagem dos resultados no MemoryAdapter antes de devolvê -los seria uma etapa sábia para mitigar futuros bugs, garantir a consistência da interface e proporcionar menos espanto aos consumidores, mas também envolve uma sobrecarga adicional (embora provavelmente insignificante).
O AnagramService é uma classe que fornece a lógica de negócios para o servidor anagrama. Requer que uma instância de um adaptador seja passada para o seu construtor.
A classe AnagramService mantém a palavra e o anagrama conta e implementa métodos que suportam diretamente a API REST.
Esta classe vive em AnagramService.js
.
server.js
Exporta um único startServer()
que cria o servidor REST (via Restify) e instancia o AnagramerService.
startServer()
requer uma instância do adaptador e, opcionalmente, aceita um número de porta do qual as solicitações de serviço e um caminho opcional para um arquivo de texto para preparar o dicionário.
A carne do server.js
é o conjunto de funções de resposta ao servidor que analisam solicitações HTTP individuais, ligue para os métodos relevantes do Anagramervice e emitem respostas com os códigos de embalagem de objetos apropriados e http.
app.js
é o ponto de entrada para o servidor anagrama. É um arquivo simples que especifica o adaptador para executar o serviço e uma fonte de pré -carga de dados opcional.
Este é o único arquivo que precisa mudar ao trocar um adaptador por outro.
A versão atual do app.js
é executada no Anagramerver com o MemoryAdapter e dictionary.txt
.
Abaixo estão algumas idéias para desenvolver um servidor anagrama.
GET /anagrams/:word.json
Retorne uma variedade JSON de palavras que são anagramas da palavra passada no URL.
Se a própria palavra passada não for uma palavra conhecida (ou seja, não no dicionário), uma matriz vazia será retornada (mesmo que os anagramas conhecidos possam ser formados a partir da palavra passada).
Por conveniência, uma palavra passada como minúscula corresponderá ao seu formulário de substantivo adequado.
Exemplo:
$ curl -i "http://localhost:3000/anagrams/care.json"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "anagrams": [
"Acer",
"acre",
"crea",
"race"]
}
GET /anagrams/:word.json?limit=<integer>
Retorne uma variedade JSON de palavras que são anagramas da palavra passada no URL, mas limitam o número de resultados retornados .
Exemplo:
$ curl -i "http://localhost:3000/anagrams/care.json?limit=2"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "anagrams": [
"Acer",
"acre"]
}
GET /anagrams/:word.json?includeInput=true
Retorne uma variedade JSON de palavras que são anagramas da palavra passada no URL, incluindo a própria palavra de entrada .
A palavra de entrada não é normalmente incluída nos resultados do Anagrama, pois uma palavra não é convencionalmente considerada um anagrama por si só.
$ curl -i "http://localhost:3000/anagrams/care.json?includeInput=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "anagrams": [
"Acer",
"acre",
"care",
"crea",
"race"]
}
GET /anagrams/:word.json?excludeProperNouns=true
Retorne uma variedade JSON de palavras que são anagramas da palavra passadas no URL, omitindo substantivos adequados .
Os substantivos adequados são normalmente incluídos nos resultados do anagrama.
$ curl -i "http://localhost:3000/anagrams/care.json?limit=2&excludeProperNouns=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "anagrams": [
"acre",
"crea"]
}
GET /anagrams?cardinalityMin=<integer>&cardinalityMax=<integer>
Retorne todos os conjuntos de anagrama que possuem uma cardinalidade mínima e/ou máxima (número de anagramas no conjunto).
CardinalityMin ou CardinalityMax podem ser omitidos.
Exemplos:
$ curl -i "http://localhost:3000/anagrams?cardinalityMin=3&cardinalityMax=4"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"anagramsByCardinality": {
"cardinalityMin": 3,
"cardinalityMax": 4,
"anagrams": [
["Aaronic", "Nicarao", "ocarina"],
["abater", "artabe", "eartab", "trabea"],
["Abe", "bae", "Bea"],
...
]
}
}
# Return all words that have anagrams
$ curl -i "http://localhost:3000/anagrams?cardinalityMin=2"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"anagramsByCardinality": {
"cardinalityMin": 2,
"anagrams": [
["A", "a"],
["aal", "ala"],
["aam", "ama"],
...
]
}
}
GET /anagrams?lengthMin=<integer>&lengthMax=<integer>
Retorne todos os conjuntos de anagrama que possuem um comprimento mínimo e/ou máximo de palavras.
Comprimento ou comprimento pode ser omitido.
Exemplo:
$ curl -i "http://localhost:3000/anagrams?lengthMin=10&lengthMax=11"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"anagramsByLength": {
"lengthMin": 10,
"lengthMax": 11,
"anagrams": [
["ablastemic", "masticable"],
["aborticide", "bacterioid"],
["acalyptrate", "Calyptratae"],
...
]
}
}
GET /anagrams?maxCardinality=true
Retorne todos os conjuntos de anagrama com a cardinalidade máxima.
Exemplo:
$ curl -i "http://localhost:3000/anagrams?maxCardinality=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"maxCardinalityAnagrams": {
"maxCardinality": 11,
"anagrams": [
["angor", "argon", "goran", "grano", "groan", "nagor", "Orang", "orang", "organ", "rogan", "Ronga"]
]
}
}
GET /anagrams?maxLength=true
Retorne todos os conjuntos de anagrama com o comprimento máximo da palavra.
Exemplo:
$ curl -i "http://localhost:3000/anagrams?maxLength=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"maxLengthAnagrams": {
"maxLength": 22,
"anagrams": [
["cholecystoduodenostomy", "duodenocholecystostomy"],
["hydropneumopericardium", "pneumohydropericardium"]
]
}
}
GET /anagrams?areAnagrams=<comma-delimited list of words>
Determine se um conjunto de palavras são anagramas um do outro.
Todas as palavras passadas devem ser conhecidas (ou seja, no dicionário) para que isso seja verdadeiro.
Exemplo:
$ curl -i "http://localhost:3000/anagrams?areAnagrams=acer,acre,race"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"anagramAffinity": {
"areAnagrams": true,
"words": ["acer", "acre", "race"]
}
}
GET /anagrams?count=true
Retorne apenas o anagrama conta. Cada conjunto de anagramas no dicionário adiciona N-1 a essa contagem, onde n é o número de anagramas no conjunto.
Exemplo:
$ curl -i "http://localhost:3000/anagrams?count=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "counts": { "anagram": 20043 }}
GET /words?count=true
Retornar o número de palavras no dicionário.
Exemplo:
$ curl -i "http://localhost:3000/words?count=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{ "counts": { "word": 235886 }}
GET /words?stats=true
Retorne algumas estatísticas sobre as palavras no dicionário.
Exemplo:
$ curl -i "http://localhost:3000/words?stats=true"
HTTP/1.1 200 OK
Content-Type: application/json
...
{
"stats": {
"wordCount": 235886,
"anagramCount": 20043,
"minWordLength": 1,
"maxWordLength": 24,
"medianWordLength": 4,
"averageWordLength": 9.569126612007494,
"minCardinality": 2,
"maxCardinality": 11,
"medianCardinality": 2,
"averageCardinality": 2.3111140184470464
}
}
POST /words.json
Pega uma variedade de palavras JSON e as adiciona ao dicionário.
Exemplo:
$ curl -i -X POST -d '{ "words": ["Canadas", "acandas", "Smurfs", "care"] }' "http://localhost:3000/words.json"
HTTP/1.1 201 Created
Content-Type: application/json
...
{
"counts": {
"word": 3,
"anagram": 1
},
"words": ["/anagrams/Canadas", "/anagrams/acandas", "/anagrams/Smurfs"]
}
DELETE /words/:word.json
Exclua uma única palavra do dicionário.
Se a própria palavra passada não for uma palavra conhecida (ou seja, não no dicionário), um 404
será devolvido.
Exemplo:
$ curl -i -X DELETE "http://localhost:3000/words/care.json"
HTTP/1.1 204 No Content
...
DELETE /words/:word.json?includeAnagrams=true
Exclua uma única palavra e todos os seus anagramas do dicionário.
Se a própria palavra passada não é uma palavra conhecida (ou seja, não no dicionário), nada é excluído e um 404
é devolvido.
Exemplo:
$ curl -i -X DELETE "http://localhost:3000/words/acre.json?includeAnagrams=true"
HTTP/1.1 204 No Content
...
DELETE /words.json
Limpe todo o conteúdo do dicionário.
Exemplo:
$ curl -i -X DELETE "http://localhost:3000/words.json"
HTTP/1.1 204 No Content
...