Кто-нибудь создал структуру данных «карта по умолчанию», или у вас есть идеи? - PullRequest
6 голосов
/ 06 января 2009

У меня есть некоторые данные конфигурации, которые я хотел бы смоделировать в коде следующим образом:

Key1,  Key2,  Key3,  Value
null,  null,  null,  1
1,     null,  null,  2
9,     null,  null,  21
1,     null,  3,     3
null,  2,     3,     4
1,     2,     3,     5

При таком наборе конфигурации мне нужно выполнить поиск на кортежах bazillion (Give или Take) {Key1, Key2, Key3}, чтобы получить «эффективное» значение. Эффективное значение, которое следует использовать, основано на ключевой / приоритетной сумме, в которой в этом примере:

Key1 - Priority 10
Key2 - Priority 7
Key3 - Priority 5

Таким образом, конкретный запрос, имеющий запись конфигурации для Key1 = null, Key2 = match и Key3 = match, превосходит тот, который имеет Key1 = match, Key2 = null и Key3 = null, поскольку Key2 + Key3 priority> Key1 priority ... что имеет смысл?!

given a key of {1, 2, 3} the value should be 5.
given a key of {3, 2, 3} the value should be 4.
given a key of {8, 10, 11} the value should be 1.
given a key of {1, 10, 11} the value should be 2.
given a key of {9, 2, 3} the value should be 4.
given a key of {8, 2, 3} the value should be 4.
given a key of {9, 3, 3} the value should be 21.

Существует ли простой способ для моделирования этой структуры данных и алгоритма поиска, который является общим в том смысле, что # и типы ключей являются переменными, а "таблицу истинности" (порядок поиска) можно определять динамически? Типы, являющиеся непатентованными, а не целыми, были бы идеальными (с плавающей точкой, двойными, короткими и т. Д.), И очень важно упростить расширение до n чисел ключей!

Предполагаемый размер таблицы «config»: 1000 строк, Приблизительные данные «ищутся»: 1e14

Это дает представление о типе ожидаемой производительности.

Я ищу идеи в C # или что-то, что можно легко перевести на C #.

Ответы [ 4 ]

3 голосов
/ 06 января 2009

Чтобы ответить на ваш вопрос о чем-то, что является общим по количеству и типу ключей - вы не можете сделать число и тип ключей динамическими и использовать обобщенные значения - универсальные средства - это все о предоставлении время компиляции информация. Конечно, вы можете использовать игнорирование статической типизации и сделать ее динамической - дайте мне знать, если вы хотите, чтобы я реализовал это вместо этого.

Сколько будет записей и как часто вам нужно их искать? Возможно, вам лучше всего хранить все записи в виде списка и перебирать их, давая определенный «балл» для каждого матча (и сохранять лучший матч и его счет на ходу). Вот реализация, включая ваши тестовые данные - но она использует ключи, имеющие приоритеты (и затем суммирующие совпадения), как в предыдущем комментарии ...

using System;
using System.Collections;
using System.Collections.Generic;

public class Test
{
    static void Main()
    {
        Config config = new Config(10, 7, 5)
        {
            { new int?[]{null,  null,  null},  1},
            { new int?[]{1,     null,  null},  2},
            { new int?[]{9,     null,  null},  21},
            { new int?[]{1,     null,  3},     3 },
            { new int?[]{null,  2,     3},     4 },
            { new int?[]{1,     2,     3},     5 }
        };

        Console.WriteLine(config[1, 2, 3]);
        Console.WriteLine(config[3, 2, 3]);
        Console.WriteLine(config[8, 10, 11]);
        Console.WriteLine(config[1, 10, 11]);
        Console.WriteLine(config[9, 2, 3]);
        Console.WriteLine(config[9, 3, 3]);
    }
}

public class Config : IEnumerable
{
    private readonly int[] priorities;
    private readonly List<KeyValuePair<int?[],int>> entries = 
        new List<KeyValuePair<int?[], int>>();

