Mailing List Archive

on writing a while loop for rolling two dice
How should I write this? I'd like to roll two six-sided dice until I
get the same number on both. I'd like to get the number of times I
tried. Here's a primitive I'm using:

--8<---------------cut here---------------start------------->8---
>>> x, y = roll()
>>> x
6
>>> y
6 # lucky

>>> x, y = roll()
>>> x
4
>>> y
1 # unlucky
--8<---------------cut here---------------end--------------->8---

Here's my solution:

--8<---------------cut here---------------start------------->8---
def how_many_times():
x, y = 0, 1
c = 0
while x != y:
c = c + 1
x, y = roll()
return c, (x, y)
--8<---------------cut here---------------end--------------->8---

Why am I unhappy? I'm wish I could confine x, y to the while loop. The
introduction of ``x, y = 0, 1'' must feel like a trick to a novice. How
would you write this? Thank you!
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
ram@zedat.fu-berlin.de (Stefan Ram) writes:

> Hope Rouselle <hrouselle@jevedi.com> writes:
>>How would you write this?
>
> """Rolls two dice until both yield the same value.
> Returns the number of times the two dice were rolled
> and the final value yielded."""
> roll_count = 0
> while True:
> outcome = roll_two_dice()
> roll_count += 1
> if outcome[ 0 ]== outcome[ 1 ]: break
> return roll_count, outcome[ 0 ]

You totally convinced me. Thanks.
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
Hope Rouselle <hrouselle@jevedi.com> writes:

> ram@zedat.fu-berlin.de (Stefan Ram) writes:
>
>> Hope Rouselle <hrouselle@jevedi.com> writes:
>>>How would you write this?
>>
>> """Rolls two dice until both yield the same value.
>> Returns the number of times the two dice were rolled
>> and the final value yielded."""
>> roll_count = 0
>> while True:
>> outcome = roll_two_dice()
>> roll_count += 1
>> if outcome[ 0 ]== outcome[ 1 ]: break
>> return roll_count, outcome[ 0 ]
>
> You totally convinced me. Thanks.

Wait, I'm surprised ``outcome'' is still a valid name at the
return-statement. Wasn't it defined inside the while? Shouldn't its
scope be restricted to the while block? I had no idea. I should learn
some Python.
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
ram@zedat.fu-berlin.de (Stefan Ram) writes:

> Hope Rouselle <hrouselle@jevedi.com> writes:
>>Wait, I'm surprised ``outcome'' is still a valid name at the
>>return-statement. Wasn't it defined inside the while? Shouldn't its
>>scope be restricted to the while block? I had no idea. I should learn
>>some Python.
>
> In Python, local names can be introduced by an assignment
> and have function scope. There is no block scope in Python.
>
> Below, "name" /is/ a local name already, but is being
> used before being assigned to.
>
> def function():
> if False:
> name = 0
> return name
>
> function()
>
> # return name
> # UnboundLocalError: local variable 'name' referenced before assignment

I appreciated the example. I had no idea. (I had looked up the rules
and it was pretty simple to understand, but an example is always nice.)
Thank you so much.
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On Sun, Aug 29, 2021 at 7:37 AM Hope Rouselle <hrouselle@jevedi.com> wrote:
>
> How should I write this? I'd like to roll two six-sided dice until I
> get the same number on both. I'd like to get the number of times I
> tried. Here's a primitive I'm using:
>
> --8<---------------cut here---------------start------------->8---
> >>> x, y = roll()
> >>> x
> 6
> >>> y
> 6 # lucky
>
> >>> x, y = roll()
> >>> x
> 4
> >>> y
> 1 # unlucky
> --8<---------------cut here---------------end--------------->8---
>
> Here's my solution:
>
> --8<---------------cut here---------------start------------->8---
> def how_many_times():
> x, y = 0, 1
> c = 0
> while x != y:
> c = c + 1
> x, y = roll()
> return c, (x, y)
> --8<---------------cut here---------------end--------------->8---
>
> Why am I unhappy? I'm wish I could confine x, y to the while loop. The
> introduction of ``x, y = 0, 1'' must feel like a trick to a novice. How
> would you write this? Thank you!

Your loop, fundamentally, is just counting. So let's just count.

def how_many_times():
for c in itertools.count():
...

Inside that loop, you can do whatever you like, including returning
immediately if you have what you want. I'll let you figure out the
details. :)

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 8/28/2021 8:00 AM, Hope Rouselle wrote:
> How should I write this? I'd like to roll two six-sided dice until I
> get the same number on both. I'd like to get the number of times I
> tried. Here's a primitive I'm using:
>
> --8<---------------cut here---------------start------------->8---
>>>> x, y = roll()
>>>> x
> 6
>>>> y
> 6 # lucky
>
>>>> x, y = roll()
>>>> x
> 4
>>>> y
> 1 # unlucky
> --8<---------------cut here---------------end--------------->8---
>
> Here's my solution:
>
> --8<---------------cut here---------------start------------->8---
> def how_many_times():
> x, y = 0, 1
> c = 0
> while x != y:
> c = c + 1
> x, y = roll()
> return c, (x, y)
> --8<---------------cut here---------------end--------------->8---
>
> Why am I unhappy? I'm wish I could confine x, y to the while loop. The
> introduction of ``x, y = 0, 1'' must feel like a trick to a novice. How
> would you write this? Thank you!

