New teacher in town project under LEARN INTERMEDIATE PYTHON 3

hi can anyone please share the code of classroom_organizer.py? I stucked at task 3 and apparently the solution is not provided and the support of Codecademy can only help with asking me to check the forum.

Thank you.

import itertools
from roster import student_roster
# Import modules above this line
class ClassroomOrganizer:
  index = 0
  def __init__(self):
    self.sorted_names = self._sort_alphabetically(student_roster)
    ClassroomOrganizer.index += 1
  def __iter__(self):
    return self


  def _sort_alphabetically(self,students):
    names = []
    for student_info in students:
      name = student_info['name']
      names.append(name)
    return sorted(names)
    _sort_alphabetically()

  def get_students_with_subject(self, subject):
    selected_students = []
    for student in student_roster:
        if student['favorite_subject'] == subject:
            selected_students.append((student['name'], subject))
    return selected_students
    get_students_with_subject()
studentRoster = ClassroomOrganizer()

That’s a bit of a specific request that may not get a reply (I don’t even recognise the name) and solutions are generally frowned on here anyway as this is a learning environment. You might get a better response if you can turn this into a code based query. See How to ask good questions (and get good answers) for an idea of how to set this up.

the project is under learn intermediate python3. I posted parts of my code. it’s just so frustrating to be stucked and found no solution provided

3 Likes

Would be worth linking the project. What is the actual issue with this question, like what are you trying to do and what have you attempted so far. Any idea why it might not be working?

There’s really not much to go on here. Follow the guidelines, get a proper question going.

figured out

#script.py
from roster import student_roster
from classroom_organizer import ClassroomOrganizer
import itertools
student_roster_iter = iter(student_roster)
#task 1
# for i in range(10):
#   print(next(student_roster_iter))
#task 4
y = ClassroomOrganizer()
#print(y.student_combination())
#task 5
Student_Math_Science = y.get_students_with_subject("Math") + y.get_students_with_subject("Science")
print(Student_Math_Science)
print(list(itertools.combinations(Student_Math_Science, 4)))

#classroom_organizer.py
#task 2
from roster import student_roster
import itertools

# Import modules above this line
class ClassroomOrganizer:
  def __init__(self):
    self.sorted_names = self._sort_alphabetically(student_roster)

  def _sort_alphabetically(self,students):
    names = []
    for student_info in students:
      name = student_info['name']
      names.append(name)
    return sorted(names)
  #task 4
  def student_combination(self):
    res = list(itertools.combinations(self.sorted_names, 2))
    return res

  def get_students_with_subject(self, subject):
    selected_students = []
    for student in student_roster:
        if student['favorite_subject'] == subject:
            selected_students.append((student['name'], subject))
    return selected_students

#task 3
# x = ClassroomOrganizer()
# print(x.sorted_names)
# x_iter = iter(x.sorted_names)
# for i in range(10):
#   print(next(x_iter))
5 Likes

i’ve written task 4::

def seat_arrangement(self):
    couplets = list(itertools.combinations(self.sorted_names, 2))
    return couplets

in scirpt.py, i call the method in this way:

student_seats = ClassroomOrganizer()

print(student_seats.seat_arrangement)

my console output is:

<bound method ClassroomOrganizer.seat_arrangement of <classroom_organizer.ClassroomOrganizer object at 0x7f7e59b5c438>>

i’ve gone back to consult the Combinatoric Iterator: Combinations exercise, which works. i don’t understand why the seat arrangement are not printing to the console.

i’d appreciate some feedback.

ps: it would help if you posted the solutions in the exercises, so i can learn on my own pace if i choose. just because the solutions are posted doesn’t take away from the learning environment. not everyone learns in the same way.

There’s still no link to this lesson so it’s harder to work out what your target was, would be great if you added a link. If you look at the details of what you printed out though it mentions that it’s a bound method object. So this code has printed out the method object itself, this is more like printing a function object, e.g.

def func(x):
    return x

print(func)
Out: <function func at 0x7fb730b>

Can you pick up on what’s missing here and consequently in your own example?

i forgot to post the link to the exercise:

https://www.codecademy.com/courses/learn-intermediate-python-3/projects/new-teacher-in-town-project.

i understand the output. i’d expect this output if i hadn’t enclosed the statement in a list() function. but since i had, i expected it to unpack the coupled seat arrangements.

in your template, x is not being sent to the function. as a result, it’s printing the function object. the reason why i haven’t sent “x” in my function is that the combinations() iterator is acting upon a class attribute, so nothing is being passed to the function. when i first attempted to solve this problem, i wrote the function differently, for which i received the same output:

def seat_arrangement(self, students):
   couplets = []
   self.seats = itertools.combinations(studentss, 2)
   for seats in self.seats:
      couplets.append(seats)
   return couplets

