Changing the range of the histogram in Python 3.4
Question: Here is a program that displays the histogram of the list below:

    
    
    costlist = [48, 43, 51, 36, 6, 25, 51, 71,
                    59, 70, 78, 36, 18, 84, 5, 9, 13,
                    90, 71, 39, 80, 2, 69, 48, 21,
                    66, 10, 37, 89, 20, 27, 7, 12,
                    314, 83, 39, 31, 36, 56, 60,
                    62, 23, 70, 51, 46, 40, 100,
                    29, 30, 59, 37, 94, 99, 20, 88,
                    10, 36, 42, 14, 24, 33, 60, 370,
                    2, 30, 32, 85, 14, 52, 47, 16,
                    25, 21, 29, 78, 83, 310, 43, 62,
                    54, 83, 74, 52, 65, 82, 44, 94,
                    83, 21, 36, 41, 67, 81, 32, 28,
                    87, 62, 12]
    

the Result is:

    
    
     Element Value  Histogram
        0-9     6
      10-19     9
      20-29    13
      30-39    15
      40-49    10
      50-59     9
      60-69     9
      70-79     7
      80-89    12
      90-99     4
    

However, I want it to output the number of items in each range:

    
    
    Range   Value    Histogram
    1 - 19   4       ****
    20 - 29  5       *****
    30 - 39  0
    40 - 49  0
    50 - 59  0
    60 - 69  5       *****
    70 - 79  10      **********
    80 - 89  0
    90 - 99  0
    100+     3       ***
    

Here is my code:

    
    
    def production_cost():
        costlist = [48, 43, 51, 36, 6, 25, 51, 71,
                    59, 70, 78, 36, 18, 84, 5, 9, 13,
                    90, 71, 39, 80, 2, 69, 48, 21,
                    66, 10, 37, 89, 20, 27, 7, 12,
                    314, 83, 39, 31, 36, 56, 60,
                    62, 23, 70, 51, 46, 40, 100,
                    29, 30, 59, 37, 94, 99, 20, 88,
                    10, 36, 42, 14, 24, 33, 60, 370,
                    2, 30, 32, 85, 14, 52, 47, 16,
                    25, 21, 29, 78, 83, 310, 43, 62,
                    54, 83, 74, 52, 65, 82, 44, 94,
                    83, 21, 36, 41, 67, 81, 32, 28,
                    87, 62, 12]
        return costlist
    def count_scores(scores, low, high):
        if
        return len([x for x in scores if x >= low and x <= high])
    def histogram(costlist):
        d = {'%d-%d'%(x, x + 9):
             count_scores(costlist, x, x + 9) for x in range(0, 100, 10)}
        for k,v in sorted(d.items()):
            print ('%7s %5d'%(k,v))
    def main():
        costlist = production_cost()
        print("%7s %5s %10s" %("Element", "Value", "Histogram"))
        histogram(costlist)
    
    main()
    

My code runs correctly, except it has no asterisks and the ranges are missing
something. The `0-9` and `10-19` should be combined, and there should be an
added one for the last range which is `100+`.

EDIT: Here are the constraints.

  * Do not use if statements 
  * Use at least one def not including main 
  * No imports


Answer: You first need to map each item of `costlist` into its proper histogram bin,
using data "similar" to what `prices` is supposed to return. I say "similar"
because e.g for `a` it computes `1 - 19`, one minus nineteen, which is a
useless `-18`, and so forth.

So, first, change `prices` to return something **useful** instead of those
useless differences, e.g:

    
    
    def prices():
        return [1] + list(range(20, 110, 10))
    

