Random Orderby on subquery in LINQ - c#

I have three entity framework objects, Quiz, Question, and Option. A Quiz has a collection of Question objects, and a Question has a collection of Option objects. I would like to return from the DBContext a randomized list of questions for a specified quiz and each question should include a randomly sorted collection of associated Options.
So far, I have been able to get the random list of questions out successfully, but I am having trouble randomizing the options for the question.
Note: I have a couple of different shuffling extension methods I have written, in this example I am using ordering by a Guid for sake of simplicity.
var questions = db.Questions.Where(q => q.QuizId == quizId).Include(q => q.Options).OrderBy(a => Guid.NewGuid());
How can I randomly shuffle the Options?

I'm not sure LINQ-to-SQL will support conversion of such a specific feature. In your boots, if I had access to the database, I'd create a stored procedure like described here to fetch random rows on the database level. Though, this is also a not-optimal solution (see accepted answer to this question)
You may go for the following approach (assuming you have less than MAXINT questions):
Random random;
var c = Questions.Count();
var randomNumbers = new List<int>();
var fetchedQuestions = new List<Question>();
var randomNumber = random.Next(1,c);
for (int i = 0; i < maxQuestions; i++) {
while (randomNumbers.Contains(randomNumber))
randomNumber = random.Next(1,c);
randomNumbers.Add(randomNumber);
fetchedQuestions.Add(Questions.ElementAt(randomNumber));
}
return fetchedQuestions;
I.e. just generate some random unique question row numbers and fetch corresponding rows.
WARNING! Optimization needed before using - this is a dirty code prototype.

Related

An improved way to retrieve values from Dictionary C#

I currently have a dictionary with an int key and the value is an instance of a class called MachinePart.
The key is significant to the what type of machine part the MachinePart is. For example, if the key is between 0-99 the machine part is in a category called "Free Movement". If the key is between 100-199,the machine part is in another category and so on...
Therefore it is useful to have a method which will retrieve a certain category from the dictionary. To clarify to return a list of Machine Parts who keys are within a certain range.
Below is the code I currently have to retrieve the free movement parts. It works fine however I was wondering if there was a more improved way of writing this instead of having to have a loop which iterates 99 times.
public static List<MachinePart> getFreeMovementParts(Dictionary<int, MachinePart> iMachineParts)
{
List<MachinePart> temp = new List<MachinePart>();
for (int i = 0; i < 99; i++)
{
MachinePart t;
if (iMachineParts.TryGetValue(i, out t))
{
temp.Add(t);
}
}
return temp;
}
You could use Linq to select the values as follows:
var freeMovementParts = iMachineParts.Where(it => it.Key >= 0 && it.Key <= 99)
.Select(it => it.Value)
.ToList();
But as suggested in the comments, it's better to think of an alternative data structure for the implementation. Also it is worth noting that iterating over the keys will lead to poor performance if the dictionary contains large number of items and you will lose the perf benefits of using the dictionary.
If you'd like to Linq-ify your code you can do something like this:
public static List<MachinePart> getFreeMovementParts(Dictionary<int, MachinePart> iMachineParts)
{
return Enumerable.Range(0, 99)
.Select(i => { iMachineParts.TryGetValue(i, out var mp); return mp; })
.Where(mp => mp != null)
.ToList();
}
It does not solve problems mentioned in the comments, a different data structure might still be more appropriate, the above is mostly just for fun.
Performance of this approach vs performance of the "enumerate all keys" approach - it is similar to DB indexes, on a small data set it is often cheaper to do a full scan (enumerate all keys). But when you need only a small subset of items from a large dictionary - it will be cheaper to do the item lookups based on known range of keys.
var r = from part in iMachineParts
where part.Key >= 0 && part.Key <= 99
select part.Value;
return r.ToList();

Picking random record from Entity Framework database without OrderBy