Something like (untested)

c = 0
while True:
c += 1
x, y = roll()
if x == y:
return c, (x,y)

or even better to me, as it will not loop forever if you mess up the
condition

for i in range(1, 1000000):
x, y = roll()
if x == y:
return i, (x,y)
# return "The universe ends as the essentially impossible happened"


--
Terry Jan Reedy

--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 29/08/2021 08.46, Hope Rouselle wrote:
> Here's my solution:
>
> --8<---------------cut here---------------start------------->8---
> def how_many_times():
> x, y = 0, 1
> c = 0
> while x != y:
> c = c + 1
> x, y = roll()
> return c, (x, y)

>
> Why am I unhappy? I'm wish I could confine x, y to the while loop. The
> introduction of ``x, y = 0, 1'' must feel like a trick to a novice. How
> would you write this?



> ram@zedat.fu-berlin.de (Stefan Ram) writes:
>> """Rolls two dice until both yield the same value.
>> Returns the number of times the two dice were rolled
>> and the final value yielded."""
>> roll_count = 0
>> while True:
>> outcome = roll_two_dice()
>> roll_count += 1
>> if outcome[ 0 ]== outcome[ 1 ]: break
>> return roll_count, outcome[ 0 ]
>
> You totally convinced me. Thanks.


On the other hand...
whilst you expressed concern about the apparently disconnected 'set up'
necessary before the loop, this solution adds a "True/Forever" and a
"Break" construct, which some may deem not that much better (if at all)

The idea of abrogating the while-condition but then adding another
(disconnected) condition to break, seems to hold equal potential for
confusion. or the type of dissatisfaction which motivated the original
question!

Looking at that from the inside-out, the loop's contents perform two
functions: the rolling and counting (per Statement of Requirements), but
also a loop-controlling element. Thus the reader's question: "what does
this loop do?" is conflated with "how many times does it do it?".


Let's go completely off-the-rails, why not use a never-ending range() to
fuel a for-loop 'counter', and within that loop perform the dice-roll(s)
and decide if it is time to 'break'. The range replaces the "True". The
for-loops index or 'counter' will deliver the desired result.

Neat? No!
Readable? No!
An improvement over the while-True? Definitely not!
Yet, the mechanism is the same AND offers a built-in counter. Hmmm...


Returning to the concern:

x, y = 0, 1
c = 0

The first line is purely to ensure that the loop executes at least once,
ie the two assigned-values are not 'real'. Hence the disquiet!

Initiating the counter is unavoidable (@Chris' suggestion notwithstanding).

However, remember that Python (like decent DBs) has a concept (and an
idiom) of a value to be used when we don't (yet) know what the value
is/should be! Further that Python allows such a value to be used in
comparisons:

>>> None != None
False
>>> None == None
True

Leading to:

c, x, y = 0, None, None
while ...


Which solution reverts to the original loop-contents. which seem more
obvious and thus more readable. (YMMV!)

Simplicity over 'being clever'...
--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 28/08/2021 21:50, Hope Rouselle wrote:

>>> roll_count = 0
>>> while True:
>>> outcome = roll_two_dice()
>>> roll_count += 1
>>> if outcome[ 0 ]== outcome[ 1 ]: break
>>> return roll_count, outcome[ 0 ]
>>
>
> Wait, I'm surprised ``outcome'' is still a valid name at the
> return-statement. Wasn't it defined inside the while?

If that really bugs you just replace the break with the return.


>>> if outcome[ 0 ]== outcome[ 1 ]:
>>> return roll_count, outcome[ 0 ]

Now its all inside the loop.

But remember readable code is better than cute code every time.
And personally I'd just declare the x,y up front. Easier to
understand and debug IMHO.

--
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


--
https://mail.python.org/mailman/listinfo/python-list
RE: on writing a while loop for rolling two dice [ In reply to ]
And there is the ever popular recursive version you call with no while loop
in sight. And, oddly, no variable declared in your main body:

#---- CODE START ---
import random

def roll2():
return random.randint(1,6), random.randint(1,6)

def roll_equal(counter):
first, second = roll2()
encountered = counter + 1
if (first == second):
return(encountered)
else:
return(roll_equal(encountered))

#--- CODE END ---

Since the result is usually a single digit of iterations, no biggie. Here is
some output:

>>> roll_equal(0)
6
>>> roll_equal(0)
7
>>> roll_equal(0)
1
>>> roll_equal(0)
7
>>> roll_equal(0)
6
>>> [ roll_equal(0) for n in range(10)]
[3, 4, 2, 5, 8, 1, 1, 2, 3, 9]
>>> [ roll_equal(0) for n in range(10)]
[3, 3, 7, 19, 7, 2, 1, 3, 8, 4]
>>> [ roll_equal(0) for n in range(10)]
[1, 3, 1, 13, 11, 4, 3, 5, 2, 4]

And the code can be a tad shorter, LOL!

But obviously then you have more overhead than an iterative solution or one
using a generator ...

-----Original Message-----
From: Python-list <python-list-bounces+avigross=verizon.net@python.org> On
Behalf Of Alan Gauld via Python-list
Sent: Saturday, August 28, 2021 6:52 PM
To: python-list@python.org
Subject: Re: on writing a while loop for rolling two dice

On 28/08/2021 21:50, Hope Rouselle wrote:

>>> roll_count = 0
>>> while True:
>>> outcome = roll_two_dice()
>>> roll_count += 1
>>> if outcome[ 0 ]== outcome[ 1 ]: break return roll_count,
>>> outcome[ 0 ]
>>
>
> Wait, I'm surprised ``outcome'' is still a valid name at the
> return-statement. Wasn't it defined inside the while?

If that really bugs you just replace the break with the return.


>>> if outcome[ 0 ]== outcome[ 1 ]:
>>> return roll_count, outcome[ 0 ]

Now its all inside the loop.

But remember readable code is better than cute code every time.
And personally I'd just declare the x,y up front. Easier to understand and
debug IMHO.

--
Alan G
Author of the Learn to Program web site
http://www.alan-g.me.uk/
http://www.amazon.com/author/alan_gauld
Follow my photo-blog on Flickr at:
http://www.flickr.com/photos/alangauldphotos


--
https://mail.python.org/mailman/listinfo/python-list

--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 28/08/2021 14:00, Hope Rouselle wrote:

> def how_many_times():
> x, y = 0, 1
> c = 0
> while x != y:
> c = c + 1
> x, y = roll()
> return c, (x, y)
> --8<---------------cut here---------------end--------------->8---
>
> Why am I unhappy? I'm wish I could confine x, y to the while loop. The
> introduction of ``x, y = 0, 1'' must feel like a trick to a novice. How
> would you write this? Thank you!

I'd probably hide the while loop under the rug:

>>> import random
>>> def roll_die():
while True: yield random.randrange(1, 7)


Then:

>>> def hmt():
for c, (x, y) in enumerate(zip(roll_die(), roll_die()), 1):
if x == y:
return c, (x, y)


>>> hmt()
(1, (2, 2))
>>> hmt()
(4, (4, 4))
>>> hmt()
(1, (5, 5))


OK, maybe a bit complicated... but does it pay off if you want to
generalize?

>>> def roll_die(faces):
while True: yield random.randrange(1, 1 + faces)

>>> def hmt(faces, dies):
for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
if len(set(d)) == 1: return c, d


>>> hmt(10, 1)
(1, (2,))
>>> hmt(10, 2)
(3, (10, 10))
>>> hmt(10, 3)
(250, (5, 5, 5))
>>> hmt(1, 10)
(1, (1, 1, 1, 1, 1, 1, 1, 1, 1, 1))

You decide :)

