根据第一列中的字母数将行与上一行连接起来

Posted

技术标签:

【中文标题】根据第一列中的字母数将行与上一行连接起来【英文标题】:Concatenate lines with previous line based on number of letters in first column 【发布时间】:2018-05-12 02:56:05 【问题描述】:

编码新手并试图弄清楚如何修复损坏的 csv 文件以使其能够正常工作。

因此该文件已从案例管理系统中导出,并包含用户名、casenr、花费时间、注释和日期等字段。

问题是偶尔的注释中有换行符,并且在导出 csv 时,工具不包含引号以将其定义为字段中的字符串。

见下例:

user;case;hours;note;date;
tnn;123;4;solved problem;2017-11-27;
tnn;124;2;random comment;2017-11-27;
tnn;125;3;I am writing a comment
that contains new lines
without quotation marks;2017-11-28;
HJL;129;8;trying to concatenate lines to re form the broken csv;2017-11-29;

我想连接第 3,4 和 5 行以显示以下内容: tnn;125;3;我正在写一个包含不带引号的新行的评论;2017-11-28;

由于每一行都以用户名开头(总是 3 个字母),我想我可以迭代这些行以找出哪些行不以用户名开头,并将其与上一行连接起来。 但它并没有真正按预期工作。

这是我目前得到的:

import re

with open('Rapp.txt', 'r') as f:

 for line in f:
  previous = line #keep current line in variable to join next line
  if not re.match(r'^[A-Za-z]3', line): #regex to match 3 letters
   print(previous.join(line)) 

脚本没有显示输出,只是默默地结束,有什么想法吗?

【问题讨论】:

如果评论中包含;会怎样?如果可能的话,也许您应该尝试修复 CSV 导出。 【参考方案1】:

我想我会采取稍微不同的方式:

import re

all_the_data = ""

with open('Rapp.txt', 'r') as f:
    for line in f:
        if not re.search("\d4-\d1,2-\d1,2;\n", line):
            line = re.sub("\n", "", line)
        all_the_data = "".join([all_the_data, line])
print (all_the_data)

有几种方法可以做到这一点,各有优缺点,但我认为这样做很简单。

按照你的做法循环文件,如果该行不以日期结尾,并且 ;取下回车并将其填充到 all_the_data 中。这样,您就不必“回顾”文件。同样,有很多方法可以做到这一点。如果您更愿意使用以 3 个字母和 a 开头的逻辑;回首往事,这是可行的:

import re

all_the_data = ""

with open('Rapp.txt', 'r') as f:
    all_the_data = ""
    for line in f:
        if not re.search("^[A-Za-z]3;", line):
            all_the_data = re.sub("\n$", "", all_the_data)
        all_the_data = "".join([all_the_data, line])

    print ("results:")
    print (all_the_data)

几乎是要求的。逻辑是如果当前行没有正确开始,则从 all_the_data 中取出上一行的回车。

如果您在使用正则表达式本身时需要帮助,这个网站很棒:http://regex101.com

【讨论】:

我选择了第二种解决方案,因为真实文件包含更多字段,最后一个不是日期。该值有时也是空的,因此很难使用第一个解决方案。【参考方案2】:

代码中的正则表达式匹配 txt 中的所有行(字符串)(找到与模式的有效匹配)。 if 条件永远不会为真,因此不会打印任何内容。

with open('./Rapp.txt', 'r') as f:
    join_words = []

    for line in f:
        line = line.strip()
        if len(line) > 3 and ";" in line[0:4] and len(join_words) > 0:
            print(';'.join(join_words)) 
            join_words = []
            join_words.append(line)
        else:
            join_words.append(line)

    print(";".join(join_words))

如果可能的话,我试图在这里不使用正则表达式以保持清晰。但是,正则表达式是更好的选择。

【讨论】:

【参考方案3】:

一种简单的方法是使用生成器作为原始文件的过滤器。如果该过滤器的第 4 列中没有分号 (;),则该过滤器会将一行连接到前一行。代码可能是:

def preprocess(fd):
    previous = next(fd)
    for line in fd:
        if line[3] == ';':
            yield previous
            previous = line
        else:
            previous = previous.strip() + " " + line
    yield previous  # don't forget last line!

然后你可以使用:

with open(test.txt) as fd:
    rd = csv.DictReader(preprocess(fd))
    for row in rd:
        ...

这里的技巧是 csv 模块只需要在每次应用next 函数时返回一行的对象,因此生成器是合适的。

但这只是一种解决方法,正确的方法是上一步直接生成正确的 CSV 文件。

【讨论】:

以上是关于根据第一列中的字母数将行与上一行连接起来的主要内容,如果未能解决你的问题,请参考以下文章

SQL:将行与列中的逗号分隔值合并

同一公式中的查询,数组和排序函数

R:一次根据一列中的条件将整行推送到NA

将行与标题进行比较,然后在列中插入值并在 VBA 中进行重复检查

有没有办法将数据帧的一列中的所有行与另一个数据帧的另一列(火花)中的所有行进行比较?

ABC拼图约束满足问题