簡體   English   中英

在列表上工作需要花費太多時間

[英]Working on a list takes too much time

這是我的班級:

public class Record
{
   public string PersonName {get; set;}
   public string RequestID {get; set;}
}

我有一個與此類相關的數據庫表,我在開始時將所有內容都拉到了內存中。 我正在嘗試使用以下算法找到兩個人之間的關系:

  1. 我選了兩個人(我的意思是那個列表中的記錄,可以有兩個屬性的多個記錄)
  2. 我列出了第一個人的RequestID
  3. 對於每個RequestID,我列出具有相同RequestID用戶
  4. 對於每個用戶,我檢查該用戶是否與第二個用戶具有相同的RequestID
  5. 如果發現,打破並做一些事情。

以下是我對上述算法的實現:

foreach(var elem in listofFirstPerson)
{
  List<Record> listofRelatedPeople = RecordList.Where(r => r. RequestID == elem.RequestID).ToList(); //I actually get distinct records from actual list and the distinct version count is about 100k

  foreach(var relatedPerson in listofRelatedPeople )
  {
    List<Record> listofRecordsforRelatedPerson = RecordList.Where(r => r. PersonName ==  relatedPerson.PersonName).ToList();

    for(int i = 0; i < listofRecordsforRelatedPerson.Count; i++)
    {
      for(int j = 0; j < listofSecondPerson.Count; j++)
      {
       if(listofRecordsforRelatedPerson[i].RequestID ==listofSecondPerson[j].RequestID)
        //break all loops and do stuff
      }
    }
  }
}

該算法有效。 但它非常緩慢。 正如我所提到的, listofRelatedPeople大約是100k,它在大約20秒內只迭代幾百條記錄。 如何讓這個算法更快? 有更快的方法嗎? 提前致謝。

編輯:

在我的列表中有這樣的記錄:

  • 名稱:“Jason”RequestID:“1”
  • 名稱:“Larry”RequestID:“1”
  • 姓名:“Kevin”RequestID:“7”
  • 名稱:“Joshua”RequestID:“4”
  • 名稱:“Tom”RequestID:“1”
  • 名稱:“Tom”RequestID:“7”
  • 名稱:“Ben”RequestID:“7”

假設我選擇JasonKevin ,因為您看到他們的請求ID不相同所以我需要找到他們之間的關系。 所以我列出了具有相同RequestID的用戶,他們是LarryTom 然后我得到Larry的所有記錄,我發現他沒有與Kevin有相同RequestID的記錄。 因此我去Tom ,我看到TomKevin有相同的RequestID ,所以我選擇了Tom並且完成了。

我理解它的方式,你當前的算法可以用LINQ表示如下:

static Record FirstRelated(List<Record> records, string firstName, string secondName)
{
    var listofFirstPerson = records.Where(r => r.PersonName == firstName).ToList();
    var listofSecondPerson = records.Where(r => r.PersonName == secondName).ToList();
    var result = (
        from r1 in listofFirstPerson // (1)
        from r2 in records //(2)
        where r2.RequestID == r1.RequestID
        from r3 in records // (3)
        where r3.PersonName == r2.PersonName
        from r4 in listofSecondPerson // (4)
        where r4.RequestID == r3.RequestID
        select r2
    ).FirstOrDefault();
    return result;
}

所以基本上你有4個嵌套循環。 如果我們指定

N = records.Count
M1 = listofFirstPerson.Count
M2 = listofSecondPerson.Count

那么算法的時間復雜度將是O(M1 * N * N * M2),其中大N是正常的,導致性能問題。

查看上面的實現,可以注意到,通過將(1)與(2),(3)與(4)合並並將結果集合與PersonName相關聯,可以實現相同的結果:

var firstRelated =
    from r1 in listofFirstPerson
    from r2 in records
    where r2.RequestID == r1.RequestID
    select r2;
var secondRelated =
    from r4 in listofSecondPerson
    from r3 in records
    where r3.RequestID == r4.RequestID
    select r3;
var result = (
    from r1 in firstRelated
    from r2 in secondRelated
    where r2.PersonName == r1.PersonName
    select r1
).FirstOrDefault();

到目前為止,我們沒有改進任何東西 - 算法仍然是相同的二次時間復雜度。 但它給了我們這個想法 - 因為現在firstRelatedsecondRelated是獨立的,所以不需要為secondRelated 的每個記錄執行firstRelated ,因此我們可以提前從secondRelated准備一個快速哈希查找數據結構(平均值為O( 1)查找時間復雜度),並在迭代一次firstRelated時使用它,導致更好的O(M1 * N)時間復雜度(幾乎就像消除代碼中導致緩慢的最后兩個內部循環的成本)。

另請注意,我們不再需要構建兩個初始列表,因為我們只處理firstRelatedsecondRelated一次。

所以最終的解決方案是這樣的:

var firstRelated =
    from r1 in records
    where r1.PersonName == firstName
    from r2 in records
    where r2.RequestID == r1.RequestID
    select r2;
var secondRelated =
    from r4 in records
    where r4.PersonName == secondName
    from r3 in records
    where r3.RequestID == r4.RequestID
    select r3;

現在要么使用LINQ join運算符為我們做有效的關聯:

var result = (
    from r1 in firstRelated
    from r2 in secondRelated
    where r2.PersonName == r1.PersonName
    select r1
).FirstOrDefault();

或者通過准備和使用帶有來自secondRelated PersonNameHashSet來手動secondRelated

