Удаление нескольких элементов из ConcurrentDictionary на основе условного ключа

Допустим, у меня есть ConcurrentDictionary:

var dict = new ConcurrentDictionary<string, someObject>();
dict.TryAdd("0_someA_someB_someC", obj0);
dict.TryAdd("1_someA_someB_someC", obj1);
dict.TryAdd("2_someA_someB_someC", obj2);
dict.TryAdd("3_someA_someB_someC", obj3);

<number>_ в ключах увеличивается, и, поскольку это словарь, нет гарантии, что элементы расположены по порядку.

Теперь представьте, что я хочу удалить из словаря все элементы, у которых number меньше 2. Я понятия не имею, как будут выглядеть ключи, только то, что они будут иметь префикс с номером, как указано выше.

Как удалить из словаря все элементы, ключ которых начинается со значения меньше 2?

Например, результирующий dict после этого процесса будет выглядеть так:

dict.TryAdd("2_someA_someB_someC", obj2);
dict.TryAdd("3_someA_someB_someC", obj3);

person pookie    schedule 17.09.2018    source источник
comment
Вы не можете сделать это атомарно без блокировки. Это может быть проблемой, если вы используете ConcurrentDictionary (но не обязательно, в зависимости от того, почему вы используете ConcurrentDictionary и почему вы удаляете ключи). ImmutableSortedDictionary — вещь, но она может подходить или не подходить для вашего сценария — опять же, в зависимости от использования.   -  person Jeroen Mostert    schedule 17.09.2018
comment
Ради эффективности вместо индексации строки рассмотрите пользовательский тип ключа (или простой кортеж), где эти поля разделены на фактические поля, или несколько словарей (в свою очередь, вы можете сгруппировать их в свою собственную пользовательскую коллекцию) . Объединенная строка — это быстрый и простой способ создания уникального ключа, но это не очень хорошая идея, если вам часто приходится анализировать ключи. Даже если вы не можете изменять вызывающие объекты, хранение избыточной дополнительной коллекции, в которой вы сопоставляете эти ключи для быстрого поиска, может быть оправдано.   -  person Jeroen Mostert    schedule 17.09.2018
comment
@JeroenMostert, это очень интересная идея. Я никогда не рассматривал такую ​​возможность. Если есть возможность, можете дать ссылку на пример.   -  person pookie    schedule 17.09.2018
comment
ValueTuple достаточно просто в последних версиях C#: var d = new ConcurrentDictionary<(int theNumber, string compoundKey), someObject>(); d.TryAdd((2, "2_someA_someB_someC"), obj0); d.Keys.Where(k => k.theNumber < 2). ValueTuple имеет подходящие реализации GetHashCode и Equals для работы в качестве ключа словаря. Это только оптимизирует парсинг строки и не факт, что нам еще придется пройтись по всем ключам, но этого может быть достаточно. Пользовательский адаптер, который разделяет словари, — это еще куча работы, над которой мне не хочется работать. :-П   -  person Jeroen Mostert    schedule 17.09.2018
comment
@JeroenMostert Отлично, спасибо. Я поиграю с этим.   -  person pookie    schedule 17.09.2018


Ответы (4)


Предполагая, что он всегда имеет этот формат, вы можете использовать LINQ:

var keysToRemove = dict.Keys.Where(key => int.Parse(key.Remove(key.IndexOf('_'))) < 2).ToList();
keysToRemove.ForEach(key => dict.TryRemove(key, out someObject obj));

String.Remove удаляет часть, начинающуюся с _, а затем анализирует оставшуюся первую часть, номер. Будут выбраны только те ключи, число которых меньше 2. Этот список будет использоваться для удаления элементов из словаря. Конечно, вам нужна блокировка, чтобы сделать это потокобезопасным.

person Tim Schmelter    schedule 17.09.2018
comment
Я не вижу ничего плохого в вашем ответе. Кажется, он отлично отвечает на мой вопрос. Я заметил, что как только вы опубликовали это (сразу), кто-то проголосовал против вас и проголосовал за ответ Сельмана Генча. Не говорю, что эти два события связаны, просто указываю, что я это заметил. Что касается безопасности потоков, у меня сложилось впечатление, что целью коллекций Concurrent было позволить разработчикам сосредоточиться на программировании, а не на проблемах параллелизма. Вы говорите, что даже с Concurrent коллекциями нам нужно использовать блокировки и семафоры? - person pookie; 17.09.2018
comment
@pookie: параллельные коллекции предлагают отдельные операции, которые эффективно выполняются параллельно без дополнительной блокировки. Как только вы захотите сделать больше чем одну вещь по принципу «все или ничего», такой вещи, как бесплатный обед, все равно не будет. Рассмотрим случай, когда этот код извлекает все ключи, а затем другой поток вставляет дополнительные ключи, которые также соответствуют проверке. Эти ключи не исчезнут к тому времени, когда этот цикл завершится. Точно так же, если другой поток удалит, а затем вставит соответствующий ключ с новым значением, этот код может удалить это значение, которое может быть или не быть тем, что вам нужно. - person Jeroen Mostert; 17.09.2018
comment
@pookie: в этих двух строках много чего происходит. Коллекция Keys, которую я использую, в основном представляет собой снимки ConcurrentDictionary в то время, когда я ее просил. Затем я перечисляю все ключи, а затем удаляю соответствующие элементы, что может занять некоторое время. В это время возможно, что ConcurrentDictionary получит больше элементов из другого потока, которые соответствуют критериям. Так что пока я их удаляю, состояние словаря уже изменилось. Если вы хотите предотвратить это, вам нужен файл lock. - person Tim Schmelter; 17.09.2018
comment
@JeroenMostert и TimSchmelter Спасибо, это имеет смысл. Я думал, что это слишком хорошо, чтобы быть правдой... - person pookie; 17.09.2018
comment
Соблазн понизить голосование из-за предложения использовать блокировки для потокобезопасности. Комбинация ConcurrentDictionary с замками не имеет смысла. Если вам нужно заблокировать, используйте вместо этого Dictionary. Потому что при блокировке нельзя быть избирательным. Вы должны блокировать каждый доступ к коллекции (как запись, и чтение). Частичная блокировка так же хороша, как и полное отсутствие блокировки. А полная блокировка ConcurrentDictionary означает использование двух механизмов синхронизации, один поверх другого, причем один является избыточным и добавляет только накладные расходы. - person Theodor Zoulias; 14.04.2020

  1. Проанализируйте число, которое стоит перед первым символом подчеркивания (Совет: IndexOf и Substring)
  2. Преобразуйте его в целое число (Совет: int.TryParse)
  3. Сравните число со значением (в данном случае 2)
  4. Отфильтруйте ключи, применяя этот метод, сохраните их в коллекции. Переберите коллекцию и вызовите TryRemove для удаления записей, связанных с ключом.
person Selman Genç    schedule 17.09.2018

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

person open-collar    schedule 17.09.2018

Вы можете использовать Split, чтобы разделить ключ на массив по символу _, преобразовать первый элемент в результирующем массиве в int (обратите внимание, что это вызовет исключение, если ключ не начинается с int), и если он меньше 2 , удалите его из словаря:

foreach (var item in dict.Where(kvp => int.Parse(kvp.Key.Split('_')[0]) < 2))
{
    SomeObject tempObject;
    dict.TryRemove(item.Key, out tempObject);
}
person Rufus L    schedule 17.09.2018