-1

I've a file with alternating lines, chords followed by lyrics:

C       G                 Am
See the stone set in your eyes,
         F                   C
see the thorn twist in your side,
  G         Am F
I wait for you

How could I merge subsequent lines in order to produce an output like the following, while keeping track of the character position:

(C)See the (G)stone set in your (Am)eyes,
see the t(F)horn twist in your s(C)ide,
I (G)wait for y(Am)ou(F)

From How do I read two lines from a file at a time using python it can be seen that iterating over the file 2 lines at a time can be done with

with open('lyrics.txt') as f:
    for line1, line2 in zip(f, f):
        ...  # process lines

but how can the lines be merged so that line 2 is split according to character positions (of chords) from line 1? A simple

chords = line1.split()

has no position information and

for i, c in enumerate(line1):
    ...

gives separate characters, not the chords.

Ilja Everilä
  • 50,538
  • 7
  • 126
  • 127
  • 1
    What have you tried? Please show your code. (Read https://stackoverflow.com/help/how-to-ask) – Yannis Jan 15 '18 at 13:25
  • Your failed attempts may seem irrelevant to you, but they can help inform potential answerers on what specifically you're stuck on. "Why isn't this working?" type questions tend to fare better than "How do I do this?" type questions. – glibdud Jan 15 '18 at 13:31

1 Answers1

1

You could use regexp match objects for extracting both position and content of chords from the 1st line. Care must be taken at the edges; the same chord may continue on the next line, and a line may contain chords with no matching lyrics. Both cases can be found in the example data.

import io
import re

# A chord is one or more consecutive non whitespace characters
CHORD = re.compile(r'\S+')

def inline_chords(lyrics):
    for chords, words in zip(lyrics, lyrics):
        # Produce a list of (position, chord) tuples
        cs = [
            # Handles chords that continue to next line.
            (0, None),
            # Unpack found chords with their positions.
            *((m.start(), m[0]) for m in CHORD.finditer(chords)),
            # Pair for the last chord. Slices rest of the words string.
            (None, None)
        ]
        # Remove newline.
        words = words[:-1]

        # Zip chords in order to get ranges for slicing lyrics.
        for (start, chord), (end, _) in zip(cs, cs[1:]):
            if start == end:
                continue

            # Extract the relevant lyrics.
            ws = words[start:end]

            if chord:
                yield f"({chord})"

            yield ws

        yield "\n"

The edges could be handled differently, for example by testing if the 1st chord begins at 0 or not before the loop, but I feel that the single for-loop makes for cleaner code.

Trying it out:

test = """\
C       G                 Am
See the stone set in your eyes,
         F                   C
see the thorn twist in your side,
  G         Am F
I wait for you
"""

if __name__ == '__main__':
    with io.StringIO(test) as f:
        print("".join(list(inline_chords(f))))

produces the desired format:

(C)See the (G)stone set in your (Am)eyes,
see the t(F)horn twist in your s(C)ide,
I (G)wait for y(Am)ou(F)
Ilja Everilä
  • 50,538
  • 7
  • 126
  • 127