views:

428

answers:

18

What I'm looking for is the best way to say, 'If this list is too short, lengthen it to 9 elements and add 'Choice 4', 'Choice 5', etc, as the additional elements. Also, replace any 'None' elements with 'Choice x'.' It is ok to replace "" and 0 too.

An example transformation would be

['a','b',None,'c']

to

['a','b','Choice 3','c','Choice 5','Choice 6','Choice 7','Choice 8','Choice 9']

My initial code abused try/except and had an off-by-one error I didn't notice; thanks to joeforker and everyone who pointed it out. Based on the comments I tried two short solutions that test equally well:

def extendChoices(cList):
  for i in range(0,9):
    try:
      if cList[i] is None:
        cList[i] = "Choice %d"%(i+1)
    except IndexError:
      cList.append("Choice %d"%(i+1)

and

def extendChoices(cList):
  # Fill in any blank entries
  for i, v in enumerate(cList):
    cList[i] = v or "Choice %s" % (i+1)

  # Extend the list to 9 choices  
  for j in range(len(cList)+1, 10):
    cList.append("Choice %s" % (j))

I think #2 wins as being more pythonic, so it's the one I'll use. It's easy to understand and uses common constructs. Splitting the steps is logical and would make it easier for someone to understand at a glance.

+3  A: 

I think I'd do something pretty much like that, but with a few tidy ups:

for i in range(0,10):
  try:
    if choicesTxt[i] is None:
      choicesTxt[i] = "Choice %d"%i
  except IndexError:
    choicesTxt.append("Choice %d"%i)

Of which the only important two are to only catch IndexError rather than any exception, and to index from 0.

And the only real problem with the original would be if choicesTxt is empty, when the choices added will be off by one.

Douglas Leeder
+2  A: 

I think you should treat resizing the array as a separate step. To do so, in the case the array is too short, call choicesTxt=choicesTxt+[None]*(10-len(choicesTxt)). The None choice reasignment can be done using list comprehensions.

Brian
A: 

I would also recommend using xrange instead of range. The xrange function generates the numbers as needed. Range generates them in advance. For small sets it doesn't make much difference, but for large ranges the savings can be huge.

Arcane
In Python 3.0 we don't have to worry about this anymore because range returns an iterator.
joeforker
+1  A: 

What about that (seems to be a dict -- not a list -- when it's incomplete)

a = {1:'a', 2:None, 5:'e'} #test data
[a[x] if x in a and a[x] is not None else 'Choice %d'%x for x in xrange(1,10)]

Edit once more: If it's really a list (not a dict):

b=['a',None,'b']
[b[x] if len(b)>x and b[x] is not None else 'Choice %d'%x for x in xrange(10)]

needs Python 2.5 I think (because of the ternary operator)?

(Thanks to joeforker fixed that it uses keys 1 to 10 and not 0 to 10 anymore; thanks to SilentGhost: in is more pythonic than has_key() or len())

Johannes Weiß
ok, so xrange(1,10)
Johannes Weiß
.has_key() is not pythonic at all
SilentGhost
x in b doesn't make no sense ;)
SilentGhost
thnx ;-). too many edits lead to too many errors...
Johannes Weiß
+1  A: 

If you don't mind replacing anything that evaluates to False with "Choice %d", then result works for Python 2.4.

If you do mind and have Python 2.5 and above then use result2_5_plus with the power of ternary if.

If you don't like or can't use ternary if, then take advantage of the fact that True == 1 and False == 0, using the result of x is None to index a list.

x = ["Blue", None, 0, "", "No, Yelloooow!"]
y = [None]*9

result = [(t or "Choice %d" % (i+1))\ 
        for i, t in enumerate(x + y[len(x):])]

result2_5_plus = [(t if t is not None else "Choice %d" % (i+1))\ 
        for i, t in enumerate(x + y[len(x):])]

result_no_ternary_if = [[t, "Choice %d" % (i+1)][t is None]\
    for i, t in enumerate(x + y[len(x):])]

['Blue', 'Choice 2', 'Choice 3', 'Choice 4', 'No, Yelloooow!', 'Choice 6', 'Choice 7', 'Choice 8', 'Choice 9']
['Blue', 'Choice 2', 0, '', 'No, Yelloooow!', 'Choice 6', 'Choice 7', 'Choice 8', 'Choice 9']
joeforker
but then '', 0 and [] get replaced by "Choice n". Thats not wanted I think
Johannes Weiß
He might want it, the empty string is not a very good choice to show to the user.
joeforker
+6  A: 

My initial reaction was to split the list extension and "filling in the blanks" into separate parts as so:

for i, v in enumerate(my_list):
    my_list[i] = v or "Choice %s" % (i+1)

for j in range(len(my_list)+1, 10):
    my_list.append("Choice %s" % (j))

# maybe this is nicer for the extension?
while len(my_list) < 10:
    my_list.append("Choice %s" % (len(my_list)+1))

If you do stick with the try...except approach, do catch a specific exception as Douglas shows. Otherwise, you'll catch everything: KeyboardInterrupts, RuntimeErrors, SyntaxErrors, ... . You do not want to do that.

EDIT: fixed 1-indexed list error - thanks DNS!

EDIT: added alternative list extension

Alabaster Codify
The second loop should start at len(my_list) + 1; otherwise, the first number will overlap. For example, if my_list is ["Choice 1"], the second loop is range(1, 10), which will append "Choice 1" again, and result in a list of 10 items, not 9.
DNS
A: 
>>> in_list = ["a","b",None,"c"]
>>> new = ['choice ' + str(i + 1) if j is None else j for i, j in enumerate(in_list)]
>>> new.extend(('choice ' +str(i + 1) for i in range(len(new), 9)))
>>> new
['a', 'b', 'choice 3', 'c', 'choice 5', 'choice 6', 'choice 7', 'choice 8', 'choice 9']
SilentGhost
+3  A: 

You could use map (dictionary) instead of list:

choices_map = {1:'Choice 1', 2:'Choice 2', 3:'Choice 12'}
for key in xrange(1, 10):
    choices_map.setdefault(key, 'Choice %d'%key)

Then you have map filled with your data.
If you want a list instead you can do:

choices = choices_map.values()
choices.sort() #if you want your list to be sorted
#In Python 2.5 and newer you can do:
choices = [choices_map[k] for k in sorted(choices_map)]
Abgan
The example transformation preserves the original ordering, sorting values() would lose that. Instead, choices = [choices_map[k] for k in sorted(choices_map.keys())]
Roger Pate
Assuming, that keys in choices_map reflect the order of original list. But you're right, I'll update my post. Thanks.
Abgan
+1  A: 

I'm a little unclear about why you're using range(1, 10); since you're using choicesTxt[i], that ends up skipping the None check for the first element in your list.

Also, there are obviously easier ways to do this if you're creating a new list, but you're asking specifically to add to an existing list.

I don't think this is really cleaner or faster, but it's a different idea for you to think about.

for i, v in enumerate(choicesTxt):
    choicesTxt[i] = v or "Choice " + str(i + 1)

choicesTxt.extend([ "Choice " + str(i) for i in range(len(choicesTxt) + 1, 10) ])
DNS
should be `v is None`
SilentGhost
You mean choicesTxt[i] = "Choice " + str(i + 1) if v is None else v? That would prevent False or "" from triggering it, but he seems to be filling a list with non-False values, so this may be closer to what he wants.
DNS
+1  A: 

I would do

for i, c in enumerate(choices):
    if c is None:
        choices[i] = 'Choice X'

choices += ['Choice %d' % (i+1) for i in range(len(choices), 10)]

which only replaces actual None values (not anything that evaluates as false), and extends the list in a separated step which I think is clearer.

dF
+1  A: 

I find that when list comprehensions get long it's better to just use a standard for loop. Nearly the same as others but anyway:

>>> in_list = ["a","b",None,"c"]
>>> full_list = in_list + ([None] * (10 - len(in_list)))
>>> for idx, value in enumerate(full_list):
...     if value == None:
...             full_list[idx] = 'Choice %d' % (idx + 1)
...
>>> full_list
['a', 'b', 'Choice 3', 'c', 'Choice 5', 'Choice 6', 'Choice 7', 'Choice 8', 'Choice 9', 'Choice 10']
monkut
+1  A: 
choices[:] = ([{False: x, True: "Choice %d" % (i + 1)}[x is None] for i, x in enumerate(choices)] +
  ["Choice %d" % (i + 1) for i in xrange(len(choices), 9)])
Ignacio Vazquez-Abrams
Is that Python or Perl?
Roger Pate
+1  A: 

You could go simpler with a list comprehension:

extendedChoices = choices + ([None] * (10 - len(choices)))
newChoices = ["Choice %d" % (i+1) if x is None else x
    for i, x in enumerate(extendedChoices)]

This appends None to your choices list until it has at least 10 items, enumerates through the result, and inserts "Choice X" if the Xth element is missing.

ojrac
that's better than mine
SilentGhost
In general, whenever I find myself writing a for loop with one line of code in it, I think about what it would look like as a list comprehension.
ojrac
that's true, but I liked algorithm: refill with Nones and replace them afterwards - more dry
SilentGhost
+4  A: 

Unlike zip, Python's map automatically extends shorter sequences with None.

map(lambda a, b: b if a is None else a,
    choicesTxt,
    ['Choice %i' % n for n in range(1, 10)])

You could simplify the lambda to

map(lambda a, b: a or b,
    choicesTxt,
    ['Choice %i' % n for n in range(1, 10)])

if it's okay to treat other false-like objects in choicesTxt the same as None.

ephemient
+1  A: 
def choice_n(index):
  return "Choice %d" % (index + 1)

def add_choices(lst, length, default=choice_n):
  """
  >>> add_choices(['a', 'b', None, 'c'], 9)
  ['a', 'b', 'Choice 3', 'c', 'Choice 5', 'Choice 6', 'Choice 7', 'Choice 8', 'Choice 9']
  """

  for i, v in enumerate(lst):
    if v is None:
      lst[i] = default(i)

  for i in range(len(lst), length):
    lst.append(default(i))

  return lst

if __name__ == "__main__":
  import doctest
  doctest.testmod()
Roger Pate
+2  A: 

Simplest and most pythonic for me is:

repl = lambda i: "Choice %d" % (i + 1) # DRY
print ([(x or repl(i)) for i, x in enumerate(aList)]
     + [repl(i) for i in xrange(len(aList), 9)])
DzinX
+1  A: 

Well in one line:

[a or 'Choice %d' % i for a,i in map(None,["a","b",None,"c"],range(10))]

Though that will replace anything that evaluates to False (e.g. None, '', 0 etc) with "Choice n". Best to replace the "a or 'Choice %d' % i" with a function if yuo don't want that.

The key thing is that map with an argument of None can be used to extend the list to the length needed with None in the required places.

A tidier (more pythonic) version would be:

def extend_choices(lst,length):
    def replace_empty(value,index):
        if value is None:
            return 'Choice %d' % index
        return value
    return [replace_empty(value,index) for value,index in map(None,lst,range(length))]

John Montgomery
A: 

My two cents...

def extendchoices(clist):
    clist.extend( [None]*(9-len(clist)) )
    for i in xrange(9):
        if clist[i] is None: clist[i] = "Choice %d"%(i+1) 
Lord British