1

毎朝別々のXMLファイルで受信される大量のデータがあります。XML内のオブジェクトを組み合わせて、それらからレポートを生成する必要があります。私はこの問題に最適な解決策を使用することを探しています。

実証するために、次の例を作成しました。

2つのXMLファイルがあります。

1つ目は、言語とそれらが話されている国のリストです。2つ目は、製品とそれらが販売されている国のリストです。私が生成するレポートは、製品名とそれに続くパッケージの言語です。

XML1:

<?xml version="1.0" encoding="utf-8"?>
<languages>
  <language>
    <name>English</name>
    <country>8</country>
    <country>9</country>
    <country>3</country>
    <country>11</country>
    <country>12</country>
  </language>
  <language>
    <name>French</name>
    <country>3</country>
    <country>6</country>
    <country>7</country>
    <country>13</country>
  </language>
  <language>
    <name>Spanish</name>
    <country>1</country>
    <country>2</country>
    <country>3</country>
  </language>
</languages>

XML2:

<?xml version="1.0" encoding="utf-8"?>
<products>
  <product>
    <name>Screws</name>
    <country>3</country>
    <country>12</country>
    <country>29</country>
  </product>
  <product>
    <name>Hammers</name>
    <country>1</country>
    <country>13</country>
  </product>
  <product>
    <name>Ladders</name>
    <country>12</country>
    <country>39</country>
    <country>56</country>
  </product>
  <product>
    <name>Wrenches</name>
    <country>8</country>
    <country>13</country>
    <country>456</country>
  </product>
  <product>
    <name>Levels</name>
    <country>19</country>
    <country>18</country>
    <country>17</country>
  </product>
</products>

サンプルプログラム出力:

 Screws ->  English, French, Spanish
 Wrenches ->  English, French
 Hammer - > French, Spanish
 Ladders-> English

現在、DataSetに逆シリアル化し、linqを使用してデータセット間で結合し、必要なレポート文字列を生成しています。(以下に表示-ファイルの名前をコマンドライン引数として渡します)。

public static List<String> XMLCombine(String[] args)
{
    var output = new List<String>();
    var dataSets = new List<DataSet>();
    //Load each of the Documents specified in the args
    foreach (var s in args)
    {
        var path = Environment.CurrentDirectory + "\\" + s;
        var tempDS = new DataSet();
        try
        {
            tempDS.ReadXml(path);
        }
        catch (Exception ex)
        {
            //Custom Logging + Error Reporting
            return null;
        }
        dataSets.Add(tempDS);
    }
    //determine order of files submitted
    var productIndex = dataSets[0].DataSetName == "products" ? 0:1;
    var languageIndex = dataSets[0].DataSetName == "products" ? 1:0;
    var joined = from tProducts in dataSets[productIndex].Tables["product"].AsEnumerable()
                 join tProductCountries in dataSets[productIndex].Tables["country"].AsEnumerable() on (int)tProducts["product_id"] equals (int)tProductCountries["product_id"]
                 join tLanguageCountries in dataSets[languageIndex].Tables["country"].AsEnumerable() on (String)tProductCountries["country_text"] equals (String)tLanguageCountries["country_text"]
                 join tLanguages in dataSets[languageIndex].Tables["language"].AsEnumerable() on (int)tLanguageCountries["language_Id"] equals (int)tLanguages["language_Id"]
                  select new
                  {
                      Language = tLanguages["name"].ToString(),
                      Product = tProducts["name"].ToString()
                  };

    var listOfProducts = joined.OrderByDescending(_ => _.Product).Select(_ => _.Product).Distinct().ToList();

    foreach (var e in listOfProducts)
    {
        var e1 = e;
        var languages = joined.Where(_ => _.Product == e1).Select(_ => _.Language).Distinct().ToList();
        languages.Sort();
        //Custom simple Array to text method
        output.Add(String.Format("{0} {1}", e, ArrayToText(languages)));
    }
    return output;
}

これは問題なく機能しますが、この問題にはもっと最適な解決策が必要であることを私は知っています(特に、XMLファイルが実際に巨大な場合)。誰かが別のアプローチ(linq以外)の経験や、現在のアプローチを最適化することで私を最良の解決策に近づけるためのアドバイスを持っていますか?

よろしくお願いします。

ソリューション 提案されたソリューションの実装:辞書を使用したCasperahのアプローチは、312msでデータセットを処理しました。Linq Lookupを使用したyamenのアプローチは、452msでデータセットを処理しました。

4

3 に答える 3

2

メモリ使用量と CPU 使用量の 2 つの問題があります。