    public Config(params int[] priorities)
    {
        // In production code, copy the array to prevent tampering
        this.priorities = priorities;
    }

    public int this[params int[] keys]
    {
        get
        {
            if (keys.Length != priorities.Length)
            {
                throw new ArgumentException("Invalid entry - wrong number of keys");
            }
            int bestValue = 0;
            int bestScore = -1;
            foreach (KeyValuePair<int?[], int> pair in entries)
            {
                int?[] key = pair.Key;
                int score = 0;
                for (int i=0; i < priorities.Length; i++)
                {
                    if (key[i]==null)
                    {
                        continue;
                    }
                    if (key[i].Value == keys[i])
                    {
                        score += priorities[i];
                    }
                    else
                    {
                        score = -1;
                        break;
                    }
                }
                if (score > bestScore)
                {
                    bestScore = score;
                    bestValue = pair.Value;
                }
            }
            return bestValue;
        }
    }

    public void Add(int?[] keys, int value)
    {
        if (keys.Length != priorities.Length)
        {
            throw new ArgumentException("Invalid entry - wrong number of keys");
        }
        // Again, copy the array in production code
        entries.Add(new KeyValuePair<int?[],int>(keys, value));
    }

    public IEnumerator GetEnumerator()
    {
        throw new NotSupportedException();
    }
}

Выше допускается переменное количество ключей, но только целые числа (или ноль). Если честно, API проще в использовании, если вы исправите количество ключей ...

3 голосов
/ 06 января 2009

РЕДАКТИРОВАТЬ: Этот код, видимо, не то, что требуется, но я оставляю его, так как это все равно интересно. Он в основном рассматривает Key1 как имеющий приоритет, затем Key2, затем Key3 и т. Д. Я действительно не понимаю предполагаемую систему приоритетов да, но когда я это сделаю, я добавлю ответ для этого.

Я бы предложил тройной слой словарей - каждый слой имеет:

Dictionary<int, NextLevel> matches;
NextLevel nonMatch;

Таким образом, на первом уровне вы будете искать Key1 - если это соответствует, это дает вам следующий уровень поиска. В противном случае используйте следующий уровень, который соответствует «non-match».

Это имеет какой-то смысл? Вот пример кода (включая пример, который вы дали). Я не совсем доволен фактической реализацией, но идея структуры данных является здравой, я думаю:

using System;
using System.Collections;
using System.Collections.Generic;

public class Test
{
    static void Main()
    {
        Config config = new Config
        {
            { null,  null,  null,  1 },
            { 1,     null,  null,  2 },
            { 1,     null,  3,     3 },
            { null,  2,     3,     4 },
            { 1,     2,     3,     5 }
        };

        Console.WriteLine(config[1, 2, 3]);
        Console.WriteLine(config[3, 2, 3]);
        Console.WriteLine(config[9, 10, 11]);
        Console.WriteLine(config[1, 10, 11]);
    }
}

// Only implement IEnumerable to allow the collection initializer
// Not really implemented yet - consider how you might want to implement :)
public class Config : IEnumerable
{
    // Aargh - death by generics :)
    private readonly DefaultingMap<int, 
                         DefaultingMap<int, DefaultingMap<int, int>>> map
        = new DefaultingMap<int, DefaultingMap<int, DefaultingMap<int, int>>>();

    public int this[int key1, int key2, int key3]
    {
        get
        {
            return map[key1][key2][key3];
        }
    }

    public void Add(int? key1, int? key2, int? key3, int value)
    {
        map.GetOrAddNew(key1).GetOrAddNew(key2)[key3] = value;
    }

    public IEnumerator GetEnumerator()
    {
        throw new NotSupportedException();
    }
}

