user5642161
user5642161

Reputation:

Counting and grouping in Python

I was working on a problem in which I had to write a program that will count the number of each item needed for the chefs to prepare. The items that a customer can order are: salad, hamburger, and water. salad:[# salad] hamburger:[# hamburger] water:[# water] for example If order = "hamburger water hamburger" then the function returns "salad:0 hamburger:2 water:1"

My code is :

def ite(order,item):
    v=order.split()
    t=[]
    for salad in v:
        if salad==item:
            t.append(item)
            v.remove(item)
    return len (t)

def item_order(order):
    s=ite(order,'salad')
    h=ite(order,'hamburger')
    w=ite(order,'water')
    x='salad:%s hamburger:%s water:%s' %(s,h,w)
    return x

but when we give the input item_order('water water water'), my program prints

salad:0 hamburger:0 water:2  

instead of

salad:0 hamburger:0 water:3

It works fine if there are no two consecutive words. How can I correct this?

Upvotes: 3

Views: 774

Answers (4)

AlokThakur
AlokThakur

Reputation: 3741

You shouldn't use t.remove() while iterating on it. But why? I tried to simulate your problem

t = ['a', 'b', 'c']
it = iter(it)  # for loop internally use iter() to iterate
it.next()      # it should print 1st element of list which is 'a'
t.remove('a')  #Now list t becomes ['b', 'c']
it.next()      # it should print 2nd element of list which is 'c'
t.remove('b')  #Now list t becomes ['c']
it.next()      #it should print 3rd element of list, But list has only one element
# It throws exception - "StopIteration"

This exception is handled by "for" loop silently, so "for" loop won't iterate on 3rd element.

Upvotes: 2

Sagar Waghmode
Sagar Waghmode

Reputation: 777

You shouldn't do

v.remove(salad)

Remove that line and it will solve your problem.

In [18]: def ite(order,item):
             v=order.split()
             t=[]                        
             for salad in v:
                 if salad==item:
                     t.append(item)
             return len(t)         

In [19]: item_order('salad salad water')
Out[19]: 'salad:2 hamburger:0 water:1'

In [20]: item_order('salad water salad')
Out[20]: 'salad:2 hamburger:0 water:1'

In [21]: item_order('salad water hamburger')
Out[21]: 'salad:1 hamburger:1 water:1'

Reason why it wouldn't work for back to back duplicate elements is, in Python, iterating over a sequence does not implicitly make a copy of the list. The list will be modified. You can make the duplicate copy by v[:],

In [98]: def ite(order,item):
    v=order.split()
    t=[]
    for salad in v[:]:
        if salad==item:
            t.append(item)
            v.remove(item)
    return len(t)

Upvotes: 2

Mike Müller
Mike Müller

Reputation: 85482

Solution

You can use collections.Counter:

from collections import Counter

def item_order(order, items=('salad', 'hamburger', 'water')):
    counter = Counter(order.split())
    return ' '.join(['{}: {}'.format(item, counter.get(item, 0)) for item in items])

print(item_order('water water water'))
print(item_order('water salad, salad'))
print(item_order('water hamburger'))

test it:

print(item_order('water water water'))
print(item_order('water salad, salad'))
print(item_order('water hamburger'))

prints:

salad: 0 hamburger: 0 water: 3
salad: 1 hamburger: 0 water: 1
salad: 0 hamburger: 1 water: 1

Explanation

The items are given as default parameter:

def item_order(order, items=('salad', 'hamburger', 'water')):

This make the function more flexible because you can hand in other items if desired:

def item_order(order, items=('salad', 'fruit', 'water')):

The use of a tuple is intentional here because mutable default parameters such as a list may cause unintentional side effects. No problem here but could be the vase in general.

After splitting the input string at white spaces into a list, Counter will create a new counter instance:

counter = Counter(order.split())

For example:

>>> Counter('water water salad'.split())
Counter({'salad': 1, 'water': 2})

Finally, a list comprehension helps to create anew string:

' '.join(['{}: {}'.format(item, counter.get(item, 0)) for item in items])

The ' '.join makes a new string form a list of strings, where the list elements are separated by white space. For example:

>>> ' '.join(['abc', 'xyz', 'uvw'])
'abc xyz uvw'

The method get() of the Python dictionary returns the value for the key if the key is in it, otherwise the default value. For example:

>>> d = {'a': 100, 'b': 200}
>>> d.get('a', 0)
100
>>> d.get('x', 0)
0

Setting this default to 0, gives a zero count for items not contained in the order:

counter.get(item, 0))

Finally, the format() method helps to put the value for the count in a string. For example:

>>> '{}: {}'.format('abc', 10)
'abc: 10'

Upvotes: 4

Lewis Fogden
Lewis Fogden

Reputation: 524

Altering a list while iterating over it is generally a bad idea - i.e. have you removed an item python has already iterated over or not?

The list object has a count method that may help!

split_order = order.split(" ")
salads = split_order.count('salad')

https://docs.python.org/2/tutorial/datastructures.html

Upvotes: 0

Related Questions