将字符串数组(#3,#1,#2,#4)中的数字压缩到范围(#1:#4)

时间:2015-11-07 16:08:57

标签: c# .net algorithm performance time-complexity

1。 我做了一个带字符串数组的函数,

{"foo", "#123", "#124", "bar", "#125", "#126"}

创建一个新数组,其数字转换为范围:

{"foo", "#123:126", "bar"}

并返回:

"foo,#123:126,bar"
  1. 请注意,它不会更改两个数字的范围(不应将{"#1", "#2"}更改为{"#1:#2"})。这是因为#1:#2#1,#2占用相同的尺寸。

  2. 顺序对于所有值都很重要,不包括在范围内压缩的值。例如,在{#6, #5, #1, foo, #2, #3}中,#2#3会被#1压扁,这没关系,但其余的应该有相同的顺序。

  3. 以下是我的实现,由于多次.Contains调用,因此效率非常低。

    using System;
    using System.Linq;
    using System.Collections.Generic;
    class Program
    {
        static void Main(string[] args)
        {
            var ids = new string[] {
                "foo", //output: unmodified
                "#60", "#59", "#61", "#62", //from integer is in between, output: #59:#62
                "#12", "#14", "#17", "#13", "#18", "#bar", "#19", "#20", //two ranges and string intertwined, output: #12:#14,#17:#20,#bar
                "#25", "#26", //output: unmodified
                "#39", "#38", //output: unmodified
                "baz", //output: unmodified
                "#12", "#13", "#14" //duplicate sequences, output: #12:#14
            };
            //this is what the function should output when fed `ids`:
            Console.WriteLine("foo,#59:#62,#12:#14,#17:#20,#bar,#25,#26,#38,#39,baz,#12:#14");
            Console.WriteLine(Compress(ids));
            Console.Read();
        }
        static string Compress(IEnumerable<string> IDs)
        {
            var result = new List<string>();
            var ignore = new HashSet<string>();
            foreach (var item in IDs)
            {
                if (ignore.Contains(item)) continue;
                var id = item;
                if (id.StartsWith("#"))
                {
                    int fromInt;
                    if (int.TryParse(id.Substring(1), out fromInt))
                    {
                        var less1 = $"#{fromInt - 1}";
                        var plus1 = $"#{fromInt + 1}";
                        var hasPlus1 = IDs.Contains(plus1);
                        if (IDs.Contains(less1) && hasPlus1) continue;
                        var plus2 = $"#{fromInt + 2}";
                        if (hasPlus1 && IDs.Contains(plus2))
                        {
                            ignore.Add(plus1);
                            ignore.Add(plus2);
                            var toInt = fromInt + 2;
                            while (IDs.Contains($"#{toInt + 1}"))
                            {
                                toInt += 1;
                                ignore.Add($"#{toInt}");
                            }
                            id = $"#{fromInt}:#{toInt}";
                        }
                    }
                }
                result.Add(id);
            }
            return string.Join(",", result);
        }
    }
    

    有人能告诉我如何才能提高效率?

2 个答案:

答案 0 :(得分:0)

以下是执行此操作的一种方法(以下代码中的注释):

$result = $mj->campaign([
    'FromEmail' => 'sender email'
]);

var_dump($result);

答案 1 :(得分:0)

我最终得到了两遍算法。主要挑战是选择支持快速键查找和重复键的正确数据结构。 Dictionary<int, List<int>看起来太占用空间,所以我决定使用链接列表,例如由Dictionary<int, int>保存第一个键位置的结构,以及一个带链接和处理信息的单独数组。

第一遍准备处理结构,第二遍发出结果。由于每个元素仅被解析一次(包括使用具有O(1)时间复杂度的字典查找的范围检查),因此算法IMO的时间复杂度应为O(N)。

除此之外,它与您的非常相似,因此可能被视为优化实施。

enum EntryType { Single, Range, Unknown }

struct Entry
{
    public string Value;
    public int Number;
    public int Next; // position of the next value with the same number
    public EntryType Type;
}

static string Compress(IEnumerable<string> input)
{
    var entryList = input.Select(value => new Entry { Value = value }).ToArray();
    var numberQueue = new Dictionary<int, int>(entryList.Length); // Key=number, Value=position
    for (int pos = entryList.Length - 1; pos >= 0; pos--)
    {
        var value = entryList[pos].Value;
        int number;
        if (value.Length > 1 && value[0] == '#' && int.TryParse(value.Substring(1), out number))
        {
            int nextPos;
            if (!numberQueue.TryGetValue(number, out nextPos)) nextPos = -1;
            entryList[pos].Number = number;
            entryList[pos].Type = EntryType.Unknown;
            entryList[pos].Next = nextPos;
            numberQueue[number] = pos;
        }
    }
    var output = new StringBuilder();
    for (int pos = 0; pos < entryList.Length; pos++)
    {
        var entryType = entryList[pos].Type;
        if (entryType == EntryType.Range) continue; // already processed
        var number = entryList[pos].Number;
        int startPos = pos, endPos = pos, prevCount = 0, nextCount = 0;
        if (entryType == EntryType.Unknown)
        {
            for (int prevPos; numberQueue.TryGetValue(number - prevCount - 1, out prevPos) && prevPos >= 0; startPos = prevPos, prevCount++) { }
            for (int nextPos; numberQueue.TryGetValue(number + nextCount + 1, out nextPos) && nextPos >= 0; endPos = nextPos, nextCount++) { }
            entryType = prevCount + nextCount >= 2 ? EntryType.Range : EntryType.Single;
            for (int offset = -prevCount; offset <= nextCount; offset++)
            {
                var nextNumber = number + offset;
                int nextPos = numberQueue[nextNumber];
                entryList[nextPos].Type = entryType;
                numberQueue[nextNumber] = entryList[nextPos].Next;
            }
        }
        if (output.Length > 0) output.Append(',');
        if (entryType == EntryType.Single)
            output.Append(entryList[pos].Value);
        else
            output.Append(entryList[startPos].Value).Append(':').Append(entryList[endPos].Value);
    }
    return output.ToString();
}