4

みなさん、ここに来た素晴らしいコミュニティです。私は電気技師で、請求書の支払いを支援するために「プログラミング」作業を行っています。私は適切なコンピュータサイエンスのトレーニングを受けていないことを考慮に入れてほしいので、これを言いますが、私は過去7年間コーディングを行っています。

私は情報(すべて数値)を含むいくつかのExcelテーブルを持っています。基本的には、1つの列に「ダイヤルされた電話番号」があり、別の列にそれらの各番号の分数があります。これとは別に、私の国のさまざまなキャリアの「キャリアプレフィックスコード番号」のリストがあります。私がやりたいのは、キャリアごとにすべての「トラフィック」を分離することです。シナリオは次のとおりです。

最初にダイヤルした番号の行123456789ABCD、100 <-これは13桁の電話番号と100分になります。

キャリア1の12,000以上のプレフィックスコードのリストがあります。これらのコードの長さはさまざまであり、すべてを確認する必要があります。

プレフィックスコード11234567 <-このコードの長さは7桁です。

ダイヤルした番号の最初の7桁を確認して、ダイヤルした番号と比較する必要があります。一致するものが見つかった場合は、後で使用するために小計に分数を追加します。すべてのプレフィックスコードが同じ長さであるとは限らず、短い場合や長い場合もあることを考慮してください。

これのほとんどは簡単なことであり、私はそれを行うことができるはずですが、私は大量のデータに少し怖がっています。ダイヤル番号リストは最大30,000の番号で構成され、「キャリアプレフィックスコード」は約13,000行の長さである場合があり、通常は3つのキャリアをチェックします。つまり、多くの「一致」を行う必要があります。

C#を使用してこれを効率的に行う方法を知っている人はいますか?または他の言語は親切に正直です。私はこれをかなり頻繁に行う必要があり、それを行うためのツールを設計することははるかに理にかなっています。その「コンピューターサイエンティスト」のバックグラウンドを持っている人からの良い視点が必要です。

リストはExcelワークシートにある必要はありません。csvファイルにエクスポートしてそこから作業できます。「MSOffice」インターフェイスは必要ありません。

ご協力いただきありがとうございます。

アップデート:

私の質問に答えてくれてありがとう。私の無知の中で、私は「効率的」という言葉を誇張しすぎたと思います。私はこのタスクを数秒ごとに実行しません。それは私が1日に1回しなければならないことであり、ExcelやVLOOKUPなどで行うのは嫌いです。

私は皆さんから新しい概念について学びました。皆さんのアイデアを使用してソリューションを構築できることを願っています。

4

7 に答える 7

11

アップデート

簡単なトリックを行うことができます-プレフィックスを最初の数字で辞書にグループ化し、正しいサブセットに対してのみ番号を照合します。すべてのプレフィックスに少なくとも3つのデジがあると仮定して、次の2つのLINQステートメントでテストしました。

const Int32 minimumPrefixLength = 3;

var groupedPefixes = prefixes
    .GroupBy(p => p.Substring(0, minimumPrefixLength))
    .ToDictionary(g => g.Key, g => g);

var numberPrefixes = numbers
    .Select(n => groupedPefixes[n.Substring(0, minimumPrefixLength)]
        .First(n.StartsWith))
    .ToList();

それで、これはどれくらい速いですか?15.000のプレフィックスと50.000の番号は250ミリ秒未満かかりました。2行のコードに十分な速さですか?

パフォーマンスは最小プレフィックス長(MPL)に大きく依存するため、作成できるプレフィックスグループの数に大きく依存することに注意してください。

     MPLランタイム
    -----------------
     110.198ミリ秒
     21.179ミリ秒
     3205ミリ秒
     4130ミリ秒
     5107ミリ秒

大まかなアイデアを与えるために-私はたった1回の実行を行い、他の多くのことを行っています。

元の回答

パフォーマンスについてはあまり気にしません。平均的なデスクトップPCは、1億行のデータベーステーブルを簡単に処理できます。たぶん5分かかりますが、1秒おきにタスクを実行したくないと思います。

テストをしました。5〜10桁の15.000の一意のプレフィックスを持つリストを生成しました。この接頭辞から、接頭辞と追加の5〜10桁の50.000の数字を生成しました。