which seams wrong (i am not sending a parameter) and redundant given i already have access to self.sorted_names.

thanks for your help.

1 Like

Ah, sorry. Perhaps a little too cryptic. I should’ve expanded on that example a little. The issue is that you have a bound method, but it never gets called. There’s a big difference between print(func) and print(func(1)). Without the call you’re printing details about the object instead of what you want the object to return.

The content of the seat_arrangement function won’t even execute if you do not call the function so this has nothing to do with the itertools functions you’re using. Even a method that takes no arguments must be called e.g. obj.method() for it to provide you with a returned value.

i am laughing at myself. while i was doing this example, i called the function:

student_seats.seat_arrangement()

and received an error, so i thought that was part of the problem i created, so i never put the parens back. of course, the problem was somewhere else. i’m still getting use to the differences between python and c++. i still catch myself using types and semicolons, etc. this is why you shouldn’t take 3-year breaks from programming. thanks for your help.

1 Like

i have a question about unpacking lists from itertools.comabinations().

if i use this method:

math_students = [student_seats.get_students_with_subject("Math")]

print(math_students)

print()

science_students = [student_seats.get_students_with_subject("Science")]

print(science_students)

print()

math_science_students = list(itertools.chain(math_students, science_students))

print(math_science_students)

print()

math_science_quads = itertools.combinations(math_science_students, 4)

print(list(math_science_quads))

print()

i get an empty list in the console, but if i use this method:

math_science_quads = student_seats.get_students_with_subject("Math") + student_seats.get_students_with_subject("Science")

print(list(itertools.combinations(math_science_quads, 4)))

i receive the lists unpacked in the console.

i don’t understand why both methods don’t result in the same output. why does the first list return empty?

I understand this is in relation to this lesson but it’s drifted far enough away from the original question that I think it warrants a new thread.

It’s a bit hard to tell without knowing what all these lists actually contain, any chance of a working example? The only thing I will note is that combinations only works if there are at least as many elements in the iterable as there are in the number of combinations requested. Have you perhaps got too many levels of nested list in your first method? It will only iterate through the top level.

If that’s not enough to go on please consider a new thread and perhaps provide some values (even a example would help) of what values like student_seats.get_students_with_subject("Science") actually contain.

i’ll reply here just for the sake of finishing the conversation. the example is from the same exercise.

there is a list of student with a favourite subject:

 math_students = [student_seats.get_students_with_subject("Math")]

print(math_students)

output: [[(‘Karina M’, ‘Math’), (‘Benny D’, ‘Math’), (‘Marisol R’, ‘Math’)]]

science_students = [student_seats.get_students_with_subject("Science")]

print(science_students)

output: [[(‘Alex C’, ‘Science’), (‘Trudy B’, ‘Science’)]]


math_science_students = itertools.chain(math_students, science_students)

print(list(math_science_students))

output: [[(‘Karina M’, ‘Math’), (‘Benny D’, ‘Math’), (‘Marisol R’, ‘Math’)], [(‘Alex C’, ‘Science’), (‘Trudy B’, ‘Science’)]]

so, there are 5 students: 3 math and 2 science

so there enough elements in the list to make combinations of 4, but

math_science_quads = itertools.combinations(math_science_students, 4)

print(list(math_science_quads))

output: [ ]

this approach:

math_science_quads = student_seats.get_students_with_subject("Math") + student_seats.get_students_with_subject("Science")

print(list(itertools.combinations(math_science_quads, 4)))

results in this output: [((‘Karina M’, ‘Math’), (‘Benny D’, ‘Math’), (‘Marisol R’, ‘Math’), (‘Alex C’, ‘Science’)), ((‘Karina M’, ‘Math’), (‘Benny D’, ‘Math’), (‘Marisol R’, ‘Math’), (‘Trudy B’, ‘Science’)), ((‘Karina M’, ‘Math’), (‘Benny D’, ‘Math’), (‘Alex C’, ‘Science’), (‘Trudy B’, ‘Science’)), ((‘Karina M’, ‘Math’), (‘Marisol R’, ‘Math’), (‘Alex C’, ‘Science’), (‘Trudy B’, ‘Science’)), ((‘Benny D’, ‘Math’), (‘Marisol R’, ‘Math’), (‘Alex C’, ‘Science’), (‘Trudy B’, ‘Science’))]

i made an example, which also returns empty:

numbers = [4, 7, 2, 9, 4, 6, 1, 9, 4]

letters = ['s', 'f', 'h', 'e', 'u']

nls = itertools.chain(numbers, letters)

print(list(nls))

nls_combos = itertools.combinations(nls, 4)

print(list(nls_combos))

