views:

136

answers:

3

I'm writing a Game Website, where the draw is a series of four digits. e.g 1234

I"m trying to write a query in django that will select all winners based on the four digits entered. winners are any combination of the same numbers or the same combination, 1 2 3 4, 2 3 1 4, 4 1 3 2 are all winners.

how is the most efficient way to write this query.

--------------------- Edit, sorry for not providing model samples here there are below: -----------

class Draw(models.Model):
    digit1 = models.PositiveSmallIntegerField(null=True,blank=True)
    digit2 = models.PositiveSmallIntegerField(null=True,blank=True)
    digit3 = models.PositiveSmallIntegerField(null=True,blank=True)
    digit4 = models.PositiveSmallIntegerField(null=True,blank=True)
    draw_date = models.DateTimeField()
    closed = models.BooleanField()
    winner = models.BooleanField()

    def __unicode__(self):
        return "Draw For Week Ending %s" %(self.draw_date)

    def get_absolute_url(self):
        return "/draw/%s/" % (self.draw_date)

    def save(self, force_insert=False, force_update=False):
        if self.digit1 and self.digit2 and self.digit3 and self.digit4:
            #check if there are winners
            try:
                winners = Ticket.objects.filter(draw=self.id,digit1=self.digit1,digit2=self.digit2,digit3=self.digit3,digit4=self.digit4)
                self.winner = True
            except Ticket.DoesNotExist:
                self.winner = False                
            #close & save draw/winners
            self.closed = True
            # Add new Draw for following week.
            new_date = self.draw_date + datetime.timedelta(hours=168)
            new_draw= Draw(draw_date=new_date)
            new_draw.save()
        super(Draw, self).save(force_insert, force_update) # Call the "real" save() method.

class Serial(models.Model):
    serial = models.CharField(max_length=4)
    closed = models.BooleanField(unique=False)

    def __unicode__(self):
        return "%s" %(self.serial)

    def get_absolute_url(self):
        return "/draw/serial/%s/" % (self.serial)    

class Ticket(models.Model):
    draw = models.ForeignKey(Draw)
    digit1 = models.PositiveSmallIntegerField()
    digit2 = models.PositiveSmallIntegerField()
    digit3 = models.PositiveSmallIntegerField()
    digit4 = models.PositiveSmallIntegerField()
    date = models.DateField(auto_now_add=True,editable=False)
    active = models.BooleanField(default=True)
    serial_used = models.ForeignKey(Serial,related_name="ticket_serial_used")

    def __unicode__(self):
        return "#: %s - %s" %(self.id,self.draw)

    def get_absolute_url(self):
        return "/ticket/%s/" % (self.id)    

    def save(self, force_insert=False, force_update=False):
        if self.serial_used:
            serial = Serial.objects.get(pk=self.serial_used.id)
            serial.closed = True
            serial.save()
        super(Ticket, self).save(force_insert, force_update) # Call the "real" save() method.
A: 

Code:

from itertools import permutations
winning_numbers = "1234"
winning_combinations = map(lambda v: "".join(v), list(permutations(winning_numbers, 4)))

winners = GamesPlayed.objects.filter(numbers__in=winning_combinations)

Assuming GamesPlayed is the model object for all games played, with a text field numbers containing the four selected numbers in the format NNNN.

If you're on Python 2.5 itertools does not have permutations. The docs have an implementation you can use: http://docs.python.org/library/itertools.html#itertools.permutations

codeape
this sounds like it would work.. i take it i do (based on my model declarations above?):winners = Ticket.objects.filter(digit1__in=winning_combinations, digit2__in=winning_combinations, digit3__in=winning_combinations, digit4__in=winning_combinations)
Rasiel
can your answer be shown in the context based on the models above, i basically have an idea of what you're doing here, but i'm not sure about the text field number as you're using a string, and I'm using 4 different integers.
Rasiel
I'm assuming winning_numbers will become:winning_numbers = "%s%s%s%s" %(self.digit1,self.digit2,self.digit3,self.digit4)
Rasiel
+5  A: 

I'd advise adjusting the code to save the digits so that they are saved in sorted order. E.g. if the user puts in "5262" then it should store that as "2256". Then, when you select a winning set of digits, you can sort those, and filter by simple equality. This will perform much, much better than trying to check for all possible combinations.

If you need the unsorted selection for other purposes, then add a new field to the model sortedDigits or something so that you have it to compare against.

Walter Mundt
would this imply that i change my model declaration from 4 individual digits to 1 combined digit?
Rasiel
I think that would be simpler. Alternatively, you just sort the digits and then have your filter do four equality checks, one per digit, against the winning combo. Either way would work.
Walter Mundt
this works thanks!, i just did the four digits and equality checks for each digit
Rasiel
Actually, Walter's original suggestion -- single field containing the sorted digits -- is a much better way to go. Not only is the code simpler, it will scale much, *much* better than having the 4 separate fields. If you index the sortedDigits fields you'll have near instantaneous results even with huge numbers of entries.
Peter Rowell
A: 

Is the order of the numbers important?

If not, you could sort the digits for tickets and draws in ascending order, then use your code

winners = Ticket.objects.filter(draw=self.id,digit1=self.digit1,digit2=self.digit2,digit3=self.digit3,digit4=self.digit4)

As an aside, Your try... except block won't catch the situation when there are no winners. The DoesNotExist exception is thrown by the get method (see docs).

If there isn't a winning ticket, the filter method will return an empty queryset, but not raise an error. You can then check whether there are winners using an if statement.

if winners
    # there are winners
    self.winner = True
else:
    # there are not winners
    self.winner = False
Alasdair
hi, no the order of the numbers are not important, the winning combination is, but i'm not sure how to sort the ticket numbers and how that translates to the logic of selecting all possible combinations of the winning digits? can you clarify please?
Rasiel
Thanks for noting the Try Except Block, I hadn't noted it!
Rasiel
It might have been clearer if I said sort the ticket _digits_ instead of _numbers_. Sorting a list in python is straight forward. There's no need to calculate all combinations, because all combinations can be reduced to the same sorted list. Hope that makes sense.
Alasdair