List<String> prefixes = GeneratePrefixes();
List<String> numbers = GenerateNumbers(prefixes);

次に、次のLINQ to Objectクエリを使用して、各番号のプレフィックスを見つけました。

var numberPrefixes = numbers.Select(n => prefixes.First(n.StartsWith)).ToList();

ええと、2.0GHzのCore2Duoラップトップでは約1分かかりました。したがって、1分の処理時間が許容できる場合、集約を含める場合は2、3分程度である場合、私は何も最適化しようとはしません。もちろん、プログラムが1、2秒でタスクを実行できれば本当に素晴らしいのですが、これによりかなり複雑になり、多くの間違いが発生します。また、設計、作成、テストには時間がかかります。LINQステートメントは私のほんの数秒しかかかりませんでした。

テストアプリケーション

多くのプレフィックスの生成は非常に遅く、1〜2分かかる場合があることに注意してください。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace Test
{
    static class Program
    {
        static void Main()
        {
            // Set number of prefixes and calls to not more than 50 to get results
            // printed to the console.

            Console.Write("Generating prefixes");
            List<String> prefixes = Program.GeneratePrefixes(5, 10, 15);
            Console.WriteLine();

            Console.Write("Generating calls");
            List<Call> calls = Program.GenerateCalls(prefixes, 5, 10, 50);
            Console.WriteLine();

            Console.WriteLine("Processing started.");

            Stopwatch stopwatch = new Stopwatch();

            const Int32 minimumPrefixLength = 5;

            stopwatch.Start();

            var groupedPefixes = prefixes
                .GroupBy(p => p.Substring(0, minimumPrefixLength))
                .ToDictionary(g => g.Key, g => g);

            var result = calls
                .GroupBy(c => groupedPefixes[c.Number.Substring(0, minimumPrefixLength)]
                    .First(c.Number.StartsWith))
                .Select(g => new Call(g.Key, g.Sum(i => i.Duration)))
                .ToList();

            stopwatch.Stop();

            Console.WriteLine("Processing finished.");
            Console.WriteLine(stopwatch.Elapsed);

            if ((prefixes.Count <= 50) && (calls.Count <= 50))
            {
                Console.WriteLine("Prefixes");
                foreach (String prefix in prefixes.OrderBy(p => p))
                {
                    Console.WriteLine(String.Format("  prefix={0}", prefix));
                }

                Console.WriteLine("Calls");
                foreach (Call call in calls.OrderBy(c => c.Number).ThenBy(c => c.Duration))
                {
                    Console.WriteLine(String.Format("  number={0} duration={1}", call.Number, call.Duration));
                }

                Console.WriteLine("Result");
                foreach (Call call in result.OrderBy(c => c.Number))
                {
                    Console.WriteLine(String.Format("  prefix={0} accumulated duration={1}", call.Number, call.Duration));
                }
            }

            Console.ReadLine();
        }

        private static List<String> GeneratePrefixes(Int32 minimumLength, Int32 maximumLength, Int32 count)
        {
            Random random = new Random();
            List<String> prefixes = new List<String>(count);
            StringBuilder stringBuilder = new StringBuilder(maximumLength);

            while (prefixes.Count < count)
            {
                stringBuilder.Length = 0;

                for (int i = 0; i < random.Next(minimumLength, maximumLength + 1); i++)
                {
                    stringBuilder.Append(random.Next(10));
                }

                String prefix = stringBuilder.ToString();

                if (prefixes.Count % 1000 == 0)
                {
                    Console.Write(".");
                }

                if (prefixes.All(p => !p.StartsWith(prefix) && !prefix.StartsWith(p)))
                {
                    prefixes.Add(stringBuilder.ToString());
                }
            }

            return prefixes;
        }

        private static List<Call> GenerateCalls(List<String> prefixes, Int32 minimumLength, Int32 maximumLength, Int32 count)
        {
            Random random = new Random();
            List<Call> calls = new List<Call>(count);
            StringBuilder stringBuilder = new StringBuilder();

            while (calls.Count < count)
            {
                stringBuilder.Length = 0;

                stringBuilder.Append(prefixes[random.Next(prefixes.Count)]);

                for (int i = 0; i < random.Next(minimumLength, maximumLength + 1); i++)
                {
                    stringBuilder.Append(random.Next(10));
                }

                if (calls.Count % 1000 == 0)
                {
                    Console.Write(".");
                }

                calls.Add(new Call(stringBuilder.ToString(), random.Next(1000)));
            }

            return calls;
        }

        private class Call
        {
            public Call (String number, Decimal duration)
            {
                this.Number = number;
                this.Duration = duration;
            }

            public String Number { get; private set; }
            public Decimal Duration { get; private set; }
        }
    }
}
于 2009-06-25T22:05:39.070 に答える
8

