3

This is a part of my homework assignment and im close to the final answer but not quite yet. I need to write a function that counts odd numbers in a list.

Create a recursive function count_odd(l) which takes as its only argument a list of integers. The function will return a count of the number of list elements that are odd, i.e., not evenly divisible by 2.\

>>> print count_odd([])  
0  
>>> print count_odd([1, 3, 5])  
3  
>>> print count_odd([2, 4, 6])  
0  
>>> print count_odd([0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144])  
8  

Here is what i have so far: #- recursive function count_odd -#

def count_odd(l):
    """returns a count of the odd integers in l.
    PRE: l is a list of integers.
    POST: l is unchanged."""
    count_odd=0

    while count_odd<len(l):
        if l[count_odd]%2==0:
            count_odd=count_odd
        else:
            l[count_odd]%2!=0
            count_odd=count_odd+1
    return count_odd

#- test harness  
print count_odd([])  
print count_odd([1, 3, 5])  
print count_odd([2, 4, 6])  
print count_odd([0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144])  

Can u help explain what im missing. The first two test harness works fine but i cant get the final two. Thanks!

dansalmo
  • 11,506
  • 5
  • 58
  • 53
  • 4
    You're missing the recursion. – Ignacio Vazquez-Abrams Nov 20 '10 at 00:15
  • can u help explain how i can do recursion? im new to python and our prof didnt really explain recursion that well. –  Nov 20 '10 at 00:16
  • [Recursion](http://en.wikipedia.org/wiki/Recursion) is not a Python-specific concept. Generally speaking, when the body of your function calls the function itself as part of computation, you are doing recursion. – Santa Nov 20 '10 at 00:19
  • 1
    Read this: http://stackoverflow.com/questions/717725/understanding-recursion . Then try again. – Zeke Nov 20 '10 at 00:22
  • Also, even if there is no recursion in this function, the logic is flawed. Try stepping through it, one line at a time, using the `[2,4,6]` argument and see if you can spot the fault. – Santa Nov 20 '10 at 00:31
  • one minor note about your test harness. you might really want 'assert count_odd([1,3,5]) == 3, "wrong"' and the like. Or if you want to up the wattage... doctest or unittest. – Gregg Lind Nov 21 '10 at 17:45

10 Answers10

4

Since this is homework, consider this pseudo-code that just counts a list:

function count (LIST)
    if LIST has more items
        // recursive case.
        // Add one for the current item we are counting,
        // and call count() again to process the *remaining* items.
        remaining = everything in LIST except the first item
        return 1 + count(remaining)
    else
        // base case -- what "ends" the recursion
        // If an item is removed each time, the list will eventually be empty.
        return 0

This is very similar to what the homework is asking for, but it needs to be translate to Python and you must work out the correct recursive case logic.

Happy coding.

  • +1! This really is the full functional approach of the problem. Not saying I'd do this in Python, but since the teacher asked for recursion... – Vincent Savard Nov 20 '10 at 00:41
  • @justin There should be no "while" and no "counter" variable -- counting is done through adding something ("1" in the above example, but you may need to make it *dependent* upon something) to the *return value* of the next recursive call :-) The only operation you are allowed to do to the list are "tell if there are more items" (`len(list)`), "look at first time" (`list[0]` -- if there is one), and "get the remainder of the list" (`list[1:]` -- everything *but* the first item). For this problem, do *not* use `list[n]`, for n != 0 (e.g. you can't index-iterate the list). –  Nov 20 '10 at 01:02
  • hmmm ok back to the drawing board –  Nov 20 '10 at 01:15
  • @justin If you get the the count version I posted working in Python (so that `count(l) == len(l)`, for every list l), it should just be a matter of adjusting it so it only counts *when* a certain condition is true about the current item. Happy coding. –  Nov 20 '10 at 01:34
  • hmm alright. will try my best. Im a complete beginner to python so it might take some time. Its quite frustrating to learn a new programming language. –  Nov 20 '10 at 01:41
  • what about it can u help me solve this problem? Ive been stuck on it for hours now. –  Nov 20 '10 at 01:45
  • that question was for the poster of this answer. justin - yes, see my answer – jon_darkstar Nov 20 '10 at 01:52
  • This answer is perfect as a hint to towards solving the problem. Being stuck on it for a few more hours after being provided this framework is where the learning happens. Once you understand how to do a simple count recursively, the rest will come. Anything more is just someone else doing your homework for you. – dansalmo Jul 13 '13 at 18:05
2
def count_odd(L):
    return (L[0]%2) + count_odd(L[1:]) if L else 0
satoru
  • 31,822
  • 31
  • 91
  • 141
  • The function could be written in a tail-recursive style e.g., the `fold`-based solution http://stackoverflow.com/questions/4230497/counting-odd-numbers-in-a-list-python/4230797#4230797 Tail-call optimization could be applied to such programs e.g., via trampolining http://stackoverflow.com/questions/4230497/counting-odd-numbers-in-a-list-python/4231908#4231908 The recursion limit in Python is small therefore non-optimized solution won't work even for average-size arrays: `count_odd([1]*1001)`. – jfs Nov 20 '10 at 11:14
  • while its in tail recursive form, i have tested my "fold" and there is no TCO. not surprising - even tho its tail-recursive i know python doesn't support this. question is - how does native reduce get around this? – jon_darkstar Nov 20 '10 at 12:29
  • @jon_darkstar: TCO could be introduced via simple trampolining http://stackoverflow.com/questions/4230497/counting-odd-numbers-in-a-list-python/4231908#4231908 – jfs Nov 20 '10 at 19:02
  • yea i got that much. is what they do with built-in reduce? – jon_darkstar Nov 20 '10 at 19:24
  • @jon_darkstar: `reduce()` doesn't use recursion; see `functools_reduce()` http://svn.python.org/view/python/trunk/Modules/_functoolsmodule.c?view=markup Here's my implementation in python http://stackoverflow.com/questions/147515/least-common-multiple-for-3-or-more-numbers/147539#147539 – jfs Nov 21 '10 at 01:26
1

All of the prior answers are subdividing the problem into subproblems of size 1 and size n-1. Several people noted that the recursive stack might easily blow out. This solution should keep the recursive stack size at O(log n):

def count_odd(series):
    l = len(series) >> 1
    if l < 1:
        return series[0] & 1 if series else 0
    else:
        return count_odd(series[:l]) + count_odd(series[l:])
pjs
  • 18,696
  • 4
  • 27
  • 56
1

Are slices ok? Doesn't feel recursive to me, but I guess the whole thing is kind of against usual idioms (i.e. - recursion of this sort in Python):

def countOdd(l):
    if l == list(): return 0           # base case, empty list means we're done
    return l[0] % 2 + countOdd(l[1:])  # add 1 (or don't) depending on odd/even of element 0.  recurse on the rest

x%2 is 1 for odds, 0 for evens. If you are uncomfortable with it or just don't understand it, use the following in place of the last line above:

   thisElement = l[0]
   restOfList = l[1:]
   if thisElement % 2 == 0: currentElementOdd = 0
   else: currentElementOdd = 1
   return currentElementOdd + countOdd(restOfList)

PS - this is pretty recursive, see what your teacher says if you turn this in =P

>>> def countOdd(l):
...     return fold(lambda x,y: x+(y&1),l,0)
... 
>>> def fold(f,l,a):
...     if l == list(): return a
...     return fold(f,l[1:],f(a,l[0]))
Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
jon_darkstar
  • 16,398
  • 7
  • 29
  • 37
  • `if l == list():` should be replaced by `if not l:`. Built-in `reduce()` function could be mentioned (it is named `fold()` in your code). – jfs Nov 20 '10 at 07:18
  • Actually, the whole thing is just a one-liner: `count_odd = lambda l: l[0]%2 + count_odd(l[1:]) if l else 0`. Now that's what I'd call recursive. ;-) – martineau Nov 20 '10 at 10:13
  • @martineau: @Satoru.Logic already provided that variant http://stackoverflow.com/questions/4230497/counting-odd-numbers-in-a-list-python/4230950#4230950 – jfs Nov 20 '10 at 11:22
  • @J.F. Sebastian: Same logic, but two lines. – martineau Nov 20 '10 at 11:35
  • im well aware of reduce, but it wouldnt LOOK too recursive if i used that would it? i chose the name "fold" exactly so i wouldn't redefine reduce as i did not want to overwrite the two argument form. – jon_darkstar Nov 20 '10 at 11:37
  • concerning first two comments - yes those are cool ideas that i didn't think of initially. i enjoyed reading about them both in @Satoru.Logic answer hours ago. still, i don't think that means portions of my answer "should be replaced" – jon_darkstar Nov 20 '10 at 11:52
  • @jon_darkstar: `if l == list():` is not idiomatic Python regardless the algorithm you use. – jfs Nov 20 '10 at 19:00
  • yes. 'if not l' is better. i saw that when Satori posted an answer hours before your comment. you're just carping – jon_darkstar Nov 20 '10 at 19:22
