Q:

Given a string, return n distinct characters with their respective counts, as well as the remainder of the string

Going through some old code, I found this function.
Given a string carrier and integer nsyms, it consumes nsyms distinct characters from the carrier string and returns a dictionary containing consumed characters and their respective counts, and the remainder of the carrier string.
def enc_consume(carrier, nsyms):
  chrs = defaultdict(int)

  i = 0  # see this?

  for i,c in enumerate(carrier):
    # `i` is not used in the loop
    chrs[c] += 1
    if len(chrs) == nsyms:
      break

  return chrs, carrier[i+1:]  # but it's used here

How would you rewrite this? I found the assignment i = 0 confusing, since it was followed by a for i..., which will of course do the assignment as well.

EDIT: The point of the explicit assignment i = 0 is that if carrier is empty, the loop will not execute. If it weren't for the assignment, i would be undefined by the time it is used in return.

A:

I would avoid using indexes altogether and rely on iter to advance character by character and remember what's left in the string:
def enc_consume(carrier, nsyms):
    characters = defaultdict(int)
    stream = iter(carrier)

    for char in stream:
        characters[char] += 1
        if len(characters) >= nsyms:
             break

    return characters, ''.join(stream)

A:

def enc_consume(carrier, nsyms):
    first_unused_idx = 0
    char_cnt = defaultdict(int)
    while first_unused_idx < len(carrier) and len(char_cnt) < nsyms:
        char_cnt[carrier[first_unused_idx]] += 1
        first_unused_idx += 1 
    return char_cnt, carrier[first_unused_idx:]

I'd suggest using a while loop here because it would make declaring the counter outside the loop natural and the number of iterations is not known beforehand (which is a common use case for a while loop).

A:

Instead of a defaultdict(int), consider using a Counter instead.  The code in this function would otherwise be identical, but the Counter would more clearly convey your intentions.  Furthermore, Counter methods such as .most_common() might be useful to the caller.

