我正在寻找帮助查找Python函数,这些函数允许我获取字符串列表,例如["I like ", " and ", " because "]
和单个目标字符串,例如"I like lettuce and carrots and onions because I do"
,并找到所有方法可以对目标字符串中的字符进行分组,使列表中的每个字符串按顺序排列。
例如:
solution(["I like ", " and ", " because ", "do"],
"I like lettuce and carrots and onions because I do")
应该返回:
[("I like ", "lettuce", " and ", "carrots and onions", " because ", "I ", "do"),
("I like ", "lettuce and carrots", " and ", "onions", " because ", "I ", "do")]
请注意,在每个元组中,list参数中的字符串按顺序存在,并且函数返回分割目标字符串的每种可能方法,以实现此目的。
另一个例子,这次只有一种可能的方式来组织角色:
solution(["take ", " to the park"], "take Alice to the park")
应该给出结果:
[("take ", "Alice", " to the park")]
以下是无法正确组织字符的示例:
solution(["I like ", " because ", ""],
"I don't like cheese because I'm lactose-intolerant")
应该回馈:
[]
因为无法做到这一点。请注意,第一个参数中的"I like "
无法拆分。目标字符串中没有字符串"I like "
,因此无法匹配。
这是最后一个例子,同样有多个选项:
solution(["I", "want", "or", "done"],
"I want my sandwich or I want my pizza or salad done")
应该返回
[("I", " ", "want", " my sandwich ", "or", " I want my pizza or salad ", "done"),
("I", " ", "want", " my sandwich or I want my pizza ", "or", " salad ", "done"),
("I", " want my sandwich or I", "want", " my pizza ", "or", " salad ", "done")]`
请注意,同样,每个字符串["I", "want", "or", "done"]
按顺序包含在每个元组中,其余字符以任何可能的方式围绕这些字符串重新排序。所有可能的重新排序列表都是返回的。
请注意,它还假设列表中的第一个字符串将出现在目标字符串的开头,列表中的最后一个字符串将出现在目标字符串的末尾。 (如果他们不这样做,该函数应返回一个空列表。)
Python的功能允许我这样做吗?
我尝试过使用正则表达式功能,但在有多个选项的情况下似乎失败了。
答案 0 :(得分:1)
我有一个解决方案,它需要相当多的重构,但它似乎工作, 我希望这会有所帮助,这是一个非常有趣的问题。
[('I like ', 'lettuce', ' and ', 'carrots and onions', ' because ', 'I ', 'do'), ('I like ', 'lettuce and carrots', ' and ', 'onions', ' because ', 'I ', 'do')]
[('I like ', 'lettuce', ' and ', 'carrots and onions', ' because ', 'I ', 'do'), ('I like ', 'lettuce and carrots', ' and ', 'onions', ' because ', 'I ', 'do')]
[('take ', 'Alice', ' to the park')]
[('take ', 'Alice', ' to the park')]
[]
[]
[('I', ' ', 'want', ' my sandwich ', 'or', ' I want my pizza or salad ', 'done'), ('I', ' ', 'want', ' my sandwich or I want my pizza ', 'or', ' salad ', 'done'), ('I', ' want my sandwich or I ', 'want', ' my pizza ', 'or', ' salad ', 'done')]
[('I', ' ', 'want', ' my sandwich ', 'or', ' I want my pizza or salad ', 'done'), ('I', ' ', 'want', ' my sandwich or I want my pizza ', 'or', ' salad ', 'done'), ('I', ' want my sandwich or I', 'want', ' my pizza ', 'or', ' salad ', 'done')]
输出:
{{1}}
编辑:重构代码以获得有意义的变量名称。
Edit2:添加了我忘记的最后一个案例。
答案 1 :(得分:0)
要回答我的问题,您不需要任何特殊功能。如果您想要一个相对容易编码的版本,请查看下面的其他答案。与下面的解决方案相比,此解决方案的记录也较少,但它使用动态编程和memoization,因此它应该比以前的解决方案更快,并且内存密集度更低。它还正确处理正则表达式字符(例如|
)。 (以下解决方案没有。)
def solution(fixed_strings, target_string):
def get_middle_matches(s, fixed_strings):
'''
Gets the fixed strings matches without the first and last first strings
Example the parameter tuple ('ABCBD", ["B"]) should give back [["A", "B", "CBD"], ["ABC", "B", "D"]]
'''
# in the form {(s, s_index, fixed_string_index, fixed_character_index): return value of recursive_get_middle_matches called with those parameters}
lookup = {}
def memoized_get_middle_matches(*args):
'''memoize the recursive function'''
try:
ans = lookup[args]
return ans
except KeyError:
ans = recursive_get_middle_matches(*args)
lookup[args] = ans
return ans
def recursive_get_middle_matches(s, s_index, fixed_string_index, fixed_character_index):
'''
Takes a string, an index into that string, a index into the list of middle fixed strings,
...and an index into that middle fixed string.
Returns what fixed_string_matches(s, fixed_strings[fixed_string_index:-1]) would return, and deals with edge cases.
'''
# base case: there's no fixed strings left to match
try:
fixed_string = fixed_strings[fixed_string_index]
except IndexError:
# we just finished matching the last fixed string, but there's some stuff left over
return [[s]]
# recursive case: we've finished matching a fixed string
# note that this needs to go before the end of the string base case
# ...because otherwise the matched fixed string may not be added to the answer,
# ...since getting to the end of the main string will short-circuit it
try:
fixed_character = fixed_string[fixed_character_index]
except IndexError:
# finished matching this fixed string
upper_slice = s_index
lower_slice = upper_slice - len(fixed_string)
prefix = s[:lower_slice]
match = s[lower_slice:upper_slice]
postfix = s[upper_slice:]
match_ans = [prefix, match]
recursive_answers = memoized_get_middle_matches(postfix, 0, fixed_string_index + 1, 0)
if fixed_string == '' and s_index < len(s):
recursive_skip_answers = memoized_get_middle_matches(s, s_index + 1, fixed_string_index, fixed_character_index)
return [match_ans + recursive_ans for recursive_ans in recursive_answers] + recursive_skip_answers
else:
return [match_ans + recursive_ans for recursive_ans in recursive_answers]
# base cases: we've reached the end of the string
try:
character = s[s_index]
except IndexError:
# nothing left to match in the main string
if fixed_string_index >= len(fixed_strings):
# it completed matching everything it needed to
return [[""]]
else:
# it didn't finish matching everything it needed to
return []
# recursive cases: either we match this character or we don't
character_matched = (character == fixed_character)
starts_fixed_string = (fixed_character_index == 0)
if starts_fixed_string:
# if this character starts the fixed string, we're still searching for this same fixed string
recursive_skip_answers = memoized_get_middle_matches(s, s_index + 1, fixed_string_index, fixed_character_index)
if character_matched:
recursive_take_answers = memoized_get_middle_matches(s, s_index + 1, fixed_string_index, fixed_character_index + 1)
if starts_fixed_string:
# we have the option to either take the character as a match, or skip over it
return recursive_skip_answers + recursive_take_answers
else:
# this character is past the start of the fixed string; we can no longer match this fixed string
# since we can't match one of the fixed strings, this is a failed path if we don't match this character
# thus, we're forced to take this character as a match
return recursive_take_answers
else:
if starts_fixed_string:
# we can't match it here, so we skip over and continue
return recursive_skip_answers
else:
# this character is past the start of the fixed string; we can no longer match this fixed string
# since we can't match one of the fixed strings, there are no possible matches here
return []
## main code
return memoized_get_middle_matches(s, 0, 0, 0)
## main code
# doing the one fixed string case first because it happens a lot
if len(fixed_strings) == 1:
# if it matches, then there's just that one match, otherwise, there's none.
if target_string == fixed_strings[0]:
return [target_string]
else:
return []
if len(fixed_strings) == 0:
# there's no matches because there are no fixed strings
return []
# separate the first and last from the middle
first_fixed_string = fixed_strings[0]
middle_fixed_strings = fixed_strings[1:-1]
last_fixed_string = fixed_strings[-1]
prefix = target_string[:len(first_fixed_string)]
middle = target_string[len(first_fixed_string):len(target_string)-len(last_fixed_string)]
postfix = target_string[len(target_string)-len(last_fixed_string):]
# make sure the first and last fixed strings match the target string
# if not, the target string does not match
if not (prefix == first_fixed_string and postfix == last_fixed_string):
return []
else:
# now, do the check for the middle fixed strings
return [[prefix] + middle + [postfix] for middle in get_middle_matches(middle, middle_fixed_strings)]
print(solution(["I like ", " and ", " because ", "do"],
"I like lettuce and carrots and onions because I do"))
print([("I like ", "lettuce", " and ", "carrots and onions", " because ", "I ", "do"),
("I like ", "lettuce and carrots", " and ", "onions", " because ", "I ", "do")])
print()
print(solution(["take ", " to the park"], "take Alice to the park"))
print([("take ", "Alice", " to the park")])
print()
# Courtesy of @ktzr
print(solution(["I like ", " because "],
"I don't like cheese because I'm lactose-intolerant"))
print([])
print()
print(solution(["I", "want", "or", "done"],
"I want my sandwich or I want my pizza or salad done"))
print([("I", " ", "want", " my sandwich ", "or", " I want my pizza or salad ", "done"),
("I", " ", "want", " my sandwich or I want my pizza ", "or", " salad ", "done"),
("I", " want my sandwich or I", "want", " my pizza ", "or", " salad ", "done")])
上一个回答:
要回答我的问题, itertools.product
功能和带有regex.finditer
参数的 overlapped
是这两个关键功能解。我想我会包含我的最终代码,以防它在类似情况下帮助其他人。
我真的非常关心我的代码是超级可读的,所以我最终根据@ ktzr的解决方案编写了我自己的解决方案。 (谢谢!)
我的解决方案使用了一些奇怪的东西。
首先,它使用overlapped
参数,该参数仅可通过regex
模块使用,并且必须安装(最有可能通过pip install regex
)。然后,使用import regex as re
将其包含在顶部。这样可以轻松搜索字符串中的重叠匹配。
其次,我的解决方案使用了一个未明确包含在库中的itertools函数,您必须将其定义为:
import itertools
def itertools_pairwise(iterable):
'''s -> (s0,s1), (s1,s2), (s2, s3), ...'''
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
这个函数只是让我在列表中成对迭代,确保列表中的每个元素(第一个和最后一个除外)都会遇到两次。
有了这两件事,这就是我的解决方案:
def solution(fixed_strings, target_string):
# doing the one fixed string case first because it happens a lot
if len(fixed_strings) == 1:
# if it matches, then there's just that one match, otherwise, there's none.
if target_string == fixed_strings[0]:
return [target_string]
else:
return []
# make sure the first and last fixed strings match the target string
# if not, the target string does not match
if not (target_string.startswith(fixed_strings[0]) and target_string.endswith(fixed_strings[-1])):
return []
# get the fixed strings in the middle that it now needs to search for in the middle of the target string
middle_fixed_strings = fixed_strings[1:-1]
# where in the target string it found the middle fixed strings.
# middle_fixed_strings_placements is in the form: [[where it found the 1st middle fixed string], ...]
# [where it found the xth middle fixed string] is in the form: [(start index, end index), ...]
middle_fixed_strings_placements = [[match.span() for match in re.finditer(string, target_string, overlapped=True)]
for string in middle_fixed_strings]
# if any of the fixed strings couldn't be found in the target string, there's no matches
if [] in middle_fixed_strings_placements:
return []
# get all of the possible ways each of the middle strings could be found once within the target string
all_placements = itertools.product(*middle_fixed_strings_placements)
# remove the cases where the middle strings overlap or are out of order
good_placements = [placement for placement in all_placements
if not (True in [placement[index][1] > placement[index + 1][0]
for index in range(len(placement) - 1)])]
# create a list of all the possible final matches
matches = []
target_string_len = len(target_string) # cache for later
# save the start and end spans which are predetermined by their length and placement
start_span = (0, len(fixed_strings[0]))
end_span = (target_string_len - len(fixed_strings[-1]), target_string_len)
for placement in good_placements:
placement = list(placement)
# add in the spans for the first and last fixed strings
# this makes it so each placement is in the form: [1st fixed string span, ..., last fixed string span]
placement.insert(0, start_span)
placement.append(end_span)
# flatten the placements list to get the places where we need to cut up the string.
# we want to cut the string at the span values to get out the fixed strings
cuts = [cut for span in placement for cut in span]
match = []
# go through the cuts and make them to create the list
for start_cut, end_cut in itertools_pairwise(cuts):
match.append(target_string[start_cut:end_cut])
matches.append(match)
return matches