I try to get random record from database:
personToCall = db.Persons.Skip(toSkip).Take(1).First();
but I get exception which tells me:
{"The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'."}
Can I do it without OrderBy? Sorting data structure (O(nlogn)) to pick random element(which should be constant) doesn't look wise.
EDIT: I use Entity Framework 6.1.1.
You can have something like :
personToCall = db.Persons.OrderBy(r => Guid.NewGuid()).Skip(toSkip).Take(1).First();
You should use FirstOrDefault to be mode defensive.
Here the dark lord teaching the force to yoda! what is the world coming to!
First you need to get the random number from 1 to max record, see this
Random rand = new Random();
int toSkip = rand.Next(0, db.Persons.Count());
db.Persons.Skip(toSkip).Take(1).First();
with order by you can use the Guid.NewGuid()
db.Persons.OrderBy(x=>x.Guid.NewGuid()).Skip(toSkip).Take(1).FirstOrDefault();
There is no way to do this without an ordering clause.
personToCall = db.Persons.OrderBy(r => Random.Next()).First();
That could be slow depending on the size of your Persons table, so if you wanted to make this fast, you'd have to add a column to Person, or join it to a dictionary of random numbers and Person keys, then order by that. But that is rarely a wise solution.
Better to ask a higher level question about the overall task at hand.
To avoid the OrderBy, dump to a List and random pick against the Index:
VB
With New List(Of Persons)
.AddRange(db.Persons)
PersonToCall = .Item(New Random().Next(0, .Count - 1))
End With
C#
var people = new List<Persons>();
people.AddRange(db.Persons);
personToCall = people.Item(new Random().Next(0, people.Count - 1));

C# List removeall taking two arguments in the predicate?