0

I would write it like this:

def countOddNumbers(numbers): 
    sum = 0
    for num in numbers:
        if num%2!=0:
            sum += numbers.count(num)
    return sum 
pascalhein
  • 5,700
  • 4
  • 31
  • 44
0

not sure if i got your question , but as above something similar:

def countOddNumbers(numbers): 
    count=0
    for i in numbers:
        if i%2!=0:
            count+=1
    return count
petr jandak
  • 11
  • 1
  • 1
  • The assignment was to use a recursive function. This solution, while it appears correct (I did not run it), does not use recursion so this does not answer the question. Also if you want a non-recursive solution consider the reduce built in function. – Dale Wilson Jan 29 '14 at 17:24
0

The goal of recursion is to divide the problem into smaller pieces, and apply the solution to the smaller pieces. In this case, we can check if the first number of the list (l[0]) is odd, then call the function again (this is the "recursion") with the rest of the list (l[1:]), adding our current result to the result of the recursion.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • hmm ok it makes sense to divide it into smaller pieces. now i gotta try to incorporate it into my code. –  Nov 20 '10 at 00:21
0
def count_odd(series):
    if not series:
        return 0
    else:
        left, right = series[0], series[1:]
        return count_odd(right) + (1 if (left & 1) else 0)
hughdbrown
  • 47,733
  • 20
  • 85
  • 108