--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 29/08/2021 20.06, Peter Otten wrote:
...
> OK, maybe a bit complicated... but does it pay off if you want to
> generalize?
>
>>>> def roll_die(faces):
>     while True: yield random.randrange(1, 1 + faces)
>
>>>> def hmt(faces, dies):
>     for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
>         if len(set(d)) == 1: return c, d


Curiosity:
why not add dies as a parameter of roll_die()?

Efficiency:
- wonder how max( d ) == min( d ) compares for speed with the set() type
constructor?
- alternately len( d ) < 2?
- or len( d ) - 1 coerced to a boolean by the if?
- how much more efficient is any of this (clever thinking!) than the
OP's basic, simpler, and thus more readable, form?

English language 'treachery':
- one die
- multiple dice
(probably not followed in US-English (can't recall), particularly on
computers running the Hollywood Operating System).

Continuous Education:
Thanks for the reminder that enumerate() can be seeded with a "start" value!
--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On Sun, Aug 29, 2021 at 8:14 PM dn via Python-list
<python-list@python.org> wrote:
> Efficiency:
> - wonder how max( d ) == min( d ) compares for speed with the set() type
> constructor?

That may or may not be an improvement.

> - alternately len( d ) < 2?
> - or len( d ) - 1 coerced to a boolean by the if?

Neither of these will make any notable improvement. The work is done
in constructing the set, and then you're taking the length. How you do
the comparison afterwards is irrelevant.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 29/08/2021 12:13, dn via Python-list wrote:
> On 29/08/2021 20.06, Peter Otten wrote:
> ...
>> OK, maybe a bit complicated... but does it pay off if you want to
>> generalize?
>>
>>>>> def roll_die(faces):
>>     while True: yield random.randrange(1, 1 + faces)
>>
>>>>> def hmt(faces, dies):
>>     for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
>>         if len(set(d)) == 1: return c, d
>
>
> Curiosity:
> why not add dies as a parameter of roll_die()?

Dunno. Maybe because I've "always" [1] wanted a version of
random.randrange() that generates values indefinitely. It would need to
check its arguments only once, thus leading to some extra

> Efficiency:
> - wonder how max( d ) == min( d ) compares for speed with the set() type
> constructor?

I did the simplest thing, speed was not a consideration. If it is, and
dies (sorry for that) is large I'd try

first = d[0]
all(x == first for x in d) # don't mind one duplicate test

For smaller numbers of dice I'd unpack (first, *rest) inside the for
loop. But it's a trade-off, you' have to measure if/when it's better to
go through the whole tuple in C.


> - alternately len( d ) < 2?
> - or len( d ) - 1 coerced to a boolean by the if?
> - how much more efficient is any of this (clever thinking!) than the
> OP's basic, simpler, and thus more readable, form?

It really isn't efficiency, it's a (misled?) sense of aesthetics where
I've come to prefer

- for-loops over while, even when I end up with both to get the desired for

- enumerate() over an explicit counter even though there is the extra
unpack, and you still need to initialize the counter in the general case:

for i, item in enumerate([]): pass
print(f"There are {i+1} items in the list.") # Oops

> English language 'treachery':
> - one die
> - multiple dice

You might have inferred that I knew (or had looked up) the singular of
dice, so this is but a momentary lapse of reason. It hurts me more than
you, trust me. Not as much, as going on record with confusing they're
and their, but still ;)