キャリアプレフィックスからトライを作成する必要があるように思えます。最終的には単一のトライになり、終端ノードがそのプレフィックスのキャリアを通知します。

次に、キャリアからintまたはlong(合計)への辞書を作成します。

次に、ダイヤルされた番号の行ごとに、キャリアが見つかるまでトライを下っていきます。運送業者のこれまでの合計分数を見つけて、現在の行を追加します。次に進みます。

于 2009-06-25T21:13:21.127 に答える
1

これをかなり効率的に行う最も簡単なデータ構造は、セットのリストです。すべてのプレフィックスを含むように、各キャリアのセットを作成します。

ここで、通話を通信事業者に関連付けるには、次のようにします。

foreach (Carrier carrier in carriers)
{
    bool found = false;

    for (int length = 1; length <= 7; length++)
    {
        int prefix = ExtractDigits(callNumber, length);

        if (carrier.Prefixes.Contains(prefix))
        {
            carrier.Calls.Add(callNumber);
            found = true;
            break;
        }
    }

    if (found)
        break;
}

10のキャリアがある場合、コールごとにセットに70のルックアップがあります。ただし、セット内のルックアップはそれほど遅くはありません(線形検索よりもはるかに高速です)。したがって、これにより、ブルートフォース線形探索よりもかなり高速になります。

さらに一歩進んで、長さに応じて各キャリアのプレフィックスをグループ化できます。そうすれば、キャリアに長さ7と4のプレフィックスしかない場合、その長さのプレフィックスのセットを調べるたびに、わざわざそれらの長さを抽出して検索するだけで済みます。

于 2009-06-25T21:41:04.037 に答える
1

データをいくつかのデータベーステーブルにダンプしてから、SQLを使用してそれらをクエリするのはどうですか?簡単!

CREATE TABLE dbo.dialled_numbers ( number VARCHAR(100), minutes INT )

CREATE TABLE dbo.prefixes ( prefix VARCHAR(100) )

-- now populate the tables, create indexes etc
-- and then just run your query...

SELECT p.prefix,
    SUM(n.minutes) AS total_minutes
FROM dbo.dialled_numbers AS n
    INNER JOIN dbo.prefixes AS p
        ON n.number LIKE p.prefix + '%'
GROUP BY p.prefix

(これはSQL Server用に作成されていますが、他のDBMS用に変換するのは非常に簡単です。)

于 2009-06-25T22:09:58.063 に答える
0

たぶん、C#の代わりにデータベースでそれを行う方が簡単です(必ずしもより効率的ではありません)。

データベースに行を挿入し、挿入時にキャリアを決定してレコードに含めることができます(おそらく挿入トリガーに)。

その場合、レポートはテーブルに対する合計クエリになります。

于 2009-06-25T21:12:06.480 に答える
0

C#でHashTableを使用することをお勧めします。

このようにして、キーと値のペアがあり、キーは電話番号であり、値は合計分である可能性があります。キーセットに一致するものが見つかった場合は、合計分数を変更します。それ以外の場合は、新しいキーを追加します。

次に、検索アルゴリズムを変更するだけで、キー全体ではなく、キーの最初の7桁だけが表示されます。

于 2009-06-25T21:53:27.210 に答える
0

おそらく、エントリをリストに入れて並べ替えてから、バイナリ検索を使用して一致するものを探します。バイナリ検索の一致基準を調整して、一致する最初のアイテムを返し、一致しないアイテムが見つかるまでリストに沿って繰り返します。バイナリ検索では、30,000アイテムのリストを検索するために、約15回の比較しか必要ありません。

于 2009-06-25T21:41:45.797 に答える