internal class DefaultingMap<TKey, TValue>
    where TKey : struct 
    where TValue : new()
{
    private readonly Dictionary<TKey, TValue> mapped = new Dictionary<TKey, TValue>();
    private TValue unmapped = new TValue();

    public TValue GetOrAddNew(TKey? key)
    {
        if (key == null)
        {
            return unmapped;
        }
        TValue ret;
        if (mapped.TryGetValue(key.Value, out ret))
        {
            return ret;
        }
        ret = new TValue();
        mapped[key.Value] = ret;
        return ret;
    }

    public TValue this[TKey key]
    {
        get
        {
            TValue ret;
            if (mapped.TryGetValue(key, out ret))
            {
                return ret;
            }
            return unmapped;
        }
    }

    public TValue this[TKey? key]
    {
        set
        {
            if (key != null)
            {
                mapped[key.Value] = value;
            }
            else
            {
                unmapped = value;
            }
        }
    }
}
1 голос
/ 06 января 2009

Я предполагаю, что есть несколько правил и большое количество элементов, которые вы собираетесь проверить на соответствие правилам. В этом случае, возможно, стоит потратить память и время на предварительное вычисление структуры, которая поможет вам быстрее найти объект.

Основной идеей для этой структуры было бы дерево, такое, что на глубине i вы будете следовать i-му элементу правила или нулевой ветви, если она не найдена в словаре.

Чтобы построить дерево, я бы построил его рекурсивно. Начните с корневого узла, содержащего все возможные правила в его пуле. Процесс:

  • Определяет текущее значение каждого правила в пуле как оценку текущего правила с учетом пути, выбранного для доступа к узлу, или -infinity, если путь невозможно выбрать. Например, если текущий узел находится в ветви «1» корня, тогда правило {null, null, null, 1} будет иметь оценку 0, а правило {1, null, null, 2} будет иметь счет 10
  • Определяет максимальное значение каждого правила в пуле как его текущий счет плюс счет оставшихся ключей. Например, если текущий узел находится в ветви «1» корня, тогда правило {null, 1, 2, 1} будет иметь оценку 12 (0 + 7 + 5), а правило {1, null, null, 2} будет иметь оценку 10 (10 + 0 + 0).
  • Удалить элементы из пула, максимальное значение которых меньше, чем максимальное текущее значение в пуле
  • Если есть только одно правило, составьте лист с правилом.
  • Если в пуле осталось несколько правил, а ключей больше нет ??? (это не ясно из описания проблемы. Я предполагаю, что берут самую высокую)
  • Для каждого уникального значения (i + 1) -ого ключа в текущем пуле и значения null создайте новое дерево из текущего узла, используя текущий пул.

В качестве окончательной проверки оптимизации я бы проверил, все ли потомки узла являются листьями, и если все они содержат одно и то же правило, то сделаем узел листом с этим значением.

с учетом следующих правил:

null,  null,  null = 1
1,     null,  null = 2
9,     null,  null = 21
1,     null,  3    = 3
null,  2,     3    = 4
1,     2,     3    = 5

дерево примеров:

       key1   key2   key3
root:
 |----- 1
 |      |----- 2          = 5
 |      |-----null
 |             |----- 3   = 3
 |             |-----null = 2
 |----- 9
 |      |----- 2
 |      |      |----- 3   = 4
 |      |      |-----null = 21
 |      |-----null        = 21
 |-----null
        |----- 2          = 4
        |-----null        = 1

Если вы построите дерево таким образом, начиная с ключа с наибольшим значением, то вы, возможно, сможете отменить множество проверок для более поздних ключей.

Изменить, чтобы добавить код:

class Program
{
    static void Main(string[] args)
    {
        Config config = new Config(10, 7, 5)
        {
            { new int?[]{null,  null,  null},  1},
            { new int?[]{1,     null,  null},  2},
            { new int?[]{9,     null,  null},  21},
            { new int?[]{1,     null,  3},     3 },
            { new int?[]{null,  2,     3},     4 },
            { new int?[]{1,     2,     3},     5 }
        };

        Console.WriteLine("5 == {0}", config[1, 2, 3]);
        Console.WriteLine("4 == {0}", config[3, 2, 3]);
        Console.WriteLine("1 == {0}", config[8, 10, 11]);
        Console.WriteLine("2 == {0}", config[1, 10, 11]);
        Console.WriteLine("4 == {0}", config[9, 2, 3]);
        Console.WriteLine("21 == {0}", config[9, 3, 3]);            
        Console.ReadKey();
    }
}