that list the list of lower bounds for the bins (each upper bound is of course
given by the next bin's lower bound). I'm calling `list` on `range` since
you're using Python 3 (in Python 2 you could elide `list).

Simplest way to use these data is to build a `dict` mapping each integer in
range to its bin number (this way looking for the bin given an integer will be
nearly instantaneous, rather than take some time with other representations):

    
    
    p = prices()
    int2bin = {}
    for i in range(1, len(p)):
        for j in range(p[i-1], p[i]):
            int2bin[j] = i - 1
    lastbin = len(p) - 1
    

Now it's trivial to find the bin for each integer, and thus, similarly trivial
to count how many integers fall in each given bin:

    
    
    import collections
    c = collections.Counter(
        int2bin.get(i, lastbin)
        for i in costlist)
    

Added: the OP just commented (though the Q isn't edited accordingly) that
module `collections` is undesired (such constraints should of course always be
spelled out clearly and explicitly in the question in the first place!)
apparently because this is a school exercise.

So, if you need to re-implement `collections.Counter` by hand, of course you
can do that...:

    
    
    c = {}
    for i in costlist:
        thebin = int2bin.get(i, lastbin)
        if thebin in c:
            c[thebin] += 1
        else:
            c[thebin] = 1
    

There -- six statements (counting if/else as one) instead of one (plus the
import), and we have reimplemented `collections.Counter` for this special
case. Personally I think it's best and wisest to use appropriately high levels
of abstraction -- though of course it's also smart to understand what,
conceptually, lies beneath them.

But the concept of **counting** comes so naturally to human beings (and in any
case has been drilled into students ever since first grade) that I don't think
it's necessary to repeat it again in this case!

"If there are already things in the bin and you put another one there, then
add one to the bin's count; if there were no things in the bin yet, so you're
putting the first think in the bin, then start the bin's count at one" -- is
it **truly** worthwhile to ask high schoolers to sit through such drills
**again**?!

Ah well, I'm not trained as a teacher, so I guess I won't rant about how
utterly **bored** I was by exactly such useless repetitions of long-absorbed
concepts throughout **my** time in school -- and I heard exactly the same from
my children back when **they** were in school:-).

Back to actually-fun stuff...:

Now, only the printing task remains, after a slightly different header (the
one you say you want):

    
    
    print("%7s %5s %10s" %("Range", "Value", "Histogram"))
    

You can just loop over the bins:

    
    
    for i, lo in enumerate(p):
        if i + 1 < len(p):
            rng = '%d-%d' % (lo, p[i+1]-1)
        else:
            rng = '%d+' % lo
        val = c[i]
        stars = '*' * val
        print("%7s %5s %-10s" %(rng, val, stars))
    

Putting it all together, you'll see the result:

    
    
      Range Value  Histogram
       1-19    15 ***************
      20-29    13 *************
      30-39    15 ***************
      40-49    10 **********
      50-59     9 ********* 
      60-69     9 ********* 
      70-79     7 *******   
      80-89    12 ************
      90-99     4 ****      
       100+     4 ****      
    

Which seems to be what you're asking for.

There are of course alternatives (e.g, use something else than a `dict` to do
the mapping from integer to bin number) and things that may require
explanation depending on your Python skills, so, feel free to ask further!

Added: so here are the latest extra constraints the OP's been piling up on
this Q: \- No if statements \- Use at least one def not including main \- No
imports

The `def` is already there in `prices`, and there are no `import`s now (in the
non-Counter version). "No `if` statements" is really ridiculous (on a par with
having to stand on just the left foot while coding!-) but fortunately Python
offers tricks to play around such absurdities.

So to build the counter, let's use:

    
    
    def counter(costlist):
        c = {}
        for i in costlist:
            thebin = int2bin.get(i, lastbin)
            try:
                c[thebin] += 1
            except KeyError:
                c[thebin] = 1
    

this is the first `if`-removing trick: instead of the more spontaneous check
whether `thebin` has already been put in the `dict` `c`, here we assume it
has, and handle the exception produced when it hadn't been. This is actually a
well-recognized Python idiom, which I promoted (even before I wrote "Python in
a Nutshell", where I pounded on it:-) as "It's easier to ask forgiveness than
permission", borrowing Commodore Hopper's great motto (see
<https://www.youtube.com/watch?v=AZDWveIdqjY> for my talk on the subject).

And we'll use **exactly** the same trick to remove the _other_ `if`, simply
rephrasing the snippet:

    
    
    for i, lo in enumerate(p):
        if i + 1 < len(p):
            rng = '%d-%d' % (lo, p[i+1]-1)
        else:
            rng = '%d+' % lo
    

with:

    
    
    for i, lo in enumerate(p):
        try:
            rng = '%d-%d' % (lo, p[i+1]-1)
        except IndexError:
            rng = '%d+' % lo
    

Here, it works because, if `(i + 1)` is **not** `< len(p)` as originally
checked by the normal `if`, the indexing `p[i+1]` will raise an `IndexError`,
and the `except` clause deals with that!

Now if I assess the OP correctly, I have two predictions: (A) this will not be
sufficient (more constraints will spring from nothingness, such as "no
try/except statements"!-) and (B) the OP will _still_ not accept this answer
and open another question with **all** the constraints. I suspect I'm spot-on
re (A) because I can't imagine a teacher willing to accept `try/except` if
they forbid the much-simpler `if`; I can just hope I'm wrong on (B), i.e that
the OP will realize they won't get one more bit of time and energy out of me
until and unless they accept this question, and thereby reluctantly click on
that checkmark outline and ask another...:-)