> (probably not followed in US-English (can't recall), particularly on
> computers running the Hollywood Operating System).

I've come to the conclusion that International English is hopelessly and
inevitably broken. That's the price native speakers have to pay for
having they're (oops, I did it again!) language used as lingua franca.

> Continuous Education:
> Thanks for the reminder that enumerate() can be seeded with a "start" value!

[1] I think I've suggested reimplementing the whole module in terms of
generators -- can't find the post though.

--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 29/08/2021 22.24, Chris Angelico wrote:
> On Sun, Aug 29, 2021 at 8:14 PM dn via Python-list
> <python-list@python.org> wrote:
>> Efficiency:
>> - wonder how max( d ) == min( d ) compares for speed with the set() type
>> constructor?
>
> That may or may not be an improvement.
>
>> - alternately len( d ) < 2?
>> - or len( d ) - 1 coerced to a boolean by the if?
>
> Neither of these will make any notable improvement. The work is done
> in constructing the set, and then you're taking the length. How you do
> the comparison afterwards is irrelevant.

It was far too late for either of us (certainly this little boy) to be
out-and-coding - plus an excellent illustration of why short-names are a
false-economy which can quickly (and easily) lead to "technical debt"!


The "d" is a tuple (the 'next' returned from the zip-output object)
consisting of a number of die-throw results). Thus, can toss that into
len() without (any overhead of) conversion to a set.
--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On Mon, Aug 30, 2021 at 9:53 AM dn via Python-list
<python-list@python.org> wrote:
>
> On 29/08/2021 22.24, Chris Angelico wrote:
> > On Sun, Aug 29, 2021 at 8:14 PM dn via Python-list
> > <python-list@python.org> wrote:
> >> Efficiency:
> >> - wonder how max( d ) == min( d ) compares for speed with the set() type
> >> constructor?
> >
> > That may or may not be an improvement.
> >
> >> - alternately len( d ) < 2?
> >> - or len( d ) - 1 coerced to a boolean by the if?
> >
> > Neither of these will make any notable improvement. The work is done
> > in constructing the set, and then you're taking the length. How you do
> > the comparison afterwards is irrelevant.
>
> It was far too late for either of us (certainly this little boy) to be
> out-and-coding - plus an excellent illustration of why short-names are a
> false-economy which can quickly (and easily) lead to "technical debt"!
>
>
> The "d" is a tuple (the 'next' returned from the zip-output object)
> consisting of a number of die-throw results). Thus, can toss that into
> len() without (any overhead of) conversion to a set.

Oh. Well, taking the length of the tuple is fast... but useless. The
point was to find out if everything in it was unique :)

Conversion to set tests this because the length of the set is the
number of unique elements; checking max and min works because two
scans will tell you if they're all the same; using all with a
generator stops early if you find a difference, but requires
back-and-forth calls into Python code; there are various options, and
the choice probably won't make a material performance difference
anyway :)

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 30/08/2021 00.47, Peter Otten wrote:
> On 29/08/2021 12:13, dn via Python-list wrote:
>> On 29/08/2021 20.06, Peter Otten wrote:
>> ...
>>> OK, maybe a bit complicated... but does it pay off if you want to
>>> generalize?
>>>
>>>>>> def roll_die(faces):
>>>      while True: yield random.randrange(1, 1 + faces)
>>>
>>>>>> def hmt(faces, dies):
>>>      for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
>>>          if len(set(d)) == 1: return c, d
>>
>>
>> Curiosity:
>> why not add dies as a parameter of roll_die()?
>
> Dunno. Maybe because I've "always" [1] wanted a version of
> random.randrange() that generates values indefinitely. It would need to
> check its arguments only once, thus leading to some extra

Each code-unit should do one job, and do it well!
SRP...


>> Efficiency:
>> - wonder how max( d ) == min( d ) compares for speed with the set() type
>> constructor?
>
> I did the simplest thing, speed was not a consideration. If it is, and
> dies (sorry for that) is large I'd try
>
> first = d[0]
> all(x == first for x in d)  # don't mind one duplicate test
>
> For smaller numbers of dice I'd unpack (first, *rest) inside the for
> loop. But it's a trade-off, you' have to measure if/when it's better to
> go through the whole tuple in C.

For larger numbers of dice, and presuming a preference for an
inner-function which rolls only a single die per call; would it be more
efficient to test each individual die's value against the first,
immediately after its (individual) roll/evaluation (rinse-and-repeat for
each die thereafter)? This, on the grounds that the first mis-match
obviates the need to examine (or even roll), any other die/dice in the
collection.