this also prints nothing (with and without list():

nls_combos = list(itertools.combinations(nls, 4))

for combo in nls_combos:
  print(combo)

this works fine, so i wonder if the using chain before combinations is an issue:

numbers = [4, 7, 2, 9, 4, 6, 1, 9, 4]

test_combo = itertools.combinations(numbers, 4)

for test in test_combo:
 print(test)

I don’t have the option to split to new threads sadly. Pretty sure your issue is as mentioned above with having too many levels of nesting.

Have another look at your the values in your first method-

math_students = [[(‘Karina M’, ‘Math’), (‘Benny D’, ‘Math’), (‘Marisol R’, ‘Math’)]]
# or if a slightly more readable format-
math_students = [
    [
        ('Karina M', 'Math'),
        ('Benny D', 'Math'),
        ('Marisol R', 'Math')
    ]
]

A list which contains a list which contains 3 elements which are tuples.

hi,

actually, i did notice this, but i wasn’t sure how to verbalize it or undo the nesting from within the function, if that makes sense. is there a way to automatically “delist” or “un-nest” a list, so it only have one layer. or is that something that has to be done manually?

is there a way to transform math_students = list(list(tuples))) —> math_students = list() using some notation rather than re-assigning math_students to a list manually? or is preventing the nesting from occurring in the first place. is this occurring as a result of a redundant coding practice on my part?

There are some routes to doing this, for example there is the * unpacking/splat operator or https://docs.python.org/3/library/itertools.html#itertools.chain.from_iterable along with standard loops or similar things. I think the easiest might be just to avoid nesting it in the first place though.

If you compare your example you’ll find you’re adding the extra levels yourself. Consider whether or not you actually want to do that before worrying about unpacking anything.

math_students = [student_seats.get_students_with_subject("Math")]
science_students = [student_seats.get_students_with_subject("Science")]
math_science_quads = math_students + science_students

math_science_quads = (
    student_seats.get_students_with_subject("Math")
    + student_seats.get_students_with_subject("Science")
)

thank you for your help. i appreciate your time.

What’s the correct way to do the __iter__ and __next__ methods in the project?

I came up with some options, but I don’t know which one we’re supposed to use for the project:

Option 0: copy iterator from .sorted_names

  def __iter__(self):
    return iter(self.sorted_names)

Option 1: make the class an iterator and use an integer index

  def __iter__(self):
    self.index = 0
    return self

  def __next__(self):
    self.index += 1
    if self.index >= len(self.sorted_names):
      raise StopIteration
    return self.sorted_names[self.index]

Option 2: using another iterator

  def __iter__(self):
    self._iterator = iter(self.sorted_names)
    return self._iterator

  def __next__(self):
    return next(self._iterator)

I’d guess option 1.
Does anyone know which one is the best one?

1 Like

I don’t know that there’s a correct answer per se. The iterator protocol simply requires a valid iterator is returned from the __iter__ method. Unless you have to implement __next__ yourself personally I think option 0 is the nicest choice.

Defining an entirely new iterator or making the ClassroomOrganiser type itself an iterator seems like unnecessary boilerplate when all you want is to iterate through an existing container type that you already hold a reference to.

If the project requires you to make ClassroomOrganiser itself an iterator then I suppose that’s what you’d have to do.

I’m also currently working on this project and I too have a question.

import roster
import itertools

students_iterable = iter(roster.student_roster)
for i in students_iterable:
  print(i)

class ClassroomOrganizer:
  def __init__(self):
    self.index = 0
  
  def __iter__(self):
    self.index = 0
    return self

  def __next__(self):
    if self.index < len(roster.student_roster):
      self.index += 1
      return roster.student_roster[self.index]
    else:
      raise StopIteration

students2 = ClassroomOrganizer()
print(next(students2)['name'])

This code runs just fine, but if I comment out the __init__ method for my ClassoroomOrganizer class I get an error telling me

AttributeError: ‘ClassroomOrganizer’ object has no attribute ‘index’

This seems to make sense to me, but the reason I am confused is that in the lessons on this topic there was no __init__ method used when defining a class with a custom iterator, and in the lessons the code ran just fine. Here is the link to that lesson: Link, and here is my passing code for that lesson:

class CustomerCounter:
# Write your code below:
  def __iter__(self):
    self.count = 0
    return self
  
  def __next__(self):
    if self.count > 100:
      raise StopIteration
    else:
      self.count += 1
      return self.count
      

customer_counter = CustomerCounter()
for customer in customer_counter:
  print(customer)

So I’m confused as to whether or not I need to always have an __init__ method defining self.index. It was my understanding that including self.index within the __iter__ method would be sufficient.

Please let me know if I’m misunderstanding something or if I have an error in my code. Thanks.

1 Like