0

Tail recursion

def count_odd(integers):
    def iter_(lst, count):
        return iter_(rest(lst), count + is_odd(first(lst))) if lst else count
    return iter_(integers, 0)

def is_odd(integer):
    """Whether the `integer` is odd."""
    return integer % 2 != 0 # or `return integer & 1`

def first(lst):
    """Get the first element from the `lst` list.

    Return `None` if there are no elements.
    """
    return lst[0] if lst else None

def rest(lst):
    """Return `lst` list without the first element."""
    return lst[1:]

There is no tail-call optimization in Python, so the above version is purely educational.

The call could be visualize as:

count_odd([1,2,3])    # returns
iter_([1,2,3], 0)      # could be replaced by; depth=1
iter_([2,3], 0 + is_odd(1)) if [1,2,3] else 0 # `bool([1,2,3])` is True in Python
iter_([2,3], 0 + True) # `True == 1` in Python
iter_([2,3], 1)        # depth=2
iter_([3], 1 + is_odd(2)) if [2,3] else 1
iter_([3], 1 + False)  # `False == 0` in Python
iter_([3], 1)          # depth=3
iter_([], 1 + is_odd(3)) if [3] else 1
iter_([], 2)           # depth=4
iter_(rest([]), 2 + is_odd(first([])) if [] else 2 # bool([]) is False in Python
2 # the answer

Simple trampolining

To avoid 'max recursion depth exceeded' errors for large arrays all tail calls in recursive functions can be wrapped in lambda: expressions; and special trampoline() function can be used to unwrap such expressions. It effectively converts recursion into iterating over a simple loop:

import functools

def trampoline(function):
    """Resolve delayed calls."""
    @functools.wraps(function)
    def wrapper(*args):
        f = function(*args)
        while callable(f):
            f = f()
        return f
    return wrapper

def iter_(lst, count):
    #NOTE: added `lambda:` before the tail call
    return (lambda:iter_(rest(lst), count+is_odd(first(lst)))) if lst else count

@trampoline
def count_odd(integers):
    return iter_(integers, 0)

Example:

count_odd([1,2,3])
iter_([1,2,3], 0)         # returns callable
lambda:iter_(rest(lst), count+is_odd(first(lst))) # f = f()
iter_([2,3], 0+is_odd(1)) # returns callable
lambda:iter_(rest(lst), count+is_odd(first(lst))) # f = f()
iter_([3], 1+is_odd(2))   # returns callable
lambda:iter_(rest(lst), count+is_odd(first(lst))) # f = f()
iter_([], 1+is_odd(3))
2                         # callable(2) is False
jfs
  • 399,953
  • 195
  • 994
  • 1,670
-1

Generator can give quick result in one line code:

sum((x%2 for x in nums))
ArtOfCode
  • 5,702
  • 5
  • 37
  • 56
  • What are you adding in value to the existing answers? The OP was clearly about learning with recursion as one of the goals, and yet your answer doesn't address that fundamental aspect of the OP at all. – Shawn Mehan Oct 10 '15 at 17:50