OTOH the simulation of rolling n-number of dice, as would happen in the
real-world, would be broken by making the computer's algorithm more
efficient (rolling until the first non-equal value is 'found'). Does
that mean the realism of the model dies?
(sorry - no, I'm not sorry - you deserved that!)

Does one "roll" a die, or "shake the dice"???


We don't know the OP's requirements wrt to execution-efficiency.
However, as you (also) probably suffered, such exercises regularly
feature in stats and probability courses. Sometimes 'the numbers' are
quite large in order to better-illustrate ("smooth") distribution
characteristics, etc.


I have a love?hate relationship with questions of Python and
'efficiency'. Today, discovered that using a cut-down Linux version (on
AWS) was actually slower than using a full-fat distribution - upon
analysis, my friendly 'expert' was able to point the finger at the way
the two distros compiled/prepared/distribute the(ir) Python Interpreter.
(I'm glad he thought such investigation 'fun'!) All of which further
complicates the business of design, given we already know of situations
where approach-a will run faster than approach-b, on your machine; yet
the comparison may be reversed on mine.

This discussion forms a sub-set of that: when to use the built-in
functions (implemented in C) because they are (claimed to be) more
efficient than another approach - and, when one approach using a
built-in function might be faster than another 'built-in'/C-coded approach.

("small things amuse small minds" - mind how you describe my mind!)


"Bottom line": I prefer to think of Python's "efficiency" as reflected
in the amount of my time that is needed, in order to complete a project!


>> - alternately len( d ) < 2?
>> - or len( d ) - 1 coerced to a boolean by the if?
>> - how much more efficient is any of this (clever thinking!) than the
>> OP's basic, simpler, and thus more readable, form?
>
> It really isn't efficiency, it's a (misled?) sense of aesthetics where
> I've come to prefer
>
> - for-loops over while, even when I end up with both to get the desired for
>
> - enumerate() over an explicit counter even though there is the extra
> unpack, and you still need to initialize the counter in the general case:
>
> for i, item in enumerate([]): pass
> print(f"There are {i+1} items in the list.")  # Oops

Next thing you'll be using for-else...
[insane giggling]


It's interesting how we arrive at these views (as a trainer I spend a
lot of time trying to detect how learners build their mental maps, or
"models", of each topic).

I've always had a clear 'formula'/rule/hobgoblin: if the number of loops
can be predicted, use 'for', otherwise use 'while'.

Of course, Python alters that view because it offers a for-each, which
means that I don't need to know the number of loops, only that the loop
will cycle through each item in the iterable.

It used to be a far simpler world!


That said, I really miss the option of while controlling the loop with a
pre-condition AND having a repeat...until controlling the loop with a
post-condition - the former enabling >=0 loops; the latter, requiring at
least one!


Using enumerate() may be a matter of aesthetics (English-English
spelling!). However, it is a basic Python idiom. It is as much a tool in
our coding as a fork/spoon/chop-sticks/fingers are to someone eating.
(yes, I'm feeling hungry).

At times I feel a 'siren call' to use some of the powerful, let's call
them "one-liner" tools, because it is 'fun' to bend my mind around the
challenge. However, most such code tends to lose readability in some
sort of inverse proportion to its power - it leaves the clarity and
simplicity of Python-space and heads towards APL, Lisp, et al.

The 'problem' is that as a coder increases his/her knowledge (learns to
harness 'the force'), the code-structures which count as one "chunk" of
thought, expand. For example, it can be difficult to see (remember) that
although comprehensions may have become a single 'unit' of thought to a
skilled practitioner; for others they are (still) opaque, and
'mountains' to climb.

Thus, the 'balance' between 'power' and readability; and between using
techniques which demand Python-expertise and thus carry an expectation
that less-capable/-experienced programmers will 'improve their game' (to
be able to contribute to 'this' project, or future re-use/maintenance).
(see also "Zen of Python")

The 'level' then becomes a convention - just as much as 'do we adhere to
PEP-008 naming' (etc). The important point is not what the convention
is, but that the team has arrived at a pragmatic agreement, eg will we
use comprehensions or explicit loops. Your team might say "of course",
whereas mine says "can't cope with that"...

BTW you already know this, but I'm not writing only to you!
(don't fret, you're still 'special'...)


>> English language 'treachery':
>> - one die
>> - multiple dice
>
> You might have inferred that I knew (or had looked up) the singular of
> dice, so this is but a momentary lapse of reason. It hurts me more than
> you, trust me. Not as much, as going on record with confusing they're
> and their, but still ;)

Reason? Logic? Consistency?
The English language.
Surely, you jest...


Having learned/lived many languages, struggling-mightily with some,
feeling comfortable with others; I am enormously grateful (greatly
grateful?) that English was one of my home-languages, and that I didn't
have to "learn" it!

I do enjoy 'playing' with it though, and as pointed-out annually
(?semesterly), making students groan in response to my
weak/pathetic/Dad-joke/seven-year-old's humor (in live-lectures) at
least gives feedback that they are 'there' and paying some attention.
(alternately, it's Pavlovian-conditioning - BTW: yes, I'm still hungry...)


Please consider the die/dice as 'educational', and a reflection of my
confusion of comprehension when trying to infer meaning whilst reading
the code-example - rather than arrogance or placing undue demand on you.


>> (probably not followed in US-English (can't recall), particularly on
>> computers running the Hollywood Operating System).
>
> I've come to the conclusion that International English is hopelessly and
> inevitably broken. That's the price native speakers have to pay for
> having they're (oops, I did it again!) language used as lingua franca.

If English really set-out to be the world's lingua-franca, why didn't it
come up with its own, a particularly (and peculiarly) English word, for
the concept???


I've spent quite some time looking into the idea of 'International
English' - to attempt to find 'an English' to employ in our course
materials used world-wide. Even 'native speakers' can't agree on what is
'English'! When the question is widened to include the broader, majority
of the world; the concept of a 'core' of English becomes impossible to
define or encapsulate...


Even being a 'native speaker' doesn't necessarily help: George Bernard
Shaw described "Britain and America are two nations divided by [the use
of] a common language"!

He was Irish - and I recall another quotation along the lines of: the
Irish learn English in order to hurl insults.

Sigh!


Let's not be racist though - or would that be lingual-ist(?) Advice when
learning French was "always expect exceptions" (apologies, I went
looking for a source, in English or French - but Python-references
crowded the first couple of web-search 'hits'). I enjoy the word-picture
in Spanish: "Hijo de tigre, no siempre sale pintado. Siempre ahi esa
excepción." (The tiger-cub isn't always born (ready-)painted
(colored?striped). There is always an exception!) So much more apropos
(to use another (?)English expression) than, that "a leopard NEVER
changes its spots"!