(I've done as much as possible search based on keywords of "removeall where" or "removeall two argument predicate" without much luck so here goes)
The problem is I have a list of objects (of Class Wave) and a relationship function as:
private bool AinB(Wave A, Wave B), returning true if A 'is in' B. Also AinB(x,y) is true guarantees AinB(y,x) is false.
What's the best way to remove all of the objects in the list where the objects 'is in' another object in the list? i.e., after the removal, the list should only contain objects where neither are in the 'is in' relationship with any other object in the list?
ideally this can be done easily as a
listX.RemoveAll( (x,y) => AinB(x,y)) but of course this is not legal in C#, also there's no easy way to specify which to remove, x or y.
I thought about looping through the list with an index
int i = listX.Count - 1;
while (i>=0)
{
int r = listX.RemoveAll(X => AinB(X, listX[i]));
i = i - r - 1;
}
This seems to work, but I am wondering if there's better way with straight linq code to solve the problem.
Thanks.
Unfortunately I can't think of any way to do this that's not at least O(n^2). But the good news is that it's not that hard from a LINQ perspective:
listX.RemoveAll(item => listX.Any(isin => AinB(item, isin)));
Use a normal for loop that inspects the highest element first down to the lowest element in the list. Inspect the element at the current position for any duplicates within the list, if found remove the current element (and possibly decrement your iterator).
Example:
List<string> stuff = new List<string>(); //full of stuff
for(int i = stuff.Count - 1; i > 0; i--)
{
//Edited here for more efficiency.
for (int x = i - 1; x > 0; x--)
{
if (stuff[x] == stuff[i])
{
stuff.RemoveAt(i);
break; //or possibly continue;
}
}
}
This was hand-coded here so it might have a few syntactical errors, feel free to shoot me an edit if you find something's not quite right.
If you're a wizard with LINQ you could also try grouping the objects in the list and then just selecting the first object in each group for your output list..
you can use the LINQ Except call,
List a = new List();
a.Add("a");
a.Add("b");
a.Add("c");
List b = new List();
b.Add("b");
b.Add("c");
b.Add("d");
List c = a.Except(b);
list c will contain only item "a";
you can even make it more clever by giving a compare object,
List c = a.Except(b, new CompareObject());

Can't make simple list operation in C#

I'm trying to make a function with list.
It is to sort and delete duplicates.
It sorts good, but don't delete duplictates.
What's the problem?
void sort_del(List<double> slist){
//here i sort slist
//get sorted with duplicates
List<double> rlist = new List<double>();
int new_i=0;
rlist.Add(slist[0]);
for (i = 0; i < size; i++)
{
if (slist[i] != rlist[new_i])
{
rlist.Add(slist[i]);
new_i++;
}
}
slist = new List<double>(rlist);
//here get without duplicates
}
It does not work because slist is passed by value. Assigning rlist to it has no effect on the caller's end. Your algorithm for detecting duplicates seems fine. If you do not want to use a more elegant LINQ way suggested in the other answer, change the method to return your list:
List<double> sort_del(List<double> slist){
// Do your stuff
return rlist;
}
with double you can just use Distinct()
slist = new List<double>(rlist.Distinct());
or maybe:
slist.Distinct().Sort();
You're not modifying the underlying list. You're trying to add to a new collection, and you're not checking if the new one contains one of the old ones correctly.
If you're required to do this for homework (which seems likely, as there are data structures and easy ways to do this with LINQ that others have pointed out), you should break the sort piece and the removal of duplication into two separate methods. The methods that removes duplicates should accept a list as a parameter (as this one does), and return the new list instance without duplicates.

How to get a Random Object using Linq

I am trying to get a random object within linq. Here is how I did.
//get all the answers
var Answers = q.Skip(1).Take(int.MaxValue);
//get the random number by the number of answers
int intRandomAnswer = r.Next(1, Answers.Count());
int count = 0;
//locate the answer
foreach(var Answer in Answers)
{
if (count == intRandomAnswer)
{
SelectedPost = Answer;
break;
}
count++;
}
Is this the best way to do this?
What about:
SelectedPost = q.ElementAt(r.Next(1, Answers.Count()));
Further reading:
The comments below make good contributions to closely related questions, and I'll include them here, since as #Rouby points out, people searching for an answer to these may find this answer and it won't be correct in those cases.
Random Element Across Entire Input
To make all elements a candidate in the random selection, you need to change the input to r.Next:
SelectedPost = Answers.ElementAt(r.Next(0, Answers.Count()));
#Zidad adds a helpful extension method to get random element over all elements in the sequence:
public static T Random<T>(this IEnumerable<T> enumerable)
{
if (enumerable == null)
{
throw new ArgumentNullException(nameof(enumerable));
}
// note: creating a Random instance each call may not be correct for you,
// consider a thread-safe static instance
var r = new Random();
var list = enumerable as IList<T> ?? enumerable.ToList();
return list.Count == 0 ? default(T) : list[r.Next(0, list.Count)];
}
Another wacky approach (not the most efficient for larger data sets):
SelectedPost = q.OrderBy(qu => Guid.NewGuid()).First();
Use a Fisher-Yates-Durstenfeld shuffle.
(You could use a helper/extension method to shuffle your IEnumerable<T> sequence. Alternatively, if you were using an IList<T> you could perform an in-place shuffle, if you prefer.)
Generic extension method based on the accepted answer (which doesn't always skip the first, and only enumerates the enumerable once):
public static class EnumerableExtensions
{
public static T Random<T>(this IEnumerable<T> enumerable)
{
var r = new Random();
var list = enumerable as IList<T> ?? enumerable.ToList();
return list.ElementAt(r.Next(0, list.Count()));
}
}
Late to the party but this is a high-up Google result. A succinct version could be:
var rnd = new Random();
var SelectedPost = q.OrderBy(x => rnd.Next()).Take(1);
It has the disadvantage that it'll apply a random number to all elements, but is compact and could easily be modified to take more than one random element.
var rand = new Random();
var selectedPost = q.Skip(rand.Next(0, q.Count())).Take(1).FirstOrDefault();
Optimally, you want to only ever make the function query for a single value, so you set up the Skip/Take to jump up to the sequence number matching the random number you're generating (bounded by dataset's itemcount, so the missing row problem bounding based on MAX(pkey) isn't an issue) and then snag the first item at that point in the sequence.
In SQL this is the same as querying for SELECT Count(*) FROM q, then SELECT * FROM q LIMIT {0}, 1 where {0} is rand.Next(0, count), which should be pretty efficient.
Pulling all of the answers and looping them isn't the most efficient way as you're moving lots of data from the database. If you're using an integer primary key that's automatically incrementing, you should get the Max of your primary key and then find the random integer within that range. Then directly get the single answer based on the primary key derived from the random function.
I'm posting an answer because I don't have enough reputation to comment.
I like this answer:
SelectedPost = q.ElementAt(r.Next(1, Answers.Count()));
But ElementAt is zero based, surely starting at 1 and going to Answers.Count() you are going to end up potentially throwing an out of range, and you are never going to get the first entity.
Wouldn't
SelectedPost = q.ElementAt(r.Next(0, Answers.Count() - 1));
Be better?
I have product table in database ,every time user enters one product detail I want to show 10 similar products in below of page.And in every refresh this list must be change .it must come randomly.
Linq looks like this
var products =
DataContextFactory.GetDataContext()
.Set<Product>()
.Where(x =>x.Id!=id)
.OrderBy(emp => Guid.NewGuid())
.Take(10).ToList();
x.Id!=id
this only for not put selected product to list .
It works perfect

Categories