我有以下测试矩阵:
a l i g t m j e a
我打算创建一个算法,帮助我只使用相邻的字母找到从给定的最小长度到最大长度的每个可能的单词。
例如:
最少:3个字母
最多:6个字母
根据测试矩阵,我应该得到以下结果:
等
我创建了一个测试代码(C#),它有一个代表字母的自定义类。
每个字母都知道它的邻居并且有一个生成计数器(用于在遍历期间跟踪它们)。
这是它的代码:
public class Letter
{
public int X { get; set; }
public int Y { get; set; }
public char Character { get; set; }
public List<Letter> Neighbors { get; set; }
public Letter PreviousLetter { get; set; }
public int Generation { get; set; }
public Letter(char character)
{
Neighbors = new List<Letter>();
Character = character;
}
public void SetGeneration(int generation)
{
foreach (var item in Neighbors)
{
item.Generation = generation;
}
}
}
我发现如果我想要它是动态的,它必须基于递归。
不幸的是,下面的代码会创建前4个单词,然后停止。毫无疑问,因为递归被指定的生成级别停止。
主要问题是递归只返回一个级别,但返回起点更好。
private static void GenerateWords(Letter input, int maxLength, StringBuilder sb)
{
if (input.Generation >= maxLength)
{
if (sb.Length == maxLength)
{
allWords.Add(sb.ToString());
sb.Remove(sb.Length - 1, 1);
}
return;
}
sb.Append(input.Character);
if (input.Neighbors.Count > 0)
{
foreach (var child in input.Neighbors)
{
if (input.PreviousLetter == child)
continue;
child.PreviousLetter = input;
child.Generation = input.Generation + 1;
GenerateWords(child, maxLength, sb);
}
}
}
所以,我觉得有点卡住了,不知道我该怎么办?
答案 0 :(得分:2)
从这里开始,您可以将其视为图遍历问题。从每个给定的字母开始,找到长度 min_size 到 max_size 的每条路径,在示例中将3和6作为这些值。我建议一个递归例程,将单词构建为通过网格的路径。这将类似于以下内容;用您的偏好替换类型和伪代码。
<array_of_string> build_word(size, current_node) {
if (size == 1) return current_node.letter as an array_of_string;
result = <empty array_of_string>
for each next_node in current_node.neighbours {
solution_list = build_word(size-1, next_node);
for each word in solution_list {
// add current_node.letter to front of that word.
// add this new word to the result array
}
}
return the result array_of_string
}
这会让你走向解决方案吗?
答案 1 :(得分:1)
在解决这些问题时,我倾向于使用不可变类,因为一切都更容易推理。以下实现使用 ad hoc ImmutableStack
,因为实现它非常简单。在生产代码中,我可能希望调查System.Collections.Immutable
以提高效果(visited
将是ImmutableHashSet<>
以指出明显的情况。)
那为什么我需要一个不可变的堆栈呢?跟踪当前的角色路径和访问过的&#34;位置&#34;在矩阵内。因为所选择的工作工具是不可变的,所以发送递归调用是没有用的,我们知道它不能改变所以我不必担心每个递归级别的不变量。
所以让我们实现一个不可变的堆栈。
我们还会实现一个帮助类Coordinates
,它封装了我们的&#34;位置&#34;在矩阵内部,将为我们提供值相等语义和获取任何给定位置的有效邻居的便捷方式。它显然会派上用场。
public class ImmutableStack<T>: IEnumerable<T>
{
private readonly T head;
private readonly ImmutableStack<T> tail;
public static readonly ImmutableStack<T> Empty = new ImmutableStack<T>(default(T), null);
public int Count => this == Empty ? 0 : tail.Count + 1;
private ImmutableStack(T head, ImmutableStack<T> tail)
{
this.head = head;
this.tail = tail;
}
public T Peek()
{
if (this == Empty)
throw new InvalidOperationException("Can not peek an empty stack.");
return head;
}
public ImmutableStack<T> Pop()
{
if (this == Empty)
throw new InvalidOperationException("Can not pop an empty stack.");
return tail;
}
public ImmutableStack<T> Push(T value) => new ImmutableStack<T>(value, this);
public IEnumerator<T> GetEnumerator()
{
var current = this;
while (current != Empty)
{
yield return current.head;
current = current.tail;
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
struct Coordinates: IEquatable<Coordinates>
{
public int Row { get; }
public int Column { get; }
public Coordinates(int row, int column)
{
Row = row;
Column = column;
}
public bool Equals(Coordinates other) => Column == other.Column && Row == other.Row;
public override bool Equals(object obj)
{
if (obj is Coordinates)
{
return Equals((Coordinates)obj);
}
return false;
}
public override int GetHashCode() => unchecked(27947 ^ Row ^ Column);
public IEnumerable<Coordinates> GetNeighbors(int rows, int columns)
{
var increasedRow = Row + 1;
var decreasedRow = Row - 1;
var increasedColumn = Column + 1;
var decreasedColumn = Column - 1;
var canIncreaseRow = increasedRow < rows;
var canIncreaseColumn = increasedColumn < columns;
var canDecreaseRow = decreasedRow > -1;
var canDecreaseColumn = decreasedColumn > -1;
if (canDecreaseRow)
{
if (canDecreaseColumn)
{
yield return new Coordinates(decreasedRow, decreasedColumn);
}
yield return new Coordinates(decreasedRow, Column);
if (canIncreaseColumn)
{
yield return new Coordinates(decreasedRow, increasedColumn);
}
}
if (canIncreaseRow)
{
if (canDecreaseColumn)
{
yield return new Coordinates(increasedRow, decreasedColumn);
}
yield return new Coordinates(increasedRow, Column);
if (canIncreaseColumn)
{
yield return new Coordinates(increasedRow, increasedColumn);
}
}
if (canDecreaseColumn)
{
yield return new Coordinates(Row, decreasedColumn);
}
if (canIncreaseColumn)
{
yield return new Coordinates(Row, increasedColumn);
}
}
}
好的,现在我们需要一种方法,一旦返回具有指定最小字符数且不超过指定最大值的单词,就会遍历访问每个位置的矩阵。
public static IEnumerable<string> GetWords(char[,] matrix,
Coordinates startingPoint,
int minimumLength,
int maximumLength)
看起来是正确的。现在,在递归时我们需要跟踪我们访问过的字符,使用我们的不可变堆栈很容易,所以我们的递归方法看起来像:
static IEnumerable<string> getWords(char[,] matrix,
ImmutableStack<char> path,
ImmutableStack<Coordinates> visited,
Coordinates coordinates,
int minimumLength,
int maximumLength)
现在剩下的只是管道和连接电线:
public static IEnumerable<string> GetWords(char[,] matrix,
Coordinates startingPoint,
int minimumLength,
int maximumLength)
=> getWords(matrix,
ImmutableStack<char>.Empty,
ImmutableStack<Coordinates>.Empty,
startingPoint,
minimumLength,
maximumLength);
static IEnumerable<string> getWords(char[,] matrix,
ImmutableStack<char> path,
ImmutableStack<Coordinates> visited,
Coordinates coordinates,
int minimumLength,
int maximumLength)
{
var newPath = path.Push(matrix[coordinates.Row, coordinates.Column]);
var newVisited = visited.Push(coordinates);
if (newPath.Count > maximumLength)
{
yield break;
}
else if (newPath.Count >= minimumLength)
{
yield return new string(newPath.Reverse().ToArray());
}
foreach (Coordinates neighbor in coordinates.GetNeighbors(matrix.GetLength(0), matrix.GetLength(1)))
{
if (!visited.Contains(neighbor))
{
foreach (var word in getWords(matrix,
newPath,
newVisited,
neighbor,
minimumLength,
maximumLength))
{
yield return word;
}
}
}
}
我们已经完成了。这是最优雅或最快的算法吗?可能不是,但我发现它是最容易理解的,因此也是可维护的。希望它可以帮助你。
更新根据以下评论,我运行了一些测试用例,其中一个是:
var matrix = new[,] { {'a', 'l'},
{'g', 't'} };
var words = GetWords(matrix, new Coordinates(0,0), 2, 4);
Console.WriteLine(string.Join(Environment.NewLine, words.Select((w,i) => $"{i:00}: {w}")));
结果是预期的:
00: ag
01: agl
02: aglt
03: agt
04: agtl
05: at
06: atl
07: atlg
08: atg
09: atgl
10: al
11: alg
12: algt
13: alt
14: altg