Python 3 Questions
Members - Reputation: 171
Posted 19 February 2014 - 11:34 AM
GDNet+ - Reputation: 3005
Posted 19 February 2014 - 01:30 PM
I don't know most of these (I don't use Python), but I can answer the odd/even numbers question.
I'll be assuming you meant if (i % 2) == 0, not if (1 % 2) == 0.
The % operator is called modulo. It returns the remainder of a division between the parameters on either side (left parameter divided by right). 3 % 2 is equal to 1, because 3 / 2 leaves 1 as a remainder.
If we modulo with 2 and check if it's equal to 0, that means the division with 2 has no remainder, which is the case for all even numbers.
The "continue" command that is triggered if this occurs makes the for loop skip through to the next iteration of the loop. In the code snippet you posted, all even numbers (which are perfectly divisible by 2) will just skip to the next iteration, without doing anything else.
So, if you want to print even numbers instead, you can simply swap the if (i % 2) == 0 to if (i % 2) == 1.
Edited by Lactose™, 19 February 2014 - 01:30 PM.
Project journal, check it out!
Crossbones+ - Reputation: 8286
Posted 19 February 2014 - 04:57 PM
Are lists with just strings always mutable lists?
Are lists with integers always immutable?
What about combinations?
Mutable implies you can do something to "mutate" it or something. What would that be?
A list in itself is always mutable. Even in a list of integers you can remove, add, or replace elements. What you can't do if the list contains immutable types is change the state of an element in-place (so if you need to, you need to replace it with a modified copy of the original, functional-style).
In other words, a list is mutable, but the elements it contains don't have to be.
Mutable operations are anything that directly affect the object's state. For instance, a function "abc".uppercase() which changes the string from "abc" to "ABC" would make the object mutable. An immutable version of this function would not modify the original string at all but instead return a string equal to "ABC". Immutability has several advantages, in particular, because objects simply cannot change after having been created, it tends to be easier to reason about a program's state over time, equality is well-defined and unencumbered with things like reference comparisons, there is no need to worry about transitivity for immutable types, and natural/mathematical language often translates better to a functional, immutable implementation than to a mutable one. On the other hand, some tasks do not lend themselves well to immutability, and the amount of copies required for immutability can often be problematic, resulting in a mixture of mutable and immutable types where appropriate.
What about combinations?
(Im)mutability, and related attributes from other languages like "constness", are not transitive, which can sometimes appear unintuitive - for instance, if you're given a const std::vector in C++, you can still go ahead and mutate the elements as you please (unless they are marked const themselves, or happen to be immutable) but you can't add or remove or replace elements. More dramatically, if you're given a const pointer to a struct, you can't modify any of the value types contained by the struct pointed to, but any reference types (in general, one level of indirection away, like additional pointers) can be freely mutated if they aren't marked const themselves (and so the struct is not really "const" in the way you might imagine - only the value types directly belonging to it are). Some languages enforce transitive constness/immutability, notably D. C, C++, Python, and most other languages do not, so you need to be careful when designing immutable types as if constness is not transitive, neither is immutability, though that isn't to say that transitivity doesn't have its own set of pitfalls.
Anyway, tl;dr: if X is an "immutable" container storing mutable elements Y, then elements Y can be mutated through X despite X being immutable. And if X is a mutable container storing immutable elements Y, then you can't modify existing elements Y but you can add, remove, or replace them inside X.
Naming a range for a variable results in it not listing the last number "201" in the shell. Why is this?
Guessing here because I'm not too sure what you mean, but ranges are conventionally given with the end of the interval excluded. I.e. range(1, 201) goes from 1 to 200. You see this in a lot of other languages as well, and all over mathematics.
Why do you have to put "[:]" before stating a range value?
listCycle[:] = range(1,201)
That's because the range() function itself does not produce an array or a list, but instead a generator (which is an object). A generator is like an iterator/enumerable which produces a sequence implicitly and one element at a time. This is important, suppose you wanted to iterate from 1 to 1 million. If it were a list you would now have a huge list of 1 million consecutive integers in memory, even though you only need one at a time (and the sequence is perfectly predictable). No need for that - instead, Python has some language support for generators, which are kind of like functions except that they produce a sequence of outputs, via the "yield" keyword. For instance, a simplified version of range could be implemented like this:
def myrange(a, b): i = a while (i < b): yield i # this is the magic bit that says "this is the next element of the sequence" i += 1
Generators in Python are quite important and you'll encounter them (and use them) a lot. Also note that generators don't even have to produce a finite sequence, you can have a generator that produces every natural number without any upper bound, of course code which uses this generator will itself have to have some termination condition.
So to answer your original question, without the slice syntax "[:]" Python thinks you want to assign the generator (as an object) to the variable "listCycle". With the slice syntax Python now knows that listCycle[:] is a list - it can't be anything else - and so the generator is automatically converted into a list by iterating it from start to end. Using "listCycle = list(range(1, 201))" works as well, and is perhaps more natural, especially since the slice syntax doesn't work too well in this situation, and manipulating generators as objects in general is awkward. A Pythonic way to do it would be this instead:
listCycle = [x for x in range(1, 201)] # gives [1, 2, 3, ..., 200]
Which is a list comprehension which basically says: for every item "x" in the sequence produced by range(1, 201), create a list using that value "x" (so it just converts it to a list). This is equivalent to just "list(range(1, 201))", but list comprehensions are more powerful, look:
listCycle = [2 * x - 1 for x in range(1, 201)] # gives [1, 3, 5, 7, 9, ...]
Which produces all odd integers instead (via the mapping x -> 2x - 1). The possibilities are endless!
I'm having trouble understanding "for" statements. What's a good place to learn more about them?
I know you can type;
for i in listCycle:
if (1%2) == 0
to only display the odd numbers. How would you display only even numbers?
You could do something like Lactose suggests, but fortunately you don't have to. List comprehensions let you specify conditions too:
even_numbers_only = [x for x in range(1, 201) if x % 2 == 0] # filter out the odd numbers for i in even_numbers_only: print(i) # prints 2, 4, 6, 8, ...
It might appear verbose or perhaps unnatural, but it is admittedly a contrived example. If you write real programs you'll start noticing plenty of places where an ugly loop could be replaced by a simple list comprehension, or when a bit of nifty Python syntax could tidy up a really nasty piece of code which you would be used to writing out manually in some other languages. Once you "get" how all the pieces of the syntax fit together, idiomatic Python code reads quite fluently.
In general you'll have many, many different ways available to do something, but a few will stand out as being supposedly "Pythonic", in the sense that they are elegant, expressive, use the language features properly instead of brute-forcing their way through the code using raw arrays and integer loops, etc.. most of the time you want to go for those unless you have good reasons not to.
The slowsort algorithm is a perfect illustration of the multiply and surrender paradigm, which is perhaps the single most important paradigm in the development of reluctant algorithms. The basic multiply and surrender strategy consists in replacing the problem at hand by two or more subproblems, each slightly simpler than the original, and continue multiplying subproblems and subsubproblems recursively in this fashion as long as possible. At some point the subproblems will all become so simple that their solution can no longer be postponed, and we will have to surrender. Experience shows that, in most cases, by the time this point is reached the total work will be substantially higher than what could have been wasted by a more direct approach.
- Pessimal Algorithms and Simplexity Analysis