var secondRelatedNames = new HashSet<string>(secondRelated.Select(r => r.PersonName));
var result = firstRelated.FirstOrDefault(r => secondRelatedNames.Contains(r.PersonName));

“。Where()”和“。ToList()”都是非常慢的操作。

您可以將“RecordList”映射到兩個字典,其中“RequestID”作為另一個“PersonName”。 在forech之前做它。 這應該跑得快得多。

var dictionary1 = RecordList.GroupBy(f => f.RequestID).ToDictionary(f => f.Key, v => v.ToArray());
var dictionary2 = RecordList.GroupBy(f => f.PersonName).ToDictionary(f => f.Key, v => v.ToArray()); 

然后在foreach內部你可以使用它們

var listofRelatedPeople = dictionary1[elem.RequestID];
var listofRecordsforRelatedPerson= dictionary2[relatedPerson.PersonName];

當然,如果密鑰不存在的可能性,最好使用dictionary1.TryGetValue()

UPDATE

如果您需要C#方式,其中一個解決方案可能是:

var recordList = new Record[]
{
    new Record() {RequestID = "1", PersonName = "User1"},
    new Record() {RequestID = "2", PersonName = "User1"},
    new Record() {RequestID = "3", PersonName = "User2"},
    new Record() {RequestID = "1", PersonName = "User2"},
    new Record() {RequestID = "4", PersonName = "User3"},
    new Record() {RequestID = "5", PersonName = "User3"},
    new Record() {RequestID = "1", PersonName = "User4"},
    new Record() {RequestID = "6", PersonName = "User4"},
    new Record() {RequestID = "7", PersonName = "User5"},
    new Record() {RequestID = "1", PersonName = "User5"},

};
var dictionary1 = recordList.GroupBy(f => f.RequestID).ToDictionary(f => f.Key, v => v.Select(z=>z.PersonName).ToArray());
var dictionary2 = recordList.GroupBy(f => f.PersonName).ToDictionary(f => f.Key, v => v.Select(z => z.RequestID).ToArray());

var rec1 = dictionary2["User1"]; //all requestsIds for User1
var rec2 = dictionary2["User2"]; //all requestsIds for User2

var ids = rec1.Intersect(rec2).Distinct();  //only request ids exists for both users in same time

foreach (var id in ids)
{
    var users = dictionary1[id];
    if (users.Length > 2)
        break;
    //users = User1, User2, User4, User5
}

更新2

SQL版本(MSSQL)這將比C#快得多

CREATE TABLE #tmp (ID varchar(max), Name varchar(max))
INSERT INTO #tmp (ID, Name) 
SELECT '1', 'User1' UNION ALL
SELECT '2', 'User1' UNION ALL
SELECT '3', 'User2' UNION ALL 
SELECT '1', 'User2' UNION ALL 
SELECT '4', 'User3' UNION ALL 
SELECT '5', 'User3' UNION ALL 
SELECT '1', 'User4' UNION ALL 
SELECT '6', 'User4' 


SELECT C.Name 
FROM #tmp A
INNER JOIN #tmp B ON A.ID = B.ID
INNER JOIN #tmp C ON A.ID = C.ID
WHERE A.Name = 'User1' and B.Name = 'User2' AND C.Name NOT IN ('User1', 'User2')

回復將是“User4”

我認為你應該讓數據庫完成工作,它會快得多。

查詢看起來像這樣:

SELECT
    r2.*
FROM
    record r1 INNER JOIN
    record r2 ON
        r2.requestId = r1.requestId AND
        r2.personName = 'NAME 2'
WHERE
    r1.personName = 'NAME 1'

我們請求第1個人的所有requestIds,看它是否與第2個人的任何一個匹配。

分組可以一次完成。 這樣做的好處不僅在於它更快,因為它只是一次通過,但如果您正在對DB進行LINQ,那么它將由DB在服務器上執行,從而減少發送到客戶端的數據量並加快使用索引等進程

        var source = new List<Record> { };
        var grouped = source
            .GroupBy(x => x.RequestID)
            //Only groups with more than one entry
            .Where(x => x.Count() > 1);

        //Loop through the data like so
        foreach(var group in grouped)
        {
            Console.WriteLine("Request: " + group.Key);
            foreach(Record record in group)
                Console.WriteLine("  " + record.PersonName);
        }

如果您希望PersonName屬性成為某種唯一標識符,以便您可以消除每個RequestID多次存在同一個人的情況,您可以執行此操作

        var source = new List<Record> { };
        var grouped = source
            .GroupBy(x => x.RequestID)
            //Select a key + only unique names
            .Select(x => new { Key = x.Key, Data = x.Select(r => r.PersonName).Distinct()})
            //Only groups with more than 1 entry
            .Where(x => x.Data.Count() > 1);

        //To loop through the data
        foreach(var group in grouped)
        {
            Console.WriteLine("Group: " + group.Key);
            foreach(var item in group.Data)
            {
                Console.WriteLine("  " + item.PersonName);
            }
        }

嗯,也許是一件小事,但是,你創建使用記錄列表。 您已創建新對象但未使用它。 所以你沒有對你的where子句做任何事情。

List<Record> listofRecordsforRelatedPerson = RecordList.Where(r => r. PersonName  

- >將記錄列表更改為relatedPeople並在兩個for循環中使用:

for(int j = 0; j <= listofSecondPerson.Count; j++)
{...} 

否則,例如,如果你的計數是25 anj = 25,它將什么都不做

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM