【问题标题】:Create cartesian product of strings by replacing substrings from a list通过替换列表中的子字符串来创建字符串的笛卡尔积
【发布时间】:2021-07-24 09:13:47
【问题描述】:

我有一个包含占位符的字典及其可能的值列表,如下所示:

{
    "~GPE~": ['UK', 'USA'],
    "~PERSON~": ['John Davies', 'Tom Banton', 'Joe Morgan'],
    # and so on ...
}

我想通过替换模板中的占位符(即~GPE~~PERSON~)来创建所有可能的字符串组合:

"My name is ~PERSON~. I travel to ~GPE~ with ~PERSON~ every year".

预期输出是:

"My name is John Davies. I travel to UK with Tom Banton every year."
"My name is John Davies. I travel to UK with Joe Morgan every year."
"My name is John Davies. I travel to USA with Tom Banton every year."
"My name is John Davies. I travel to USA with Joe Morgan every year."
"My name is Tom Banton. I travel to UK with John Davies every year."
"My name is Tom Banton. I travel to UK with Joe Morgan every year."
"My name is Tom Banton. I travel to USA with John Davies every year."
"My name is Tom Banton. I travel to USA with Joe Morgan every year."
"My name is Joe Morgan. I travel to UK with Tom Banton every year."
"My name is Joe Morgan. I travel to UK with John Davies every year."
"My name is Joe Morgan. I travel to USA with Tom Banton every year."
"My name is Joe Morgan. I travel to USA with John Davies every year."

还要注意字典中某个键对应的值如何在同一个句子中不重复。例如我不想:“我的名字是 Joe Morgan。我每年都和 Joe Morgan 一起去美国旅行。” (所以不完全是笛卡尔积,但足够接近)

我是 python 新手,正在尝试使用 re 模块,但找不到解决此问题的方法。

编辑

我面临的主要问题是替换字符串会导致长度发生变化,这使得后续修改字符串变得困难。这尤其是由于字符串中相同占位符的多个实例的可能性。下面是一个sn-p来详细说明:

label_dict = {
    "~GPE~": ['UK', 'USA'],
    "~PERSON~": ['John Davies', 'Tom Banton', 'Joe Morgan']
}


template = "My name is ~PERSON~. I travel to ~GPE~ with ~PERSON~ every year."

for label in label_dict.keys():
    modified_string = template
    offset = 0
    for match in re.finditer(r'{}'.format(label), template):
        for label_text in label_dict.get(label, []):
            start, end = match.start() + offset, match.end() + offset
            offset += (len(label_text) - (end - start))
#             print ("Match was found at {start}-{end}: {match}".format(start = start, end = end, match = match.group()))
            modified_string = modified_string[: start] + label_text + modified_string[end: ]
            print(modified_string)

给出不正确的输出为:

My name is ~PERSON~. I travel to UK with ~PERSON~ every year.
My name is ~PERSON~. I travel USA with ~PERSON~ every year.
My name is John Davies. I travel to ~GPE~ with ~PERSON~ every year.
My name is JohTom Banton. I travel to ~GPE~ with ~PERSON~ every year.
My name is JohToJoe Morgan. I travel to ~GPE~ with ~PERSON~ every year.
My name is JohToJoe Morgan. I travel to ~GPE~ with John Davies every year.
My name is JohToJoe Morgan. I travel to ~GPE~ with JohTom Banton every year.
My name is JohToJoe Morgan. I travel to ~GPE~ with JohToJoe Morgan every year.

【问题讨论】:

  • 这能回答你的问题吗? Permutations between two lists of unequal length
  • @Tomalak 不,因为我不知道如何替换给定字符串中相同占位符的多个实例。例如“~GPE~”在模板中出现两次。使用 re.finditer 我可以处理占位符的多个实例,但是字符串的长度会在用可能的值替换占位符时发生变化。添加了 sn-p 以进一步详细说明。
  • 正则表达式用于替换patterns。你没有模式。你有一个固定的字符串。您可以使用旧的 str.replace(),并且可以指示该函数仅替换 N(例如 1)个实例。 docs.python.org/3/library/stdtypes.html#str.replace
  • 我确实有模式。字典中的键是模式。挑战是用可能值中的唯一值替换模式/占位符的所有实例。
  • 不,你没有。你有"~GPE~""~PERSON~",它们是固定字符串。

标签: python-3.x string algorithm combinatorics python-re


【解决方案1】:

这里有两种方法,如果你包括我刚才添加的新代码,那么三种方法,你可以做到,它们都会产生所需的输出。

嵌套循环

data_in ={
    "~GPE~": ['UK', 'USA'],
    "~PERSON~": ['John Davies', 'Tom Banton', 'Joe Morgan']
}

data_out = []
for gpe in data_in['~GPE~']:
    for person1 in data_in['~PERSON~']:
        for person2 in data_in['~PERSON~']:
            if person1 != person2: 
                data_out.append(f'My name is {person1}. I travel to {gpe} with {person2} every year.')

print('\n'.join(data_out))

列表理解

data_in ={
    "~GPE~": ['UK', 'USA'],
    "~PERSON~": ['John Davies', 'Tom Banton', 'Joe Morgan']
}

data_out = [f'My name is {person1}. I travel to {gpe} with {person2} every year.' for gpe in data_in['~GPE~'] for person1 in data_in['~PERSON~'] for person2 in data_in['~PERSON~'] if person1!=person2]

print('\n'.join(data_out))

使用 Pandas 的合并

注意,此代码需要 Pandas 1.2 或更高版本。

import pandas as pd

data = {
    "~GPE~": ['UK', 'USA'],
    "~PERSON~": ['John Davies', 'Tom Banton', 'Joe Morgan'],
    # and so on ...
}

country = pd.DataFrame({'country':data['~GPE~']})
person = pd.DataFrame({'person':data['~PERSON~']})

cart = country.merge(person, how='cross').merge(person, how='cross')

cart.columns = ['country', 'person1', 'person2']

cart = cart.query('person1 != person2').reset_index()

cart['sentence'] = cart.apply(lambda row: f"My name is {row['person1']}. I travel to {row['country']} with {row['person2']} every year." , axis=1)

sentences = cart['sentence'].to_list()

print('\n'.join(sentences))

【讨论】:

  • 这非常接近我想要的。这可以扩展到涵盖字典的所有键吗?如果可以避免假设,则此解决方案是完美的。我当然可以使用另一个循环来遍历键以获得“~GPE~”和“~PERSON~”,但解决方案假设我知道每个占位符需要替换多少次并添加尽可能多的“for循环”它。例如'~PERSON~' 出现两次,因此需要两个循环,但在运行时无法确定。
  • python如何知道你想替换多少次占位符?我稍后会尝试在我的答案中添加一些内容,展示如何使用 pandas 进行笛卡尔乘积,而不使用循环,但如果你这样做,你需要进一步处理数据以获得你想要的确切结果。
  • 我知道确保所有占位符实例都被替换的唯一方法是使用 re.finditer。如果只需要计数,则 len(findall(r'~PERSON~', template)) 可以工作。感谢您的努力!
猜你喜欢
  • 2018-04-04
  • 2022-01-06
  • 1970-01-01
  • 2015-08-08
  • 1970-01-01
  • 2020-08-23
  • 1970-01-01
  • 2021-05-21
  • 2017-06-22
相关资源
最近更新 更多