>> Continuous Education:
>> Thanks for the reminder that enumerate() can be seeded with a "start"
>> value!
>
> [1] I think I've suggested reimplementing the whole module in terms of
> generators -- can't find the post though.

Can't we assume lazy-evaluation - if only by virtue of the example
equivalent-code illustration in the docs? (it features a "yield")
(https://docs.python.org/3/library/functions.html?highlight=enumerate#enumerate)

Must admit, once the use of generators started to multiply
release-after-release (both in Python code and 'under the hood'), and
tools like range()/xrange() were made more memory-efficient, etc, that
pretty much all such constructs had become "lazy".
(Careless assumption. Don't know!)
--
Regards,
=dn
--
https://mail.python.org/mailman/listinfo/python-list
RE: on writing a while loop for rolling two dice [ In reply to ]
> def how_many_times():
> x, y = 0, 1
> c = 0
> while x != y:
> c = c + 1
> x, y = roll()
> return c, (x, y)

Since I haven't seen it used in answers yet, here's another option using our new walrus operator

def how_many_times():
roll_count = 1
while (rolls := roll())[0] != rolls[1]:
roll_count += 1
return (roll_count, rolls)
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On Mon, Aug 30, 2021 at 11:13 PM David Raymond <David.Raymond@tomtom.com> wrote:
>
> > def how_many_times():
> > x, y = 0, 1
> > c = 0
> > while x != y:
> > c = c + 1
> > x, y = roll()
> > return c, (x, y)
>
> Since I haven't seen it used in answers yet, here's another option using our new walrus operator
>
> def how_many_times():
> roll_count = 1
> while (rolls := roll())[0] != rolls[1]:
> roll_count += 1
> return (roll_count, rolls)
>

Since we're creating solutions that use features in completely
unnecessary ways, here's a version that uses collections.Counter:

def how_many_times():
return next((count, rolls) for count, rolls in
enumerate(iter(roll, None)) if len(Counter(rolls)) == 1)

Do I get bonus points for it being a one-liner that doesn't fit in
eighty characters?

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 30/08/2021 15:50, Chris Angelico wrote:

> def how_many_times():
> return next((count, rolls) for count, rolls in
> enumerate(iter(roll, None)) if len(Counter(rolls)) == 1)


That's certainly the most Counter-intuitive version so far;)


> Do I get bonus points for it being a one-liner that doesn't fit in
> eighty characters?

Nah, but you'll get an honorable mention when you run it through
pycodestyle without line break...

--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On Tue, Aug 31, 2021 at 12:28 AM Peter Otten <__peter__@web.de> wrote:
>
> On 30/08/2021 15:50, Chris Angelico wrote:
>
> > def how_many_times():
> > return next((count, rolls) for count, rolls in
> > enumerate(iter(roll, None)) if len(Counter(rolls)) == 1)
>
>
> That's certainly the most Counter-intuitive version so far;)

Thank you, I appreciate that :)

> > Do I get bonus points for it being a one-liner that doesn't fit in
> > eighty characters?
>
> Nah, but you'll get an honorable mention when you run it through
> pycodestyle without line break...
>

Are there any linters that warn against "unintuitive use of
two-argument iter()"?

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On 30/08/2021 06:17, dn via Python-list wrote:

> OTOH the simulation of rolling n-number of dice, as would happen in the
> real-world, would be broken by making the computer's algorithm more
> efficient (rolling until the first non-equal value is 'found'). Does
> that mean the realism of the model dies?

You've got to ask your dietician...

(I'm sure everyone agrees that I should stop here. And stop I will)

--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
dn <PythonList@DancesWithMice.info> writes:

> On 29/08/2021 08.46, Hope Rouselle wrote:
>> Here's my solution:
>>
>> --8<---------------cut here---------------start------------->8---
>> def how_many_times():
>> x, y = 0, 1
>> c = 0
>> while x != y:
>> c = c + 1
>> x, y = roll()
>> return c, (x, y)
>
>>
>> Why am I unhappy? I'm wish I could confine x, y to the while loop. The
>> introduction of ``x, y = 0, 1'' must feel like a trick to a novice. How
>> would you write this?
>
>> ram@zedat.fu-berlin.de (Stefan Ram) writes:
>>> """Rolls two dice until both yield the same value.
>>> Returns the number of times the two dice were rolled
>>> and the final value yielded."""
>>> roll_count = 0
>>> while True:
>>> outcome = roll_two_dice()
>>> roll_count += 1
>>> if outcome[ 0 ]== outcome[ 1 ]: break
>>> return roll_count, outcome[ 0 ]
>>
>> You totally convinced me. Thanks.
>
> On the other hand...
> whilst you expressed concern about the apparently disconnected 'set up'
> necessary before the loop, this solution adds a "True/Forever" and a
> "Break" construct, which some may deem not that much better (if at all)
>
> The idea of abrogating the while-condition but then adding another
> (disconnected) condition to break, seems to hold equal potential for
> confusion. or the type of dissatisfaction which motivated the original
> question!

Pretty well observed! Hats to you.

> Looking at that from the inside-out, the loop's contents perform two
> functions: the rolling and counting (per Statement of Requirements), but
> also a loop-controlling element. Thus the reader's question: "what does
> this loop do?" is conflated with "how many times does it do it?".

Well put.

> Let's go completely off-the-rails, why not use a never-ending range() to
> fuel a for-loop 'counter', and within that loop perform the dice-roll(s)
> and decide if it is time to 'break'. The range replaces the "True". The
> for-loops index or 'counter' will deliver the desired result.
>
> Neat? No!
> Readable? No!
> An improvement over the while-True? Definitely not!
> Yet, the mechanism is the same AND offers a built-in counter. Hmmm...

Yeah. Here's a little context. I came across this by processing a list
of exercises. (I'm teaching a course --- you know that by now, I
guess.) So the first thing I observed was the equal volume of work
dedicated to while loops and for loops --- so I decided to compared
which appeared more often in a certain sample of well-written Python
code. It turns out the for loop was much more frequent. Students have
been reporting too much work in too little time, so I decided to reduce
the number of exercises involving while loops. When I began to look at
the exercises, to see which ones I'd exclude, I decided to exclude them
all --- lol! --- except for one. The one that remained was this one
about rolling dice until a satisfying result would appear. (All other
ones were totally more naturally written with a for loop.)

So if I were to also write this with a for-loop, it'd defeat the purpose
of the course's moment. Besides, I don't think a for-loop would improve
the readability here.

But I thought your protest against the while-True was very well put:
while-True is not too readable for a novice. Surely what's readable or
more-natural /to someone/ is, well, subjective (yes, by definition).
But perhaps we may agree that while rolling dice until a certain
success, we want to roll them while something happens or doesn't happen.
One of the two. So while-True is a bit of a jump. Therefore, in this
case, the easier and more natural option is to say while-x-not-equal-y.

But this approach seems to force me into initializing x, y with
different values.

> Returning to the concern:
>
> x, y = 0, 1
> c = 0
>
> The first line is purely to ensure that the loop executes at least once,
> ie the two assigned-values are not 'real'. Hence the disquiet!
>
> Initiating the counter is unavoidable (@Chris' suggestion notwithstanding).
>
> However, remember that Python (like decent DBs) has a concept (and an
> idiom) of a value to be used when we don't (yet) know what the value
> is/should be! Further that Python allows such a value to be used in
> comparisons:
>
>>>> None != None
> False
>>>> None == None
> True
>
> Leading to:
>
> c, x, y = 0, None, None
> while ...
>
>
> Which solution reverts to the original loop-contents. which seem more
> obvious and thus more readable. (YMMV!)
>
> Simplicity over 'being clever'...

I don't see it. You seem to have found what we seem to agree that it
would be the more natural way to write the strategy. But I can't see
it. It certainly isn't

--8<---------------cut here---------------start------------->8---
def how_many_times_1():
c, x, y = 0, None, None
while x != y:
c = c + 1
x, y = roll()
return c, x, y
--8<---------------cut here---------------end--------------->8---

nor

--8<---------------cut here---------------start------------->8---
def how_many_times_2():
c, x, y = 0, None, None
while x == y:
c = c + 1
x, y = dados()
return c, x, y
--8<---------------cut here---------------end--------------->8---

What do you have in mind? I couldn't see it.
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
David Raymond <David.Raymond@tomtom.com> writes:

>> def how_many_times():
>> x, y = 0, 1
>> c = 0
>> while x != y:
>> c = c + 1
>> x, y = roll()
>> return c, (x, y)
>
> Since I haven't seen it used in answers yet, here's another option using our new walrus operator
>
> def how_many_times():
> roll_count = 1
> while (rolls := roll())[0] != rolls[1]:
> roll_count += 1
> return (roll_count, rolls)

That's nice, although it doesn't seem more readable to a novice seeing a
while for the first time, seeing a loop for the first time, than that
while-True version. In fact, I think the while-True is the clearest so
far. But it's always nice to spot a walrus in the wild! (If you're
somewhere safe, that is.)
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
Chris Angelico <rosuav@gmail.com> writes:

> On Mon, Aug 30, 2021 at 11:13 PM David Raymond <David.Raymond@tomtom.com> wrote:
>>
>> > def how_many_times():
>> > x, y = 0, 1
>> > c = 0
>> > while x != y:
>> > c = c + 1
>> > x, y = roll()
>> > return c, (x, y)
>>
>> Since I haven't seen it used in answers yet, here's another option using our new walrus operator
>>
>> def how_many_times():
>> roll_count = 1
>> while (rolls := roll())[0] != rolls[1]:
>> roll_count += 1
>> return (roll_count, rolls)
>>
>
> Since we're creating solutions that use features in completely
> unnecessary ways, here's a version that uses collections.Counter:
>
> def how_many_times():
> return next((count, rolls) for count, rolls in
> enumerate(iter(roll, None)) if len(Counter(rolls)) == 1)
>
> Do I get bonus points for it being a one-liner that doesn't fit in
> eighty characters?

Lol. You do not. In fact, this should be syntax error :-D --- as I
guess it would be if it were a lambda expression?
--
https://mail.python.org/mailman/listinfo/python-list
Re: on writing a while loop for rolling two dice [ In reply to ]
On Fri, Sep 3, 2021 at 4:33 AM Hope Rouselle <hrouselle@jevedi.com> wrote:
> Yeah. Here's a little context. I came across this by processing a list
> of exercises. (I'm teaching a course --- you know that by now, I
> guess.) So the first thing I observed was the equal volume of work
> dedicated to while loops and for loops --- so I decided to compared
> which appeared more often in a certain sample of well-written Python
> code. It turns out the for loop was much more frequent. Students have
> been reporting too much work in too little time, so I decided to reduce
> the number of exercises involving while loops. When I began to look at
> the exercises, to see which ones I'd exclude, I decided to exclude them
> all --- lol! --- except for one. The one that remained was this one
> about rolling dice until a satisfying result would appear. (All other
> ones were totally more naturally written with a for loop.)
>
> So if I were to also write this with a for-loop, it'd defeat the purpose
> of the course's moment. Besides, I don't think a for-loop would improve
> the readability here.

It's on the cusp. When you ask someone to express the concept of "do
this until this happens", obviously that's a while loop; but as soon
as you introduce the iteration counter, it becomes less obvious, since
"iterate over counting numbers until this happens" is a quite viable
way to express this. However, if the students don't know
itertools.count(), they'll most likely put in an arbitrary limit (like
"for c in range(100000000)"), which you can call them out for.

> But I thought your protest against the while-True was very well put:
> while-True is not too readable for a novice. Surely what's readable or
> more-natural /to someone/ is, well, subjective (yes, by definition).
> But perhaps we may agree that while rolling dice until a certain
> success, we want to roll them while something happens or doesn't happen.
> One of the two. So while-True is a bit of a jump. Therefore, in this
> case, the easier and more natural option is to say while-x-not-equal-y.

That may be the case, but in Python, I almost never write "while
True". Consider the two while loops in this function:

https://github.com/Rosuav/shed/blob/master/autohost_manager.py#L92

Thanks to Python's flexibility and efficient compilation, these loops
are as descriptive as those with actual conditions, while still
behaving exactly like "while True". (The inner loop, "more pages",
looks superficially like it should be a for loop - "for page in
pages:" - but the data is coming from successive API calls, so it
can't know.)

> I don't see it. You seem to have found what we seem to agree that it
> would be the more natural way to write the strategy. But I can't see
> it. It certainly isn't
>
> --8<---------------cut here---------------start------------->8---
> def how_many_times_1():
> c, x, y = 0, None, None
> while x != y:
> c = c + 1
> x, y = roll()
> return c, x, y
> --8<---------------cut here---------------end--------------->8---
>
> nor
>
> --8<---------------cut here---------------start------------->8---
> def how_many_times_2():
> c, x, y = 0, None, None
> while x == y:
> c = c + 1
> x, y = dados()
> return c, x, y
> --8<---------------cut here---------------end--------------->8---
>
> What do you have in mind? I couldn't see it.

You're overlaying two loops here. One is iterating "c" up from zero,
the other is calling a function and testing its results. It's up to
you which of these should be considered the more important, and which
is a bit of extra work added onto it. With the counter as primary, you
get something like this:

for c in itertools.count():
x, y = roll()
if x == y: return c, x, y

With the roll comparison as primary, you get this:

c, x, y = 0, 0, 1
while x != y:
x, y = roll()
c += 1
return c, x, y

Reworking the second into a do-while style (Python doesn't have that,
so we have to write it manually):

c = 0
while "x and y differ":
x, y = roll()
c += 1
if x == y: break
return c, x, y

And at this point, it's looking pretty much identical to the for loop
version. Ultimately, they're all the same and you can pick and choose
elements from each of them.

ChrisA
--
https://mail.python.org/mailman/listinfo/python-list

1 2 3  View All