Encontrar uma combinação de dois elementos que não foram vistos juntos (LINQ, SQL ou C #)
-
03-07-2019 - |
Pergunta
Eu tenho uma página que exibe dois objetos e, em seguida, o usuário escolhe um deles. Eu gravo a preferência ea combinação em um banco de dados MSSQL e acabam armazenando dados como este:
UserId=1, BetterObjectId=1, WorseObjectId=2
Agora eu gostaria de evitar mostrando que a combinação dos objetos (1,2 / 2,1) nunca mais.
Então, como faço para gerar combinações aleatórias para mostrar ao usuário excluindo combinações previamente vistas?
Isto parece que deve ser uma pergunta muito simples, mas como a maioria dos programadores que eu sou curto no sono e café para a sua ajuda é muito apreciada: -)
O próprio abordagem ingênuo é algo como isto (e todas as chamadas para esta função teria que ser envolto em uma verificação para ver se o usuário já tenha avaliado como muitas vezes como nCr onde n é o contagem item e r é 2):
public List<Item> GetTwoRandomItems(int userId)
{
Item i = null, i2 = null;
List<Item> r = null;
while (i == null || i2 == null)
{
r = GetTwoRandomItemsRaw();
i = r[0];
i2 = r[1];
if (GetRating(i.Id, i2.Id, userId) != null) /* Checks if viewed */
{
i = null;
i2 = null;
}
}
return r;
}
private List<Item> GetTwoRandomItemsRaw()
{
return Items.ToList().OrderBy(i => Guid.NewGuid()).Take(2).ToList();
}
Edições
Usando algumas SQL I pode gerar uma lista de todos os itens que não são completo (ou seja, há uma combinação envolvendo o item que o usuário não tenha visto), mas eu não acho que é particularmente útil.
Eu também posso imaginar gerar todas as combinações possíveis e eliminando os já vistos antes de escolher 2 itens aleatórios, mas esta é uma outra solução terrível.
A possibilidade (memória intensivo para grande n) é gerar todas as combinações possíveis e armazenar o combinationId na classificação. Então eu posso apenas fazer um SELECT de todas as combinações ONDE combinationId NÃO ESTÁ EM (SELECT combinationId DE classificações ONDE userId = x), com algumas alterações para refletir a relação simétrica de combinações.
Solução
Table Item: ItemId
Table Rating: UserId, ItemId1, ItemId2, WinnerId
Se você precisar que ItemId1 Aqui está um blogue sobre a obtenção de "random" traduzido para o banco de dados. var pair = db.Items.Join(db.Items,
i1 => i1.ItemId,
i2 => i2.ItemId,
(i1, i2) => new {i1, i2}
) //produce all pairs
.Where(x => x.i1.ItemId < x.i2.ItemId) //filter diagonal to unique pairs
.Where(x =>
!db.Ratings
.Where(r => r.UserId == userId
&& r.ItemId1 == x.i1.ItemId
&& r.ItemId2 == x.i2.ItemId)
.Any() //not any ratings for this user and pair
)
.OrderBy(x => db.GetNewId()) //in-database random ordering
.First(); // just give me the first one
return new List<Item>() {pair.i1, pair.i2 };
Outras dicas
Uma solução é a seguinte:
SELECT TOP 1 i.id item1, i2.id item2 from item i, item i2
WHERE i.id <> i2.id
AND (SELECT COUNT(*) FROM Rating WHERE userId=@userId AND FK_ItemBetter=i.id AND FK_ItemWorse=i2.id) = 0
AND (SELECT COUNT(*) FROM Rating WHERE userId=@userId AND FK_ItemBetter=i2.id AND FK_ItemWorse=i.id) = 0
ORDER BY NEWID()
Eu não estava ciente do < em> junção cruzada método de apenas de listagem múltipla de tabelas antes.
Assumindo que a lista de itens disponíveis é no banco de dados, eu iria lidar com este problema inteiramente no banco de dados. Você está batendo no banco de dados já, não importa o quê, então por que não fazê-lo lá?
O que sobre a colocação de todos os objetos em uma fila ou uma pilha, e em seguida, pop 2 e 2 fora até que eles estão vazios?