public class Config : IEnumerable
{
    private readonly int[] priorities;
    private readonly List<KeyValuePair<int?[], int>> rules =
        new List<KeyValuePair<int?[], int>>();
    private DefaultMapNode rootNode = null;

    public Config(params int[] priorities)
    {
        // In production code, copy the array to prevent tampering
        this.priorities = priorities;
    }

    public int this[params int[] keys]
    {
        get
        {
            if (keys.Length != priorities.Length)
            {
                throw new ArgumentException("Invalid entry - wrong number of keys");
            }

            if (rootNode == null)
            {
                rootNode = BuildTree();
                //rootNode.PrintTree(0);
            }

            DefaultMapNode curNode = rootNode;
            for (int i = 0; i < keys.Length; i++)
            {
                // if we're at a leaf, then we're done
                if (curNode.value != null)
                    return (int)curNode.value;

                if (curNode.children.ContainsKey(keys[i]))
                    curNode = curNode.children[keys[i]];
                else
                    curNode = curNode.defaultChild;
            }

            return (int)curNode.value;
        }
    }

    private DefaultMapNode BuildTree()
    {
        return new DefaultMapNode(new int?[]{}, rules, priorities);
    }

    public void Add(int?[] keys, int value)
    {
        if (keys.Length != priorities.Length)
        {
            throw new ArgumentException("Invalid entry - wrong number of keys");
        }
        // Again, copy the array in production code
        rules.Add(new KeyValuePair<int?[], int>(keys, value));

        // reset the tree to know to regenerate it.
        rootNode = null;
    }

    public IEnumerator GetEnumerator()
    {
        throw new NotSupportedException();
    }

}


public class DefaultMapNode
{
    public Dictionary<int, DefaultMapNode> children = new Dictionary<int,DefaultMapNode>();
    public DefaultMapNode defaultChild = null; // done this way to workaround dict not handling null
    public int? value = null;

    public DefaultMapNode(IList<int?> usedValues, IEnumerable<KeyValuePair<int?[], int>> pool, int[] priorities)
    {
        int bestScore = Int32.MinValue;

        // get best current score
        foreach (KeyValuePair<int?[], int> rule in pool)
        {
            int currentScore = GetCurrentScore(usedValues, priorities, rule);
            bestScore = Math.Max(bestScore, currentScore);
        }

        // get pruned pool
        List<KeyValuePair<int?[], int>> prunedPool = new List<KeyValuePair<int?[], int>>();
        foreach (KeyValuePair<int?[], int> rule in pool)
        {
            int maxScore = GetCurrentScore(usedValues, priorities, rule);
            if (maxScore == Int32.MinValue)
                continue;

            for (int i = usedValues.Count; i < rule.Key.Length; i++)
                if (rule.Key[i] != null)
                    maxScore += priorities[i];

            if (maxScore >= bestScore)
                prunedPool.Add(rule);
        }

        // base optimization case, return leaf node
        // base case, always return same answer
        if ((prunedPool.Count == 1) || (usedValues.Count == prunedPool[0].Key.Length))
        {
            value = prunedPool[0].Value;
            return;
        }

        // add null base case
        AddChild(usedValues, priorities, prunedPool, null);
        foreach (KeyValuePair<int?[], int> rule in pool)
        {
            int? branch = rule.Key[usedValues.Count];
            if (branch != null && !children.ContainsKey((int)branch))
            {
                AddChild(usedValues, priorities, prunedPool, branch);
            }
        }


        // if all children are the same, then make a leaf
        int? maybeOnlyValue = null;
        foreach (int v in GetAllValues())
        {
            if (maybeOnlyValue != null && v != maybeOnlyValue)
                return;
            maybeOnlyValue = v;
        }
        if (maybeOnlyValue != null)
            value = maybeOnlyValue;

    }

