【问题标题】:Finding same integer ranges that are in two arrays查找两个数组中的相同整数范围
【发布时间】:2018-07-16 12:35:09
【问题描述】:

我正在尝试查找由数组中两个或多个相邻数字组成的范围的索引和长度,该数组可能也存在于另一个数组中(但可能不会从相同的索引开始)。示例:

var source = new[] {6, 15, 8, 1, 2, 4, 11, 21};
var target = new[] {8, 1, 2, 4, 15, 11, 21, 6};

这两个数组不同,但都拥有两个相等范围的两个相邻数字[1, 2][11, 21]。我想编写一个函数,可以在源数组中找到此类范围的起始索引和长度(在这种情况下,索引 3,第一个长度为 2,索引 6,第二个长度为 2)。

我最初的方法可能是编写嵌套循环,迭代两个数组中的每个字段并比较它们,但这很快就会变成大型数组的性能杀手。是否有任何有用的 LINQ API 或其他方法来查找这些值?

【问题讨论】:

  • 那么{8, 1, 2, 4}
  • @DragandDrop 对,没看到那个!
  • @xanatos 感谢您的有用评论!这至少给了我一个可以搜索的名字。

标签: c# arrays linq range


【解决方案1】:

这并没有以任何方式优化性能。我相信您可以使用某种类型的子字符串搜索优化 (Boyer-Moore?) 来提高性能。

一般来说,LINQ 解决方案不会比过程解决方案更有效,尤其是在处理索引速度很快的数组时。

使用几个扩展方法,您可以找到所有常见的子序列。首先,为一个序列生成所有可能的子序列的扩展方法:

public static IEnumerable<IEnumerable<T>> Subsequences<T>(this IEnumerable<T> src) => 
    Enumerable.Range(0, src.Count() - 1)
              .Select(k => src.Skip(k))
              .SelectMany(s1s => Enumerable.Range(2, s1s.Count() - 1).Select(k => s1s.Take(k)));

通过跳过越来越多的开始项目来生成所有序列(长度至少为 2),然后通过从末尾删除越来越多的项目来生成这些序列的所有子序列。

使用两个参数Select方法,你可以记住每个项目的位置以备后用。我转换为List,因此调用Count 时子序列生成效率更高:

var s1ps = s1.Select((n,i) => (n,i)).ToList().Subsequences();
var s2ps = s2.Select((n,i) => (n,i)).ToList().Subsequences();

现在,您可以从源中找到所有匹配的子序列。然后,您可以按照它们在原始源中的起始位置对匹配进行分组,并保留最长的匹配,并按结束位置分组并保留最长的匹配。为了解决这个问题,请使用扩展方法,将 IEnumerable 按键函数分组,然后根据值函数保持最大值:

public static IEnumerable<T> MaximumMatch<T, TKey>(this IEnumerable<T> src, Func<T,TKey> keyFn, Func<T,int> valueFn) =>
    src.GroupBy(keyFn).Select(sg => sg.OrderByDescending(s => valueFn(s)).First());

通过两次应用此扩展,您可以获得所有最长的匹配子序列:

var ans = s1ps.SelectMany(as1p => s2ps.Where(as2p => as1p.Count() == as2p.Count()).Where(as2p => as1p.Select(sp => sp.n).SequenceEqual(as2p.Select(sp => sp.n))).Select(as2p => (as1p,as2p)))
                .MaximumMatch(st => st.as1p.First().i, st => st.as1p.Count())
                .MaximumMatch(st => st.as1p.Last().i, st => st.as1p.Count())
                .Select(stg => new { s1begin = stg.as1p.First().i, s1end = stg.as1p.Last().i, s2begin = stg.as2p.First().i, s2end = stg.as2p.Last().i });

最后,您获取每个匹配的子序列并将其投影到其开始和结束位置。

【讨论】:

  • 我正在尝试使用您的建议,但在使用 s1.Select((, i)... 时出现错误:error CS8179: Predefined type System.ValueTuple 2 is not defined or imported, or is declared in multiple referenced assemblies
  • ... 我应该尝试明确指定 Select 的类型参数。但是它需要什么类型的参数呢?我尝试了各种类型的元组,但都不起作用。
  • 该代码使用最新版本的 C# 中的 ValueTuple。如果您使用的是旧版本的 .Net,则必须使用 NuGet 添加 ValueTuple。否则,只需将元组替换为匿名对象即可。
  • 感谢@NetMage,该错误已通过切换到 Net 4.7.1 得到解决,但我仍然遇到其他错误...在sp.n 的最后一个代码块中,n 无法解决,stg.as1p stgas2p无法解决。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-15
  • 2014-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-06
  • 2018-09-02
相关资源
最近更新 更多