【问题标题】:Split list of strings by list of key words [closed]按关键字列表拆分字符串列表[关闭]
【发布时间】:2020-04-01 21:22:47
【问题描述】:

我有一个字符串列表 例如{“apple.txt”、“orange.sd.2.txt”、“apple.2.tf.txt”、“orange.txt”} 和另一个字符串列表来分组第一个列表 例如{“苹果”,“橙色”} 这样第一个列表就被拆分成一个列表,看起来像这样:

{{"apple.txt", "apple.2.tf.txt"},{"orange.txt", "orange.sd.2.txt"}}

如何使用 linq 实现这一点?

【问题讨论】:

  • 如果一个字符串被称为“apple.orange.txt”,它的表现如何?
  • 它不是一个用例,但可以在两个组中
  • 最终列表的顺序是否需要与分组列表中的顺序一致?

标签: c# list linq


【解决方案1】:

这个怎么样:

var groupedList = firstList.GroupBy(x => secondList.Single(y => x.Name.Contains(y)));

【讨论】:

  • 这是您问题的答案还是延伸?
  • 您可能想要使用First,除非您确定给定的firstList 成员不能有多个键匹配。您可能想要使用FirstOrDefault,除非您肯定每个firstList 成员都将属于一个组。否则,做得很好。
  • 您可以使用SelectMany 进行更多处理来处理属于多个组的项目:dataToSplit.SelectMany(ds => keyList.Where(k => ds.Contains(k)).Select(k => new { Key = k, Value = ds })) .GroupBy(kds => kds.Key, kds => kds.Value)
  • @GertArnold 我猜一个可能的答案
  • “这个怎么样:”是一个新问题。
【解决方案2】:

您可以使用匿名类型的SplitSelectManyGroupBy,将每个原始list 的元素按所有 个可能的键分组:

var list = new List<string> { "apple.txt", "orange.sd.2.txt", "apple.2.tf.txt", "orange.txt" };
var groups = list
    .SelectMany(element => element
        .Split('.')
        .Select(part => new { Part = part, Full = element }))
    .GroupBy(entry => entry.Part);

现在您可以使用Where 选择要保留的组,然后使用SelectToList 将结果转换为嵌套列表:

var keys = new List<string> { "apple", "orange" };
var result = group
    .Where(group => keys.Contains(group.Key))
    .Select(group => group
        .Select(entry => entry.Full)
        .ToList())
    .ToList();

注意原始列表中不包含任何指定键的元素将不会出现在结果中,而包含多个指定键的元素将在结果中出现多次。

编辑:正如@NetMage 所指出的,我对拆分字符串做出了错误的假设 - 这是另一个版本,尽管它是O(m * n)

var result = keys
    .Select(key => list.Where(element => element.Contains(key)).ToList())
    .ToList();

【讨论】:

  • 有趣的方法 - 通过将 keys 转换为 HashSet 我认为这最接近 O(n) 解决方案,尽管我相信处理所有不需要的部分的开销以及拆分的假设在. 上可行意味着它不是最佳答案。
  • @NetMage 谢谢,你说得对,这是一个不正确的假设 - 为我的答案添加了替代方案
【解决方案3】:

这是一种简单的方法。有很多方法,这将包括重复的密钥作为我对您的问题的评论。如果许多键匹配相同的数据,则分组将包括副本。

// have the list of keys (groups)
var keyList = new List<string>() {"apple", "orange"};

// have the list of all the data to split
var dataToSplit = new List<string>() 
{
    "apple.txt", 
    "apple.2.tf.txt",
    "orange.txt", 
    "orange.sd.2.txt"
};

// now split to get just as desired you select what you want for each keys
var groupedData = keyList.Select(key => dataToSplit.Where(data => data.Contains(key)).ToList()).ToList();

// groupedData is a List<List<string>>

以更“对象”的方式获取值的第二个选项是使用匿名。如果您要进行大量操作并且代码中更加“冗长”,则特别好。但如果你是新手,我不推荐这种方法,但无论如何就是这样。

// have the list of keys (groups)
var keyList = new List<string>() {"apple", "orange"};

