<commit_before>using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using WalletWasabi.Bases;
using WalletWasabi.Blockchain.TransactionOutputs;

namespace WalletWasabi.Blockchain.Analysis.Clustering
{
	public class Cluster : NotifyPropertyChangedBase, IEquatable<Cluster>
	{
		private List<SmartCoin> Coins { get; set; }

		private SmartLabel _labels;

		public SmartLabel Labels
		{
			get => _labels;
			private set => RaiseAndSetIfChanged(ref _labels, value);
		}

		public int Size => Coins.Count;

		public Cluster(params SmartCoin[] coins)
			: this(coins as IEnumerable<SmartCoin>)
		{
		}

		public Cluster(IEnumerable<SmartCoin> coins)
		{
			Coins = coins.ToList();
			Labels = SmartLabel.Merge(Coins.Select(x => x.Label));
		}

		public void Merge(Cluster clusters) => Merge(clusters.Coins);

		public void Merge(IEnumerable<SmartCoin> coins)
		{
			var insertPosition = 0;
			foreach (var coin in coins.ToList())
			{
				if (!Coins.Contains(coin))
				{
					Coins.Insert(insertPosition++, coin);
				}
				coin.Clusters = this;
			}
			if (insertPosition > 0) // at least one element was inserted
			{
				Labels = SmartLabel.Merge(Coins.Select(x => x.Label));
			}
		}

		#region EqualityAndComparison

		public override bool Equals(object obj) => Equals(obj as Cluster);

		public bool Equals(Cluster other) => this == other;

		public override int GetHashCode()
		{
			int hash = 0;
			if (Coins != null)
			{
				foreach (var coin in Coins)
				{
					hash ^= coin.GetHashCode();
				}
			}
			return hash;
		}

		public static bool operator ==(Cluster x, Cluster y)
		{
			if (x is null)
			{
				if (y is null)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			else
			{
				if (y is null)
				{
					return false;
				}
				else
				{
					return x.Coins.SequenceEqual(y.Coins);
				}
			}
		}

		public static bool operator !=(Cluster x, Cluster y) => !(x == y);

		#endregion EqualityAndComparison
	}
}<commit_msg>Lock cluster and optimize coins contains.<commit_after>using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using WalletWasabi.Bases;
using WalletWasabi.Blockchain.TransactionOutputs;

namespace WalletWasabi.Blockchain.Analysis.Clustering
{
	public class Cluster : NotifyPropertyChangedBase, IEquatable<Cluster>
	{
		private object Lock { get; }
		private List<SmartCoin> Coins { get; set; }
		private HashSet<SmartCoin> CoinsSet { get; set; }

		private SmartLabel _labels;

		public SmartLabel Labels
		{
			get => _labels;
			private set => RaiseAndSetIfChanged(ref _labels, value);
		}

		public int Size => Coins.Count;

		public Cluster(params SmartCoin[] coins)
			: this(coins as IEnumerable<SmartCoin>)
		{
		}

		public Cluster(IEnumerable<SmartCoin> coins)
		{
			Lock = new object();
			Coins = coins.ToList();
			CoinsSet = Coins.ToHashSet();
			Labels = SmartLabel.Merge(Coins.Select(x => x.Label));
		}

		public void Merge(Cluster clusters) => Merge(clusters.Coins);

		public void Merge(IEnumerable<SmartCoin> coins)
		{
			lock (Lock)
			{
				var insertPosition = 0;
				foreach (var coin in coins.ToList())
				{
					if (CoinsSet.Add(coin))
					{
						Coins.Insert(insertPosition++, coin);
					}
					coin.Clusters = this;
				}
				if (insertPosition > 0) // at least one element was inserted
				{
					Labels = SmartLabel.Merge(Coins.Select(x => x.Label));
				}
			}
		}

		public IEnumerable<SmartCoin> GetCoins()
		{
			lock (Lock)
			{
				return Coins.ToList();
			}
		}

		#region EqualityAndComparison

		public override bool Equals(object obj) => Equals(obj as Cluster);

		public bool Equals(Cluster other) => this == other;

		public override int GetHashCode()
		{
			lock (Lock)
			{
				int hash = 0;
				if (Coins is { })
				{
					foreach (var coin in Coins)
					{
						hash ^= coin.GetHashCode();
					}
				}
				return hash;
			}
		}

		public static bool operator ==(Cluster x, Cluster y)
		{
			if (x is null)
			{
				if (y is null)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			else
			{
				if (y is null)
				{
					return false;
				}
				else
				{
					lock (x.Lock)
					{
						return x.Coins.SequenceEqual(y.Coins);
					}
				}
			}
		}

		public static bool operator !=(Cluster x, Cluster y) => !(x == y);

		#endregion EqualityAndComparison
	}
}
{'fraction_non_alphanumeric': 0.11382299270072993, 'fraction_numerical': 0.0013686131386861315, 'mean_word_length': 2.7736660929432015, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4436946', 'n_tokens_mistral': 1899, 'n_tokens_neox': 1460, 'n_words': 370}