20

Understand "better" as a quicker, elegant and readable.

I have two strings (a and b) that could be null or not. And I want concatenate them separated by a hyphen only if both are not null:

a - b

a (if b is null)

b (where a is null)

8
  • 8
    What do you want if they are both null? Commented Mar 12, 2013 at 12:21
  • 5
    @ShankarCabus: But all of these implementations do something when they are both null, and not all of them do the same thing. If you want an "elegant" way to do this operation, the elegance should cover all the cases and not require an extra if block before it. Commented Mar 12, 2013 at 12:40
  • 1
    (a+'-'+b).strip('-') ...but it fails miserably if a or b =='-' Commented Mar 12, 2013 at 13:20
  • 1
    @andsoa -- It also "fails miserably" if a and b are anything but ''. The end goal was to have a '-' between a and b, and you're stripping that out. Commented Mar 12, 2013 at 18:23
  • 2
    @Hoopdady: Incorrect, string stripping functions almost universally only apply to leading and trailing characters, otherwise it would just be a .replace('-', '') Commented Mar 12, 2013 at 22:28

10 Answers 10

47
# Concatenates a and b with ' - ' or Coalesces them if one is None
'-'.join([x for x in (a,b) if x])

Edit
Here are the results of this algorithm (Note that None will work the same as ''):

>>> '-'.join([x for x in ('foo','bar') if x])
'foo-bar'
>>> '-'.join([x for x in ('foo','') if x])
'foo'
>>> '-'.join([x for x in ('','bar') if x])
'bar'
>>> '-'.join([x for x in ('','') if x])
''

*Also note that Rafael's assessment, in his post below, only showed a difference of .0002 secs over a 1000 iterations of the filter method, it can be reasoned that such a small difference can be due to inconsistencies in available system resources at the time of running the script. I ran his timeit implementation over several iteration and found that either algorithm will be faster about 50% of the time, neither by a wide margin. Thus showing they are basically equivalent.

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

15 Comments

as I said, sorry for the strong words, but I've encountered a lot of similar uncommented code in large projects, where you spend plenty of time figuring out 'what the heck is going here'?
Well I would agree that a CS101 student would probably not get this, but this isn't a difficult concept for those a little ways into python. I happen to think its pretty elegant, though not necessarily the most efficient. But that's the beauty of SO, the people will decide. :-)
@Hoopdady: I agree with BasicWolf. Given the context of this question, I can follow what is happening. But if it just shows up on one line in a file, I would have to think before I knew what it did. When I see BasicWolf's answer, I know what it is doing immediately.
@Hoopdady -- I'd also remove the parens around '-'. It's pretty idiomatic to leave them off -- In fact, I've seen it written with parenthesis so rarely that the parenthesis actually made me take a second look at this.
@mgilson You're pretty idiomatic... fine I'll do it. Done.
|
36

How about something simple like:

# if I always need a string even when `a` and `b` are both null,
# I would set `output` to a default beforehand.
# Or actually, as Supr points out, simply do `a or b or 'default'`
if a and b:
    output = '%s - %s' % (a, b)
else:
    output = a or b

Edit: Lots of interesting solutions in this thread. I chose this solution because I was emphasizing readability and quickness, at least in terms of implementation. It's not the most scalable or interesting solution, but for this scope it works, and lets me move on to the next problem very quickly.

4 Comments

+1 for simplicity - not the simplicity of the code necessarily, but of the mapping between human understanding and code. Yeah, I guess that's about the same as readability (and maintainability, reliability, testability).
+1, I'd prefer seeing this in production code, as it states clearly the intent
I don't know Python very well, but couldn't the null/null case be handled by changing the last line to output = a or b or 'default' instead of setting it beforehand?
@Supr, you're right, that does work and is a much slicker way to handle it.
31

Wow, seems like a hot question :p My proposal:

' - '.join(filter(bool, (a, b)))

Which gives:

>>> ' - '.join(filter(bool, ('', '')))
''
>>> ' - '.join(filter(bool, ('1', '')))
'1'
>>> ' - '.join(filter(bool, ('1', '2')))
'1 - 2'
>>> ' - '.join(filter(bool, ('', '2')))
'2'

Obviously, None behaves like '' with this code.

3 Comments

Great solution! The most elegant for now.
filter(None,(a,b)) would also work, but bool is probably a little more explicit. For the record, this really isn't any different than the version above which uses a list-comp instead.
Using bool I get a slight decrease in performance. Probably because passing None allows to call the truth methods more directly, while passing bool the filter has to do a normal function call.
12

Here is one option:

