Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq2sql: efficient way to get random elements with weight?

Byt lets say I have an integer weight where i.e. elements with weight 10 has 10 times higher probability to be selected than element with weight 1.

var ws = db.WorkTypes
.Where(e => e.HumanId != null && e.SeoPriority != 0)
.OrderBy(e => /*????*/ * e.SeoPriority)
.Select(e => new
{
   DescriptionText = e.DescriptionText,
   HumanId = e.HumanId
})
.Take(take).ToArray();

How do I solved getting random records in Linq when I need the result to be weighted?

I need somthing like Random Weighted Choice in T-SQL but in linq and not only getting one record?

If I wouldn't have the weighted requirement, I'd use the NEWID approach, can I adopt this some way?

partial class DataContext
{
    [Function(Name = "NEWID", IsComposable = true)]
    public Guid Random()
    {
        throw new NotImplementedException();
    }
}

...

var ws = db.WorkTypes
.Where(e => e.HumanId != null && e.SeoPriority != 0)
.OrderBy(e => db.Random())
.Select(e => new
{
   DescriptionText = e.DescriptionText,
   HumanId = e.HumanId
})
.Take(take).ToArray();
like image 515
Niels Bosma Avatar asked Jan 23 '23 23:01

Niels Bosma


1 Answers

My first idea was the same as Ron Klein's - create a weighted list and select randomly from that.

Here's a LINQ extension method to create the weighted list from the normal list, given a lambda function that knows the weight property of the object.

Don't worry if you don't get all the generics stuff right away... The usage below should make it clearer:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public class Item
    {
        public int Weight { get; set; }
        public string Name { get; set; }
    }

    public static class Extensions
    {
        public static IEnumerable<T> Weighted<T>(this IEnumerable<T> list, Func<T, int> weight)
        {
            foreach (T t in list)
                for (int i = 0; i < weight(t); i++)
                    yield return t;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<Item> list = new List<Item>();
            list.Add(new Item { Name = "one", Weight = 5 });
            list.Add(new Item { Name = "two", Weight = 1 });

            Random rand = new Random(0);

            list = list.Weighted<Item>(x => x.Weight).ToList();

            for (int i = 0; i < 20; i++)
            {
                int index = rand.Next(list.Count());
                Console.WriteLine(list.ElementAt(index).Name);
            }

            Console.ReadLine();
        }
    }
}

As you can see from the output, the results are both random and weighted as you require.

like image 164
ifatree Avatar answered Jan 25 '23 13:01

ifatree