Home > Enterprise >  Clean and generic way to transform one element to tuple/list/set/str of one element
Clean and generic way to transform one element to tuple/list/set/str of one element

Time:10-25

I meet a interview question to implement following function:

def f(sequence_type, element):
    pass

for example:

f(str, 'a')  return 'a'
f(list, 'a') return ['a']
f(list, 'ab') return ['ab']
f(list, 1) return [1]
f(tuple, 1) return (1, )
f(set, 1) return {1}
f(str, 1) return '1'
f(str, [1]) return '[1]'

I came up a naive method:

def f(sequence_type, element):
    return sequence_type(element)

This method works for str. However, I will get error, since tupe(1) will raise TypeError: 'int' object is not iterable.

Certainly, I can write bunch of if else to check type and use specific way to generate one element sequence e.g. [x] (x,) and so on. Is there clean and generic way to do this?

CodePudding user response:

You can approximate the requirement this way:

from collections.abc import Iterable

def f2(sequence_type, *elements):
    if isinstance(elements[0],Iterable):
        return sequence_type(elements[0])
    else:
        return sequence_type(elements[:1])

which is close, but fails for f(list, 'ab') which returns ['a', 'b'] not ['ab']

It is hard to see why one would expect a Python function to treat strings of length 2 differently from strings of length 1. The language itself says that list('ab') == ['a', 'b'].

I suspect that is an expectation imported from languages like C that treat characters and strings as different datatypes, in other words I have reservations about that aspect of the question.

But saying you don't like the spec isn't a recipe for success, so that special treatment has to be coded as such:

def f(sequence_type, elements):
    if isinstance(elements, str) and len(elements) > 1 and sequence_type != str:
        return sequence_type([elements])
    else:
        return f2(sequence_type, elements)

The result is generic but the special-casing can't really be called clean.

CodePudding user response:

The examples for str doesn't really fit the description of the function, but here are some implementations that pass your test cases - you want to wrap the element into a collection first for tuple/list/set before converting:

def f(sequence_type, element):
    return sequence_type([element] if sequence_type != str else element)
def f(sequence_type, element):
    return sequence_type((element,) if sequence_type != str else element)
def f(sequence_type, element):
    return sequence_type({element} if sequence_type != str else element)

CodePudding user response:

You can try this one:

def f(sequence_type, element):
    g = lambda *args: args
    return str(g(element)[0]) if str==sequence_type else sequence_type(g(element))

CodePudding user response:

I think that you want to change the behavior of the literals, in particular for the case str-list. Since the changes are decided by you it means that you have to make either a case-study with if-else or by bypassing it in some tricky way. In my solution I choose the latter but I required only a conditional for the str-case to normalized its behavior. The mapping is string-based that should be evaluated.

def f(sequence_type, element):
        
    type_literal_map = {tuple: '({},)', set: '{{{}}}', list: '[{}]', str: '"{}"'}

    if isinstance(element, str) and not isinstance(sequence_type(), str):
        element = f'"{element}"'

    return eval(type_literal_map[sequence_type].format(element))

l = [(str, 'a'), (list, 'a'), (list, 'ab'), (list, 1), (tuple, 1), (set, 1), (str, 1), (str, [1])]
    
    
for t in l:
    print(t[0].__name__, t[1],'->',  f(*t))

Output

str a -> a
list a -> ['a']
list ab -> ['ab']
list 1 -> [1]
tuple 1 -> (1,)
set 1 -> {1}
str 1 -> 1
str [1] -> [1]
  • Related