("%s - %s" if (a and b) else "%s%s") % (a,b)

EDIT: As pointed by mgilson, this code would fail on with None's a better way (but less readable one) would be:

"%s - %s" % (a,b) if (a and b) else (a or b)

2 Comments

Note that you pick up the a = None case if you re-write it slightly: "%s - %s"%(a,b) if (a and b) else (a or b) -- But that's harder to read I would say.
" - ".join((a, b)) if a and b else a or b
4

I just wanted to offer toxotes' solution rewritten as a one liner using format.

output = "{0} - {1}".format(a, b) if (a and b) else (a or b)

Comments

3

There's a lot of answers here :)

The two best answers (performance and clean code in one line) are the answers of @icecrime and @Hoopdady

Both asnwers results equally, the only difference is performance.

cases = [
 (None, 'testB'),
 ('', 'testB'),
 ('testA', 'testB'),
 ('testA', ''),
 ('testA', None),
 (None, None)
]

for case in cases: print '-'.join(filter(bool, case))
'testB'
'testB'
'testA-testB'
'testA'
'testA'

for case in cases: print '-'.join([x for x in case if x])
'testB'
'testB'
'testA-testB'
'testA'
'testA'

So let's do a benchmark :)

import timeit

setup = '''
cases = [
  (None, "testB"),
  ("", "testB"),
  ("testA","testB"),
  ("testA", ""),
  ("testA", None),
  (None, None)
]
'''

print min(timeit.Timer(
  "for case in cases: '-'.join([x for x in case if x])", setup=setup
).repeat(5, 1000))
0.00171494483948

print min(timeit.Timer(
  "for case in cases: '-'.join(filter(bool, case))", setup=setup
).repeat(5, 1000))
0.00283288955688

But, as @mgilson said, using None instead of bool as the function in filter produces the same result and have a quite better performance:

print min(timeit.Timer(
  "for case in cases: '-'.join(filter(None, case))", setup=setup
).repeat(5, 1000))
0.00154685974121

So, the best result is the answer gave by @icecrime with the suggestion from @mgilson:

'-'.join(filter(None, (a,b)))

The performance difference is in milliseconds per 1000 iterations (microseconds per iteration). So these two methods have a quite equals performance, and, for almost any project you could choose any one; In case your project must have a better performance, considering microseconds, you could follow this benchmark :)

6 Comments

Is there something I'm missing. I've tried to copy and paste your code and it errors everytime
Also when I run my own time test, my way is slightly faster.
@Hoopdady, thanks! Something goes wrong with identations in setup declaration and I've missed a ) in your code, fixed now!
In copying your code and running it. I've found that my solution vs Icecrime's solutions are pretty much identical in time. Sometimes when I run your test, mine beats his, and sometimes his beats mine, it really depends on what all is going on in the system at the time.
I'm just saying that if you run it again, it may not give you better performance. Its such a small difference you have to consider inconsistencies in your available system resources at the time of running the script. I found that to be the case when I ran your script myself. Sometimes mine was faster, sometimes the filter implementation was faster.
|
1

Do it like this: '-'.join(max(x,'') for x in [a,b] if x is not None)

Comments

0

Try this:

def myfunc(a,b):
    if not b:
        return a
    elif not a:
        return b
    else:
        return a+' - '+b

Or

def myfunc(a,b):
    if a and b:
        return a+' - '+b
    else:
        return a or b

5 Comments

You don't really need an else if the preceding if will return from the function. I often wondered if it should be included for readability, but decided against it. It'd be nice if we could do return a if b else c.
@Carl: Surely you can do return a if b else c
@Eric: Sweet. I could've sworn I tried that once and got a syntax error, but I just tried it and it works fine. Thanks. That's such a nice feature.
@carl: All you're seeing is return (a if b else c)
Makes perfect sense. I could've sworn it didn't work for me when I tried it. It must have been something else. +1
0

Something pythonian, readable and elegant:

strings = string1, string2

'{0}{1}{2}'.format(
    # output first string if it's not empty
    strings[0] if strings[0] else '',

    # join with hyphen if both strings are not empty    
    '-' if all(strings) else '',

    # output second string if it's not empty
    strings[1] if strings[1] else ''
    )

And fast too ;)

Comments

0

I would do it like this:

def show_together(item1=None, item2=None, seperator='-'):
    return '%s%s%s' % (item1,seperator,item2) if item1 and item2 else item1 or item2



>>> show_together(1,1)
'1-1'

>>> show_together(1)
1

>>> show_together()
>>> show_together(4,4,'$')
'4$4'

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.