7

So i'm green as grass and learning programming from How to think like a computer scientist: Learn python 3. I'm able to answer the question (see below) but fear i'm missing the lesson.

Write a function (called insert_at_end) that will pass (return the bold given the two arguments before) for all three:

test(insert_at_end(5, [1, 3, 4, 6]), **[1, 3, 4, 6, 5]**)
test(insert_at_end('x', 'abc'),  **'abcx'**)
test(insert_at_end(5, (1, 3, 4, 6)), **(1, 3, 4, 6, 5)**)

The book gives this hint:"These exercises illustrate nicely that the sequence abstraction is general, (because slicing, indexing, and concatenation are so general), so it is possible to write general functions that work over all sequence types.".

This version doesn't have solutions on-line (that i could find) but in I found someone's answers to a previous version of the text (for python 2.7) and they did it this way:

def encapsulate(val, seq):
    if type(seq) == type(""):
        return str(val)
    if type(seq) == type([]):
        return [val]
    return (val,)

def insert_at_end(val, seq): 
    return seq + encapsulate(val, seq)

Which seems to be solving the question by distinguishing between lists and strings... going against the hint. So how about it Is there a way to answer the question (and about 10 more similar ones) without distinguishing? i.e not using "type()"

1
  • 1
    I don't think you will learn anything useful from trying to solve this problem. Commented Jan 19, 2012 at 20:23

9 Answers 9

3

My best effort:

def insert_at_end(val, seq):
    t = type(seq)
    try:
        return seq + t(val)
    except TypeError:
        return seq + t([val])

This will attempt to create the sequence of type(seq) and if val is not iterable produces a list and concatenates.

Sign up to request clarification or add additional context in comments.

1 Comment

test(insert_at_end('xyz', ['abc']), **['abc','xyz']**) wouldn't pass.
2

I'd say that the example isn't symetric, meaning that it asks the reader handle two different cases:

  • int, list
  • str, str

In my opinion, the exercise should ask to implement this:

  • list, list: insert_at_end([5], [1, 3, 4, 6])
  • str, str: insert_at_end('x', 'abc')

In this case, the reader has to work only with two parameters that use the same sequence type and the hint would make much more sense.

2 Comments

I don't agree - it's not the sequence type that's a problem - it's whether the new value is iterable or not.
I think, the whole problem is that the str (or unicode) work differently than list, tuple or whatever. Otherwise str(['a', 'b', 'c']) would produce 'abc' and not "['a', 'b', 'c']"...
1

This is not a solution but rather an explanation why a truly elegant solution does not look possible.

  • + concatenates sequences, but only sequences of same type.
  • values passed as first argument to insert_at_end are 'scalar', so you have to convert them to the sequence type that the second argument has.
  • to do that, you cannot simply call a sequence constructor with a scalar argument and create a one-item sequence of that type: tuple(1) does not work.
  • str works differently than other sequence types: tuple(["a"]) is ("a",), list(["a"]) is ["a"], but str(["a"])) is "['a']" and not "a".

This renders + useless in this situation, even though you can easily construct a sequence of given type cleanly, without instanceof, just by using type().

You can't use slice assignment, too, since only lists are mutable.

In this situation, the solution by @Hamish looks cleanest.

3 Comments

Hamish's solution is fairly clean, but I think it goes badly wrong if you try to insert a multi character string at the end of a list of strings.
@Duncan: well, with lists, you can add a nested list, but there's no such thing as a nested string. To properly prevent it you either need to explicitly check for str (which defeats the purpose) or have a static type system powerful enough to forbid nested lists (which takes a different language than Python).
@9000 Excellent Thank you, essential (more then a specific answer ) i was looking to confirm that as you put it "a elegant solution doesn't not look possible". I was worried i was missing an underlying concept that might later hound me.
1

That problem is one of a long list and the hint applies to all of them. I think it is reasonable that, having written the encapsulate function which can be re-used for things like insert_at_front, the rest of the implementation is type agnostic.

However, I think a better implementation of encapsulate might be:

