Найдите комбинацию двух элементов, которые не просматривались вместе (LINQ, SQL или C#).
-
03-07-2019 - |
Вопрос
У меня есть страница, на которой отображаются два объекта, а затем пользователь выбирает один из них.Я записываю предпочтение и комбинацию в базу данных MSSQL и в конечном итоге сохраняю данные следующим образом:
UserId=1, BetterObjectId=1, WorseObjectId=2
Теперь я бы хотел больше никогда не показывать эту комбинацию объектов (1,2/2,1).
Итак, как мне генерировать случайные комбинации, чтобы показать пользователю исключение ранее просмотренных комбинаций?
Кажется, это должен быть действительно простой вопрос, но, как и большинству программистов, мне не хватает сна и кофе, поэтому я очень ценю вашу помощь :-)
А очень наивный подход выглядит примерно так (и все вызовы этой функции должны быть обернуты проверкой, чтобы увидеть, поставил ли пользователь уже оценку столько раз, сколько nCr, где n — количество элементов, а 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();
}
Правки
Используя некоторый SQL, я могу создать список всех незавершенных элементов (т.есть комбинация, включающая элемент, который пользователь не видел), но я не думаю, что она особенно полезна.
Я также могу представить себе создание всех возможных комбинаций и удаление уже просмотренных, прежде чем выбрать 2 случайных предмета, но это еще одно ужасное решение.
Есть возможность (интенсивная память для больших n) — сгенерировать все возможные комбинации и сохранить в рейтинге идентификатор комбинации.Затем я могу просто выполнить ВЫБОР всех комбинаций, ГДЕ комбинацияId НЕТ (ВЫБРАТЬ идентификатор комбинации из рейтингов, ГДЕ userId=x) с некоторыми изменениями, чтобы отразить симметричное соотношение комбинаций.
Решение
Table Item: ItemId
Table Rating: UserId, ItemId1, ItemId2, WinnerId
Если вам требуется, чтобы ItemId1 < ItemId2 в таблице рейтингов, вам нужно проверить таблицу рейтингов только один раз.
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 };
Вот блог о получении «случайного» перевода в базу данных.
Другие советы
Одно из решений заключается в следующем:
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()
я не знал о перекрестное соединение метод простого перечисления нескольких таблиц FROM раньше.
Предполагая, что список доступных элементов находится в базе данных, я бы полностью решил эту проблему в базе данных.Вы уже обращаетесь к базе данных, несмотря ни на что, так почему бы не сделать это там?
А как насчет помещения всех объектов в очередь или стек, а затем извлечения 2 и 2, пока они не станут пустыми?