1

I'm trying to find the 5 most recent years without repeating digits, and I keep getting the error 'int' object has no attribute '__getitem__'

This is my code and I so far -I can't figure out what's wrong with it. Any explanation as to how to fix it is appreciated.

def find_year():
    start = 2015
    years = []
    while len(years) < 5:
        if start[0] == start[1]:
            return False
        elif start[0] == start[2]:
            return False
        elif start[0] == start[3]:
            return False
        elif start[1] == start[2]:
            return False
        elif start[1] == start[3]:
            return False
        elif start[2] == start[3]:
            return False
        else:
            years.append(start)
            start -= 1
     else:
          print years

 find_year()
martineau
  • 119,623
  • 25
  • 170
  • 301
hudies
  • 11
  • 2
  • 1
    When you have a collection of N items and want to test they're all different, then instead of making N(N-1)/2 unnecessary equality tests, just assign them to a set/dict/tuple/list and test its length is == N. dict/set is your friend, it has O(1) lookup time, as opposed to O(n) for list/tuple or O(log2 N) for binary-tree. – smci Mar 10 '15 at 01:19
  • possible duplicate of ['int' object has no attribute '\_\_getitem\_\_'](http://stackoverflow.com/questions/11194110/int-object-has-no-attribute-getitem) – Dan Oberlam Mar 14 '15 at 05:27

4 Answers4

5

You are assuming that you can index ([]) an integer to get a digit out of it but that's not something you can do with integers in Python. What you can do is convert an integer into a string, and then you can index the string to get characters out of it:

>>> year = 2015
>>> year_as_string = str(year)
>>> year_as_string[0]
'2'

The reason the error message mentions an attribute __getitem__ is because year[0] basically expands to year.__getitem__(0). Info on __getitem__.

Also I'd advise tackling this in smaller segments. Rather than trying to solve the whole problem at once, start by writing a function that takes one year and will return True if it doesn't have any repeating digits and False otherwise. Then when you've got that working you can write another function calling the first function in a loop to get the five results you need.

There are a couple of nice ways to solve the complete problem:

List comprehension:

>>> [y for y in range(2015, 0, -1) if len(set(str(y))) == 4][0:5]
[2015, 2014, 2013, 1987, 1986]

Iterator:

from itertools import islice
>>> list(islice(filter(lambda y: len(set(str(y))) == 4, range(2015, 0, -1)), 5))
[2015, 2014, 2013, 1987, 1986]

The list comprehension is simpler but does a lot more work (it will try all the years in the range, only to throw out all but the first five); the iterator version is not as straightforward but (in Python 3) only does as much work as it needs to.

Andrew Magee
  • 6,506
  • 4
  • 35
  • 58
3

It's a one-liner with a list comprehension:

[year for year in range(2015, 1915, -1) if len(set(str(year)))==4] [0:5]

[2015, 2014, 2013, 1987, 1986]

You can test for no repeated digit with len(set(str(year)))==4. And range(2015, 1915, -1) bounds the number of years we need to test. Then we just slice the result with [0:5] to take the first five such years (in decreasing order from 2015).

smci
  • 32,567
  • 20
  • 113
  • 146
0

This may work, try it:

    >> from collections import Counter

    >> year = 2015
    >> list = map(int, str(year))
    >> counts = Counter(list)
    >> counts
      Counter({0: 1, 1: 1, 2: 1, 5: 1})
    >>Counter(list).values()
     [1,1,1,1]

The last method will count the occurrence of each digits in the year. This should be more efficient. Now if you want to know if there is a repeated digit, use this post

>> import bisect

>> bisect.bisect(Counter(list).values(), 1)
4

Any value under 4 means there is a digit that has more than one occurrence.

Community
  • 1
  • 1
Yasel
  • 2,920
  • 4
  • 40
  • 48
  • 3
    Umm... `collections.Counter` seems overkill here - just run the digits through a `set` and check it's the same length as the input. `year = 2015; str_year = str(year); if len(str_year) == len(set(str_year)) # no repeated digits` – Jon Clements Mar 10 '15 at 00:25
0

The problem is that you can't access the digits of an integer using [] indexing notation. An easy way to do that to convert it into a sequence of digits, and an easy way to check for duplicates is by converting that sequence into a set and checking whether the length of the set is the same a the number of digits, it won't be if there are any duplicates — see this answer to another related question.

The code below does this looking both at prior and future years from the starting year. It also checks the starting year, but it's not clear from your question if it should do this or not.

def has_dup_digits(year):
    """ Check if integer year has any repeated digits.
    """
    digits = str(year)
    return len(digits) != len(set(digits))

def find_years(start_year):
    """ Find the 5 years closest to the starting year without repeated digits.
    """
    years = []
    if not has_dup_digits(start_year): # check start year
        years.append(start_year)
    delta = 1
    while len(years) < 5:
        if not has_dup_digits(start_year+delta):
            years.append(start_year+delta)
        if len(years) >= 5:
            break
        if not has_dup_digits(start_year-delta):
            years.append(start_year-delta)
        delta += 1

    print(sorted(years))

find_years(2015)  # --> [2013, 2014, 2015, 2016, 2017]
Community
  • 1
  • 1
martineau
  • 119,623
  • 25
  • 170
  • 301