def encapsulate(val, seq):
    if isinstance(seq, basestring):
        return val
    return type(seq)([val])

which handles a wider range of types with less code.

3 Comments

Upside: this solution works. Downside: it's not a nice type-agnostic solution that uses the common interface of sequences; instead, it special-cases some magical class.
@9000, strings often need to be special cased, they are the only sequence that only contain objects of the same type as themselves and that messes up a lot of otherwise clean duck typed code. Otherwise it handles list, tuple, their subclasses and any other sequence that follows the general model that it can be constructed from a list.
yes, strings need to be special-cased, and this is the grim truth that prevents this problem from having an elegant type-agnostic, fully interface-based solution.
0

The challenge with this question (in Python 2.7, I'm testing 3.2 right now to verify) is that two of the possible input types for seq are immutable, and you're expected to return the same type as was passed in. For strings, this is less of an issue, because you could do this:

return seq + char

As that will return a new string that's the concatenation of the input sequence and the appended character, but that doesn't work for lists or tuples. You can only concatenate a list to a list or a tuple to a tuple. If you wanted to avoid "type" checking, you could get there with something like this:

if hasattr(seq, 'append'): # List input.
  seq.append(char)
elif hasattr(seq, 'strip'): # String input.
  seq = seq + char
else: # Tuple
  seq = seq + (char,)

return seq

That's really not much different from actually checking types, but it does avoid using the type function directly.

Comments

0

This solution still requires some separate code for strings as opposed to lists/tuples, but it is more concise and doesn't do any checking for specific types.

def insert_at_end(val, seq):
    try:
        return seq + val
    except TypeError:   # unsupported operand type(s) for +
        return seq + type(seq)([val])

1 Comment

how about: assert insert_at_end(['val'], ['seq']) == ['seq', ['val']] ?
0

Maybe this is nearer the answer:

def genappend(x, s):
    if isinstance(s, basestring):
        t = s[0:0].join
    else:
        t = type(s)
    lst = list(s)
    lst.append(x)
    return t(lst)

print genappend(5, [1,2,3,4])    
print genappend(5, (1,2,3,4))
print genappend('5', '1234')

There could also be completely user-defined sequence types. They will also work as long as convertable to and from a list. This also works:

print genappend('5', set('1234'))

Comments

0

I agree that the point is if item is iterable or not.

So my solution would be this:

def iterate(seq, item):
    for i in seq:
        yield i
    yield item

def insert_at_end(seq, item):
    if hasattr(item, '__iter__'):
        return seq + item
    else:
        return type(seq)(iterate(seq, item))

Example:

>>> insert_at_end('abc', 'x')
'abcx'
>>> insert_at_end([1, 2, 4, 6], 5)
[1, 2, 4, 6, 5]
>>> insert_at_end((1, 2, 4, 6), 5)
(1, 2, 4, 6, 5)

Since insert_at_end can handle iterable and not, works fine even with:

>>> insert_at_end('abc', 'xyz')
'abcxyz'
>>> insert_at_end([1, 2, 4, 6], [5, 7])
[1, 2, 4, 6, 5, 7]
>>> insert_at_end((1, 2, 4, 6), (5, 7))
(1, 2, 4, 6, 5, 7)

2 Comments

This is plain wrong. insert_at_end([1, 2, 4, 6], [5]) should be [1, 2, 4, 6, [5]] by all logic.
@RomanSusi: If you want to handle string and list in the same way, I would say that that it's not what I'd expect. On the other hand by common sense I would agree with you; so I'm the first to be a little a confused and I wont argue too much because it seems that the author of the exercise had his own logic too...
-1

While encapsulate relies on the type, the code directly in insert_at_end does not, and relies on + meaning related things for all 3 types, and so in that sense, fits in with the hint.

3 Comments

There must be a nicer answer. The answer could probably use type, but not particular types.
You can't just throw the offending code into a different function and call it a win! :D
I'd be interested to see that, too -- but I haven't seen anyone suggest one yet.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.