メモリ使用量を制限するには、巨大な xml ファイルの小さなチャンクのみを読み取る XmlReader を使用できます。CPU 使用率を制限するには、国コードにインデックスが必要です。

1. すべての言語を読み取り、次のように辞書に挿入します。 // キーは国、値は言語のリストです。Dictionary> 国 = 新しい Dictionary>(); 2. XmlReader を使用して一度に 1 つずつ製品を読み取ります。 3. 国を検索し、言語の重複を避けるために HashSet を使用して言語を書き出します。

それが私のアプローチです-頑張ってください

この例を作成しました:

        Dictionary<int, List<string>> countries = new Dictionary<int, List<string>>();

        XmlReader xml = XmlReader.Create("file://D:/Development/Test/StackOverflowQuestion/StackOverflowQuestion/Countries.xml");
        string language = null;
        string elementName = null;
        while (xml.Read())
        {
            switch (xml.NodeType)
            {
                case XmlNodeType.Element:
                    elementName = xml.Name;
                    break;

                case XmlNodeType.Text:
                    if (elementName == "name") language = xml.Value;
                    if (elementName == "country")
                    {
                        int country;
                        if (int.TryParse(xml.Value, out country))
                        {
                            List<string> languages;
                            if (!countries.TryGetValue(country, out languages))
                            {
                                languages = new List<string>();
                                countries.Add(country, languages);
                            }
                            languages.Add(language);
                        }
                    }
                    break;
            }
        }
        using (StreamWriter result = new StreamWriter(@"D:\Development\Test\StackOverflowQuestion\StackOverflowQuestion\Output.txt"))
        {
            xml = XmlReader.Create("file://D:/Development/Test/StackOverflowQuestion/StackOverflowQuestion/Products.xml");
            string product = null;
            elementName = null;
            HashSet<string> languages = new HashSet<string>();
            while (xml.Read())
            {
                switch (xml.NodeType)
                {
                    case XmlNodeType.Element:
                        elementName = xml.Name;
                        break;

                    case XmlNodeType.Text:
                        if (elementName == "name")
                        {
                            if (product != null && languages != null)
                            {
                                result.Write(product);
                                result.Write(" -> ");
                                result.WriteLine(string.Join(", ", languages.ToArray()));
                                languages.Clear();
                            }
                            product = xml.Value;
                        }
                        if (elementName == "country")
                        {
                            int country;
                            if (int.TryParse(xml.Value, out country))
                            {
                                List<string> countryLanguages;
                                if (countries.TryGetValue(country, out countryLanguages))
                                    foreach (string countryLanguage in countryLanguages) languages.Add(countryLanguage);
                            }
                        }
                        break;
                }
            }
        }
    }

次の例が生成されます。

Screws -> English, French, Spanish
Hammers -> Spanish, French
Ladders -> English
Wrenches -> English, French

XmlReader.Create は uri を受け取ります。「http://www.mysite.com/countries.xml」のようなものを使用することもできます。

于 2012-05-28T11:55:51.203 に答える
1

OK、これはまだ LINQ to XML ですが、アルゴリズムに関しては非常に効率的だと思います。唯一の問題は、XML が非常に大きい場合 (つまり、RAM が保持できるサイズよりも大きい場合) です。そうしないと、これ以上速くなることはありません。

関連する XML ファイルが含まれていると仮定languageFileします。productFile

言語をルックアップに変換します。

var languages = (from language in XElement.Load(languageFile).Descendants("language")
                from country in language.Elements("country")
                select new {Language = language.Element("name").Value, Country = country.Value})
                .ToLookup(l => l.Country, l => l.Language);

次に、言語ルックアップで製品を取得します。

var products = from product in XElement.Load(productFile).Descendants("product")
               select new {Product = product.Element("name").Value, 
                           Languages = product.Elements("country").SelectMany(e => languages[e.Value]).Distinct().ToList()};

もちろん、それらを印刷することもできます:

foreach (var product in products.Where(x => x.Languages.Count > 0))
{
    Console.WriteLine("{0} -> {1}", product.Product, String.Join(", ", product.Languages));
}

どちらが返されますか:

Screws -> English, French, Spanish
Hammers -> Spanish, French
Ladders -> English
Wrenches -> English, French
于 2012-05-28T11:42:36.697 に答える
1

あなたの場合、言語ファイルのデータを辞書などに保存します。その後、各製品ファイルを解析し、その場で最終的な結合結果を生成します。この方法の方が高速で、大量のデータで発生するメモリの問題を回避できると思います。

于 2012-05-28T11:49:03.063 に答える