Mailing List Archive

Packing Problem
I found Hen Hanna's "packing" problem to be an intriguing one: Given a
list of words:
    ['APPLE', 'PIE', 'APRICOT', 'BANANA', 'CANDY']
find a string (in general non-unique) as short as possible which
contains the letters of each of these words, in order, as a subsequence.
It struck me as being rather hard for a homework problem, unless I'm
missing something blindingly obvious.
Here is what I came up with (I could have done with
removeprefix/removesuffix but I'm stuck on Python 3.8 for now ????):

# Pack.py
def Pack(Words):
    if not Words:
        return ''
    # The method is to build up the result by adding letters at the
beginning
    # and working forward, and by adding letters at the end, working
backwards,
    # meanwhile shrinking the words in the list.
    Words = list(Words) # Don't mutate the original
    Initial = ''
    Final = ''
    while True:
        AnyProgress = False
        # It is safe to add an initial letter (of one or more of the
words) if
        # EITHER    There is no word that contains it as
        #             a non-initial letter but not as an initial letter.
        #  OR       All words start with it.
        while True:
            FirstLetters = ''.join(w[0] for w in Words)
            FirstLetters = [ ch for ch in FirstLetters if
                all(w[0]==ch for w in Words)
                or not any(ch in w[1:] and w[0]!=ch for w in Words) ]
            if not FirstLetters:
                break
            AnyProgress = True
            ch = FirstLetters[0]    # Pick one
            Initial += ch           # Build up the answer from the
beginning
            Words = [ (w[1:] if w[0]==ch else w) for w in Words ]
            Words = [ w for w in Words if w != '']
            if not Words:
                return Initial + Final
        # It is safe to add a final letter (of one or more of the words) of
        # EITHER    There is no word that contains it as
        #             a non-final letter but not as a final letter.
        #  OR       All words end with it.
        while True:
            LastLetters = ''.join(w[-1] for w in Words)
            LastLetters = [ ch for ch in LastLetters if
                all(w[-1]==ch for w in Words)
                or not any(ch in w[:-1] and w[-1]!=ch for w in Words) ]
            if not LastLetters:
                break
            AnyProgress = True
            ch = LastLetters[0]     # Pick one
            Final = ch + Final      # Build up the answer from the end
            Words = [ (w[:-1] if w[-1]==ch else w) for w in Words ]
            Words = [ w for w in Words if w != '']
            if not Words:
                return Initial + Final
        if not AnyProgress:
            break
    # Try all the possibilities for the next letter to add at the
beginning,
    # with recursive calls, and pick one that gives a shortest answer:
    BestResult = None
    for ch in set(w[0] for w in Words):
            Words2 = list( (w[1:] if w[0] == ch else w) for w in Words )
            Words2 = [ w for w in Words2 if w != '' ]
            res = ch + Pack(Words2)
            if BestResult is None or len(res) < len(BestResult):
                BestResult = res
    return Initial + BestResult + Final

print(Pack(['APPLE', 'PIE', 'APRICOT', 'BANANA', 'CANDY']))

The output:
BAPPRICNANADYOTLE
which has the same length as the answer I came up with trying to solve
it with my unaided brain, which may or may not be reassuring ????,
and also contains a much-needed BRANDY.
I expect there are simpler and more efficient solutions.
Best wishes
Rob Cliffe
--
https://mail.python.org/mailman/listinfo/python-list
Re: Packing Problem [ In reply to ]
Slightly improved version (deals with multiple characters together
instead of one at a time):

# Pack.py
def Pack(Words):
    if not Words:
        return ''
    # The method is to build up the result by adding letters at the
beginning
    # and working forward, and by adding letters at the end, working
backwards,
    # meanwhile shrinking the words in the list.
    Words = list(Words) # Don't mutate the original
    Initial = ''
    Final = ''
    while True:
        # It is safe to add an initial letter (of one or more of the
words) if
        # EITHER    There is no word that contains it as
        #             a non-initial letter but not as an initial letter.
        #  OR       All words start with it.
        while True:
            FirstLetters = set(w[0] for w in Words)
            FirstLettersSafe = sorted(ch for ch in FirstLetters if
                all(w[0]==ch for w in Words)
                or not any(ch in w[1:] and w[0]!=ch for w in Words))
                # sorted() to make the answer deterministic
                # (not dependent on set ordering)
            if not FirstLettersSafe:
                break
            AnyProgress = True
            Initial += ''.join(FirstLettersSafe)   # Build up the
answer from the beginning
            Words = [ (w[1:] if w[0] in FirstLettersSafe else w) for w
in Words ]
            Words = [ w for w in Words if w != '']
            if not Words:
                return Initial + Final
        # It is safe to add a final letter (of one or more of the words) of
        # EITHER    There is no word that contains it as
        #             a non-final letter but not as a final letter.
        #  OR       All words end with it.
        while True:
            LastLetters = set(w[-1] for w in Words)
            LastLettersSafe = sorted(ch for ch in LastLetters if
                all(w[-1]==ch for w in Words)
                or not any(ch in w[:-1] and w[-1]!=ch for w in Words))
                # sorted() to make the answer deterministic
                # (not dependent on set ordering)
            if not LastLettersSafe:
                break
            Final = ''.join(LastLettersSafe) + Final   # Build up the
answer from the end
            Words = [ (w[:-1] if w[-1] in LastLettersSafe else w) for w
in Words ]
            Words = [ w for w in Words if w != '']
            if not Words:
                return Initial + Final
        if not (FirstLettersSafe or LastLettersSafe):
            break # stuck
    # Try all the possibilities for the next letter to add at the
beginning,
    # with recursive calls, and pick one that gives a shortest answer:
    BestResult = None
    for ch in FirstLetters:
            Words2 = list( (w[1:] if w[0] == ch else w) for w in Words )
            Words2 = [ w for w in Words2 if w != '' ]
            res = ch + Pack(Words2)
            if BestResult is None or len(res) < len(BestResult):
                BestResult = res
    return Initial + BestResult + Final

print(Pack(['APPLE', 'PIE', 'APRICOT', 'BANANA', 'CANDY']))

Rob Cliffe
--
https://mail.python.org/mailman/listinfo/python-list
Re: Packing Problem [ In reply to ]
Haven?t look at it all in detail, but I?d replace the list comprehensions with a loop:
CopyOfWords = list(Words)
Words = [(w[1:] if w[0] == ch else w) for w in Words]
Words = [w for w in Words if w != '']
nextWords = []
for w in CopyOfWords:
if w[0] != ch:
nextWords.append(w)
elif len(w) > 1:
nextWords.append(w[1:])
assert Words == nextWords

From: Python-list <python-list-bounces+gweatherby=uchc.edu@python.org> on behalf of Rob Cliffe via Python-list <python-list@python.org>
Date: Thursday, March 2, 2023 at 2:12 PM
To: python-list@python.org <python-list@python.org>
Subject: Re: Packing Problem
*** Attention: This is an external email. Use caution responding, opening attachments or clicking on links. ***

Slightly improved version (deals with multiple characters together
instead of one at a time):

# Pack.py
def Pack(Words):
if not Words:
return ''
# The method is to build up the result by adding letters at the
beginning
# and working forward, and by adding letters at the end, working
backwards,
# meanwhile shrinking the words in the list.
Words = list(Words) # Don't mutate the original
Initial = ''
Final = ''
while True:
# It is safe to add an initial letter (of one or more of the
words) if
# EITHER There is no word that contains it as
# a non-initial letter but not as an initial letter.
# OR All words start with it.
while True:
FirstLetters = set(w[0] for w in Words)
FirstLettersSafe = sorted(ch for ch in FirstLetters if
all(w[0]==ch for w in Words)
or not any(ch in w[1:] and w[0]!=ch for w in Words))
# sorted() to make the answer deterministic
# (not dependent on set ordering)
if not FirstLettersSafe:
break
AnyProgress = True
Initial += ''.join(FirstLettersSafe) # Build up the
answer from the beginning
Words = [ (w[1:] if w[0] in FirstLettersSafe else w) for w
in Words ]
Words = [ w for w in Words if w != '']
if not Words:
return Initial + Final
# It is safe to add a final letter (of one or more of the words) of
# EITHER There is no word that contains it as
# a non-final letter but not as a final letter.
# OR All words end with it.
while True:
LastLetters = set(w[-1] for w in Words)
LastLettersSafe = sorted(ch for ch in LastLetters if
all(w[-1]==ch for w in Words)
or not any(ch in w[:-1] and w[-1]!=ch for w in Words))
# sorted() to make the answer deterministic
# (not dependent on set ordering)
if not LastLettersSafe:
break
Final = ''.join(LastLettersSafe) + Final # Build up the
answer from the end
Words = [ (w[:-1] if w[-1] in LastLettersSafe else w) for w
in Words ]
Words = [ w for w in Words if w != '']
if not Words:
return Initial + Final
if not (FirstLettersSafe or LastLettersSafe):
break # stuck
# Try all the possibilities for the next letter to add at the
beginning,
# with recursive calls, and pick one that gives a shortest answer:
BestResult = None
for ch in FirstLetters:
Words2 = list( (w[1:] if w[0] == ch else w) for w in Words )
Words2 = [ w for w in Words2 if w != '' ]
res = ch + Pack(Words2)
if BestResult is None or len(res) < len(BestResult):
BestResult = res
return Initial + BestResult + Final

print(Pack(['APPLE', 'PIE', 'APRICOT', 'BANANA', 'CANDY']))

Rob Cliffe
--
https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!l3ysx0BUPZBdKdwb9F8mw4BAE2UIflvNqWeZLfALY2kjEo9e4KTy6fEYoGCTileOUtYe0yp6nL18ymdOguC3TGagEA$<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!l3ysx0BUPZBdKdwb9F8mw4BAE2UIflvNqWeZLfALY2kjEo9e4KTy6fEYoGCTileOUtYe0yp6nL18ymdOguC3TGagEA$>
--
https://mail.python.org/mailman/listinfo/python-list
Re: Packing Problem [ In reply to ]
On 02/03/2023 19:40, Weatherby,Gerard wrote:
> Haven’t look at it all in detail, but I’d replace the list comprehensions with a loop:
> CopyOfWords = list(Words)
> Words = [(w[1:] if w[0] == ch else w) for w in Words]
> Words = [w for w in Words if w != '']
> nextWords = []
> for w in CopyOfWords:
> if w[0] != ch:
> nextWords.append(w)
> elif len(w) > 1:
> nextWords.append(w[1:])
> assert Words == nextWords
Why?
Rob Cliffe
>
> From: Python-list <python-list-bounces+gweatherby=uchc.edu@python.org> on behalf of Rob Cliffe via Python-list <python-list@python.org>
> Date: Thursday, March 2, 2023 at 2:12 PM
> To: python-list@python.org <python-list@python.org>
> Subject: Re: Packing Problem
> *** Attention: This is an external email. Use caution responding, opening attachments or clicking on links. ***
>
> Slightly improved version (deals with multiple characters together
> instead of one at a time):
>
> # Pack.py
> def Pack(Words):
> if not Words:
> return ''
> # The method is to build up the result by adding letters at the
> beginning
> # and working forward, and by adding letters at the end, working
> backwards,
> # meanwhile shrinking the words in the list.
> Words = list(Words) # Don't mutate the original
> Initial = ''
> Final = ''
> while True:
> # It is safe to add an initial letter (of one or more of the
> words) if
> # EITHER There is no word that contains it as
> # a non-initial letter but not as an initial letter.
> # OR All words start with it.
> while True:
> FirstLetters = set(w[0] for w in Words)
> FirstLettersSafe = sorted(ch for ch in FirstLetters if
> all(w[0]==ch for w in Words)
> or not any(ch in w[1:] and w[0]!=ch for w in Words))
> # sorted() to make the answer deterministic
> # (not dependent on set ordering)
> if not FirstLettersSafe:
> break
> AnyProgress = True
> Initial += ''.join(FirstLettersSafe) # Build up the
> answer from the beginning
> Words = [ (w[1:] if w[0] in FirstLettersSafe else w) for w
> in Words ]
> Words = [ w for w in Words if w != '']
> if not Words:
> return Initial + Final
> # It is safe to add a final letter (of one or more of the words) of
> # EITHER There is no word that contains it as
> # a non-final letter but not as a final letter.
> # OR All words end with it.
> while True:
> LastLetters = set(w[-1] for w in Words)
> LastLettersSafe = sorted(ch for ch in LastLetters if
> all(w[-1]==ch for w in Words)
> or not any(ch in w[:-1] and w[-1]!=ch for w in Words))
> # sorted() to make the answer deterministic
> # (not dependent on set ordering)
> if not LastLettersSafe:
> break
> Final = ''.join(LastLettersSafe) + Final # Build up the
> answer from the end
> Words = [ (w[:-1] if w[-1] in LastLettersSafe else w) for w
> in Words ]
> Words = [ w for w in Words if w != '']
> if not Words:
> return Initial + Final
> if not (FirstLettersSafe or LastLettersSafe):
> break # stuck
> # Try all the possibilities for the next letter to add at the
> beginning,
> # with recursive calls, and pick one that gives a shortest answer:
> BestResult = None
> for ch in FirstLetters:
> Words2 = list( (w[1:] if w[0] == ch else w) for w in Words )
> Words2 = [ w for w in Words2 if w != '' ]
> res = ch + Pack(Words2)
> if BestResult is None or len(res) < len(BestResult):
> BestResult = res
> return Initial + BestResult + Final
>
> print(Pack(['APPLE', 'PIE', 'APRICOT', 'BANANA', 'CANDY']))
>
> Rob Cliffe
> --
> https://urldefense.com/v3/__https://mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!l3ysx0BUPZBdKdwb9F8mw4BAE2UIflvNqWeZLfALY2kjEo9e4KTy6fEYoGCTileOUtYe0yp6nL18ymdOguC3TGagEA$<https://urldefense.com/v3/__https:/mail.python.org/mailman/listinfo/python-list__;!!Cn_UX_p3!l3ysx0BUPZBdKdwb9F8mw4BAE2UIflvNqWeZLfALY2kjEo9e4KTy6fEYoGCTileOUtYe0yp6nL18ymdOguC3TGagEA$>

--
https://mail.python.org/mailman/listinfo/python-list