2009-12-08 51 views
3

我有一个方法从数据库中返回一个通用列表集合(List)。此集合已获得订单详细信息,即订单ID,订单名称,产品详细信息等。从集合中随机返回项目

此外,该方法返回的集合仅包含按订单日期降序排序的前5个订单。

我的要求是每次客户端调用这个方法时,我需要返回有5个随机顺序的集合。

如何使用C#实现此目的?

回答

11

我在使用Fisher-Yates shuffle回到它做到这一点写了TakeRandom扩展方法。这非常有效,因为它只是麻烦随机确定您实际想要返回的项目数量,并且保证没有偏见。

public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> source, int count) 
{ 
    var array = source.ToArray(); 
    return ShuffleInternal(array, Math.Min(count, array.Length)).Take(count); 
} 

private static IEnumerable<T> ShuffleInternal<T>(T[] array, int count) 
{ 
    for (var n = 0; n < count; n++) 
    { 
     var k = ThreadSafeRandom.Next(n, array.Length); 
     var temp = array[n]; 
     array[n] = array[k]; 
     array[k] = temp; 
    } 

    return array; 
} 

ThreadSafeRandom的实现可以是found at the PFX team blog

+0

+1:这是一个相当完整的答案:D – 2009-12-08 12:17:22

+2

虽然它似乎在复制列表(效率?)或修改现有的(从签名中不太明显)。 – 2009-12-08 12:20:02

+0

@Vilk:正如我的废话答案所证明的那样,要避免有效地重复是很不容易的,所以我怀疑它很难提高效率。 – 2009-12-08 12:38:14

1
return collection.Where(()=>Random.Next(100) > (5/collection.Count * 100))); 
+0

与矿,你可以得到dups(也许不会得到五个) – 2009-12-08 12:17:54

2
return myList.OfType<Order>().OrderBy(o => Guid.NewGuid()).Take(5); 
+0

这'访问'阵列的每个项目(是的,我知道你知道) – 2009-12-08 12:09:20

+0

是的。我不明白原始问题中* generics *要求的作用。如果我们只是有一个订单列表,我们可以放弃'OfType',而上面的查询对于linq到sql表格的列表也同样适用。如果它是一个sql表的linq,那么'OrderBy'子句实际上会在数据库级别解析为'newid()'随机化的顺序,这是完全可取的(正如你指出的那样) – 2009-12-08 12:23:37

+0

@David:我认为asker意味着列表因此OfType是无关紧要的。问题是NewGuid效率低于Random()(请记住,*每个Guid都是神圣的,每个Guid都很棒*。没有实现默认的L2S [大概EF和LLBLGP等]默认的OrderBy翻译 - 这使得这个worty成为+1 (你应该说,在帖子中,你不应该:D) – 2009-12-08 12:40:29

4

你真的应该在数据库中做到这一点 - 没有意义只返回一大堆东西,只能放下所有五个。你应该修改你的问题来解释什么类型的数据访问栈被涉及,这样人们可以给出更好的答案。例如,你可以做一个ORDER BY RAND():

SELECT TOP 5 ... FROM orders 
ORDER BY RAND() 

但是那visits every row, which you don't want。如果您正在使用SQL Server [并且希望与其绑定:P],则可以使用TABLESAMPLE

If you're using LINQ to SQL, go here

编辑:这里只是假装这个心不是的剩下的 - 它的效率不高,因此Greg的答复是更为可取的,如果你想客户端进行排序。

但是,对于完整性,以下内容粘贴到LINQPad

var orders = new[] { "a", "b", "c", "d", "e", "f" }; 
var random = new Random(); 
var result = Enumerable.Range(1,5).Select(i=>orders[random.Next(5)]) 
result.Dump(); 

编辑:蛮力回答Greg的点(是的,效率不高,或者漂亮)

var orders = new[] { "a", "b", "c", "d", "e", "f" }; 

var random = new Random(); 

int countToTake = 5; 

var taken = new List<int>(countToTake); 

var result = Enumerable.Range(1,countToTake) 
    .Select(i=>{ 
     int itemToTake; 
     do { 
      itemToTake = random.Next(orders.Length); 
     } while (taken.Contains(itemToTake)); 
     taken.Add(itemToTake); 
     return orders[itemToTake]; 
    }); 

result.Dump(); 
+0

虽然这将返回重复,这可能不是他所追求的。 – 2009-12-08 12:13:16

+0

@Greg Beech:好点,会解决(真正的问题是他的排序需要在数据库中发生) – 2009-12-08 12:15:04

+1

投票。绝对应该在数据库上做到这一点。 – Firestrand 2009-12-08 13:40:41