3

I'm looking to create a list whose values alternate based on a set of known patterns. Note that there may not be an equal number of items with each prefix. (2 foos, 3 bars, 3 spams). My current solution is nasty gaggle that .pop()'s from lists built from common prefixes and appends to a new list.

prefix_patterns = ['foo','bar','spam']

inlist = ['fooABC','fooXYZ','barABC','barXYZ','spamABC','bar123','spamXYZ','spam123']

Desired output:

outlist = ['fooABC','barABC','spamABC','fooXYZ','barXYZ','spamXYZ','bar123','spam123']

Current solution (doesn't handle lists of differing lengths):

foos = [value for value in inlist if 'foo' in value]
bars = [value for value in inlist if 'bar' in value]
spams = [value for value in inlist if 'spam' in value]

while foos:
    outlist.append(foos.pop())
    outlist.append(bars.pop())
    outlist.append(spams.pop())

For context: Looking to use this as a sort of throttling mechanism when making requests to 4 different servers.

8
  • generators are always a fun way to do things like this.. they save on memory too Commented Oct 21, 2016 at 18:46
  • Could you show your current solution. Commented Oct 21, 2016 at 18:50
  • Are the words sharing a prefix always sorted in inlist? foo<str> always befotre bar<str> before spam<str> Commented Oct 21, 2016 at 18:52
  • 1
    @kabanus No, they are in random order Commented Oct 21, 2016 at 18:59
  • 1
    @leaf done..... Commented Oct 21, 2016 at 19:02

3 Answers 3

1

Since you're using Python 2.x this would work:

# group by prefix first -> [['fooXX']['barXX']['spamXX']]
prefix_match = [[x for x in inlist if x.startswith(pre)] for pre in prefix_patterns]
outlist = [x for i in map(None,*prefix_match) for x in i if x]

The map built-in function will zip prefix_match together and pad with None if one of the lists is too short. Then you can simply flatten this list and exclude any None objects.

For Python 3.x you could replace the map function with itertools.zip_longest:

from itertools import zip_longest
prefix_match = [[x for x in inlist if x.startswith(pre)] for pre in prefix_patterns]
outlist = [x for i in zip_longest(*prefix_match) for x in i if x]
Sign up to request clarification or add additional context in comments.

Comments

1
inlist =['fooABC','fooXYZ','barABC','barXYZ','spamABC','bar123','spamXYZ','spam123']

new_inlist = []

separator = '-'

# Adding separator, I believe it won't require in real scenario as there must be separator between host and user.
for prefix in ['foo', 'bar', 'spam']:
    for item in inlist:
        if item.startswith(prefix):
           new_inlist.append(item.replace(prefix, prefix + separator))

# Ultimately new_inlist ->
#['foo-ABC','foo-XYZ','bar-ABC','bar-XYZ','spam-ABC','bar-123','spam-XYZ','spam-123']

# Now Just do sorting
new_inlist.sort(key=lambda x: x.split(separator)[1])

2 Comments

That doesn't answer the question: Sorting isn't mentioned or asked for. Why is it being provided?
I think in question James didn't mention that he is not looking for sort solution that is why I added sort solution.
0

You're looking for the itertools recipe roundrobin.

from itertools import cycle, islice

prefix_patterns = ['foo','bar','spam']
inlist = ['fooABC','fooXYZ','barABC','barXYZ','spamABC','bar123','spamXYZ','spam123']
outlist = ['fooABC','barABC','spamABC','fooXYZ','barXYZ','spamXYZ','bar123','spam123']

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    pending = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while pending:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            pending -= 1
            nexts = cycle(islice(nexts, pending))

def check_prefix(item):
    for prefix in prefix_patterns:
        if item.startswith(prefix):
            return prefix

group = {}
for item in inlist:
    key = check_prefix(item)
    group.setdefault(key, []).append(item)
print([x for x in roundrobin(*list(group.values()))])

>> ['barABC', 'spamABC', 'fooABC', 'barXYZ', 'spamXYZ', 'fooXYZ', 'bar123', 'spam123']

Comments

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.