    private static int GetCurrentScore(IList<int?> usedValues, int[] priorities, KeyValuePair<int?[], int> rule)
    {
        int currentScore = 0;
        for (int i = 0; i < usedValues.Count; i++)
        {
            if (rule.Key[i] != null)
            {
                if (rule.Key[i] == usedValues[i])
                    currentScore += priorities[i];
                else
                    return Int32.MinValue;
            }
        }
        return currentScore;
    }

    private void AddChild(IList<int?> usedValues, int[] priorities, List<KeyValuePair<int?[], int>> prunedPool, Nullable<int> nextValue)
    {
        List<int?> chainedValues = new List<int?>();
        chainedValues.AddRange(usedValues);
        chainedValues.Add(nextValue);            
        DefaultMapNode node = new DefaultMapNode(chainedValues, prunedPool, priorities);
        if (nextValue == null)
            defaultChild = node;
        else
            children[(int)nextValue] = node;
    }

    public IEnumerable<int> GetAllValues()
    {
        foreach (DefaultMapNode child in children.Values)
            foreach (int v in child.GetAllValues())
                yield return v;
        if (defaultChild != null)
            foreach (int v in defaultChild.GetAllValues())
                yield return v;
        if (value != null)
            yield return (int)value;
    }

    public void PrintTree(int depth)
    {
        if (value == null)
            Console.WriteLine();
        else
        {
            Console.WriteLine(" = {0}", (int)value);
            return;
        }

        foreach (KeyValuePair<int, DefaultMapNode> child in children)
        {
            for (int i=0; i<depth; i++)
                Console.Write("    ");
            Console.Write(" {0}  ", child.Key);                
            child.Value.PrintTree(depth + 1);
        }
        for (int i = 0; i < depth; i++)
            Console.Write("    ");
        Console.Write("null");
        defaultChild.PrintTree(depth + 1);
    }
}
1 голос
/ 06 января 2009