// have the list of all the data to split
var dataToSplit = new List<string>() 
{
    "apple.txt", 
    "apple.2.tf.txt",
    "orange.txt", 
    "orange.sd.2.txt"
};

// create the anonymous
var anonymousGroup = keyList.Select(key =>
{
    return new 
    { 
        Key = key, 
        Data = dataToSplit.Where(data => data.Contains(key)).ToList()
    }
});

// anonymousGroup is a List<A> where keeping the order you should access all data for orange like this
var orangeGroup = anonymousGroup.FirstOfDefault(o=> o.Key = "orange"); // get the anonymous
var orangeData = orangeGroup.Data; // get the List<string> for that group

第三种方法的复杂度低于 O(m*n)。诀窍是从集合中删除数据,以减少重新检查已处理项目的机会。这是来自我的代码库,它是 List 的扩展,它只是根据谓词从集合中删除项目并返回已删除的内容。

public static List<T> RemoveAndGet<T>(this List<T> list, Func<T, bool> predicate)
{
    var itemsRemoved = new List<T>();

    // iterate backward for performance
    for (int i = list.Count - 1; i >= 0; i--)
    {
        // keep item pointer
        var item = list[i];

        // if the item match the remove predicate
        if (predicate(item))
        {
            // add the item to the returned list
            itemsRemoved.Add(item);

            // remove the item from the source list
            list.RemoveAt(i);
        }
    }

    return itemsRemoved;
}

现在有了这个扩展,当你有一个列表时,你可以像这样轻松地使用它:

// have the list of keys (groups)
var keyList = new List<string>() {"apple", "orange"};

// have the list of all the data to split
var dataToSplit = new List<string>() 
{
    "apple.txt", 
    "apple.2.tf.txt",
    "orange.txt", 
    "orange.sd.2.txt"
};

// now split to get just as desired you select what you want for each keys
var groupedData = keyList.Select(key => dataToSplit.RemoveAndGet(data => data.Contains(key))).ToList();

在这种情况下,由于两个集合中的顺序,第一个键是 apple,因此它将迭代 dataToSplit 中的 4 个项目并仅保留 2 个并将 dataToSplit 集合减少到 2 个项目仅是具有orange 在其中。在第二个键上,它将仅迭代 2 个项目,这将使其在这种情况下更快。通常,此方法将与我提供的前 2 个方法一样快或更快,同时清晰且仍然使用 linq。

【讨论】:

  • 我试图想办法避免 O(m*n) 并使其成为 O(n),但我看不到。
  • 如果你有 2 个键的项目可以属于 2 个类别,那么除了 O(m*n) 之外,你不能做任何其他方式。如果您不在乎,并且这些情况可能属于随机情况,则可以降低复杂性。您需要的是迭代数据集合并在找到匹配项时从该集合中删除,这将在构建列表时减少后续键的数据子集。
  • 有趣 - 我的迭代是在数据上然后在键上,假设数据更长,所以如果你只需要第一个包含的键,你可以停止早期搜索键。
【解决方案4】:

您可以使用这个简单的代码来实现:

var list1 = new List<string>() {"apple.txt", "orange.sd.2.txt", "apple.2.tf.txt", "orange.txt"};
var list2 = new List<string>() {"apple", "orange"};
var result = new List<List<string>>();

list2.ForEach(e => {
    result.Add(list1.Where(el => el.Contains(e)).ToList());
});

【讨论】:

    【解决方案5】:

    元组来救援!

    var R = new List<(string, List<string>)> { ("orange", new List<string>()), ("apple", new List<string>()) };
    var L = new List<string> { "apple.txt", "apple.2.tf.txt", "orange.txt", "orange.sd.2.txt" };
    R.ForEach(r => L.ForEach(l => { if (l.Contains(r.Item1)) { r.Item2.Add(l); } }));
    var resultString = string.Join("," , R.Select(x => "{" + string.Join(",", x.Item2) + "}"));
    

    如果需要,您可以轻松地动态构建 R。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-03
      • 2019-02-16
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-06
      相关资源
      最近更新 更多