Еще одно решение - представьте, что записи представляют собой битовый паттерн нуль / не ноль. У вас есть один словарь на битовую комбинацию (т. Е. {1, null, null} и {9, null, null} входят в один и тот же словарь, но {1, 2, 3} идет в другой. а также - сумма приоритетов для ненулевых частей ключа. В итоге вы получите 2 ^ n словарей, где n - количество элементов в ключе.

Вы упорядочиваете словари в обратном порядке, а затем просто ищите данный ключ в каждом из них. Каждый словарь должен игнорировать значения в ключе, которые не находятся в его битовой структуре, что достаточно легко сделать с помощью пользовательского IComparer<int[]>.

Хорошо, вот реализация:

------------ Test.cs -----------------
using System;

sealed class Test
{
    static void Main()
    {
        Config config = new Config(10, 7, 5)
        {
            { null, null, null, 1 },
            {null,  null,  null,  1},
            {1,     null,  null,  2},
            {9,     null,  null,  21},
            {1,     null,  3,     3 },
            {null,  2,     3,     4 },
            {1,     2,     3,     5 }
        };

        Console.WriteLine(config[1, 2, 3]);
        Console.WriteLine(config[3, 2, 3]);
        Console.WriteLine(config[8, 10, 11]);
        Console.WriteLine(config[1, 10, 11]);
        Console.WriteLine(config[9, 2, 3]);
        Console.WriteLine(config[9, 3, 3]);
    }
}

--------------- Config.cs -------------------
using System;
using System.Collections;

sealed class Config : IEnumerable
{
    private readonly PartialMatchDictionary<int, int> dictionary;

    public Config(int priority1, int priority2, int priority3)
    {
        dictionary = new PartialMatchDictionary<int, int>(priority1, priority2, priority3);
    }

    public void Add(int? key1, int? key2, int? key3, int value)
    {
        dictionary[new[] { key1, key2, key3 }] = value;
    }

    public int this[int key1, int key2, int key3]
    {
        get
        {
            return dictionary[new[] { key1, key2, key3 }];
        }
    }

    // Just a fake implementation to allow the collection initializer
    public IEnumerator GetEnumerator()
    {
        throw new NotSupportedException();
    }
}

-------------- PartialMatchDictionary.cs -------------------
using System;
using System.Collections.Generic;
using System.Linq;

public sealed class PartialMatchDictionary<TKey, TValue> where TKey : struct
{
    private readonly List<Dictionary<TKey[], TValue>> dictionaries;
    private readonly int keyComponentCount;

    public PartialMatchDictionary(params int[] priorities)
    {
        keyComponentCount = priorities.Length;
        dictionaries = new List<Dictionary<TKey[], TValue>>(1 << keyComponentCount);
        for (int i = 0; i < 1 << keyComponentCount; i++)
        {
            PartialComparer comparer = new PartialComparer(keyComponentCount, i);
            dictionaries.Add(new Dictionary<TKey[], TValue>(comparer));
        }
        dictionaries = dictionaries.OrderByDescending(dict => ((PartialComparer)dict.Comparer).Score(priorities))
                                   .ToList();
    }

    public TValue this[TKey[] key]
    {
        get
        {
            if (key.Length != keyComponentCount)
            {
                throw new ArgumentException("Invalid key component count");
            }
            foreach (Dictionary<TKey[], TValue> dictionary in dictionaries)
            {
                TValue value;
                if (dictionary.TryGetValue(key, out value))
                {
                    return value;
                }
            }
            throw new KeyNotFoundException("No match for this key");
        }
    }

    public TValue this[TKey?[] key]
    {
        set
        {
            if (key.Length != keyComponentCount)
            {
                throw new ArgumentException("Invalid key component count");
            }
            // This could be optimised (a dictionary of dictionaries), but there
            // won't be many additions to the dictionary compared with accesses
            foreach (Dictionary<TKey[], TValue> dictionary in dictionaries)
            {
                PartialComparer comparer = (PartialComparer)dictionary.Comparer;
                if (comparer.IsValidForPartialKey(key))
                {
                    TKey[] maskedKey = key.Select(x => x ?? default(TKey)).ToArray();
                    dictionary[maskedKey] = value;
                    return;
                }
            }
            throw new InvalidOperationException("We should never get here");
        }
    }

    private sealed class PartialComparer : IEqualityComparer<TKey[]>
    {
        private readonly int keyComponentCount;
        private readonly bool[] usedKeyComponents;
        private static readonly EqualityComparer<TKey> Comparer = EqualityComparer<TKey>.Default;

        internal PartialComparer(int keyComponentCount, int usedComponentBits)
        {
            this.keyComponentCount = keyComponentCount;
            usedKeyComponents = new bool[keyComponentCount];
            for (int i = 0; i < keyComponentCount; i++)
            {
                usedKeyComponents[i] = ((usedComponentBits & (1 << i)) != 0);
            }
        }

        internal int Score(int[] priorities)
        {
            return priorities.Where((value, index) => usedKeyComponents[index]).Sum();
        }

        internal bool IsValidForPartialKey(TKey?[] key)
        {
            for (int i = 0; i < keyComponentCount; i++)
            {
                if ((key[i] != null) != usedKeyComponents[i])
                {
                    return false;
                }
            }
            return true;
        }

        public bool Equals(TKey[] x, TKey[] y)
        {
            for (int i = 0; i < keyComponentCount; i++)
            {
                if (!usedKeyComponents[i])
                {
                    continue;
                }
                if (!Comparer.Equals(x[i], y[i]))
                {
                    return false;
                }
            }
            return true;
        }

        public int GetHashCode(TKey[] obj)
        {
            int hash = 23;
            for (int i = 0; i < keyComponentCount; i++)
            {
                if (!usedKeyComponents[i])
                {
                    continue;
                }
                hash = hash * 37 + Comparer.GetHashCode(obj[i]);
            }
            return hash;
        }
    }
}

Это дает правильные результаты для образцов, которые вы дали. Я не знаю, на что похожа производительность - это должно быть O (1), но, вероятно, его можно оптимизировать немного дальше.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...