Browse Source

权重筛选器

懒得勤快 6 năm trước cách đây
mục cha
commit
7ec2e05395

+ 9 - 18
Masuit.Tools.Core/Extensions.cs

@@ -505,7 +505,7 @@ namespace Masuit.Tools
         /// <returns>int类型的数字</returns>
         public static int ToInt32(this string s)
         {
-            bool b = int.TryParse(s, out int result);
+            int.TryParse(s, out int result);
             return result;
         }
 
@@ -516,7 +516,7 @@ namespace Masuit.Tools
         /// <returns>int类型的数字</returns>
         public static long ToInt64(this string s)
         {
-            bool b = long.TryParse(s, out var result);
+            long.TryParse(s, out var result);
             return result;
         }
 
@@ -527,7 +527,7 @@ namespace Masuit.Tools
         /// <returns>double类型的数据</returns>
         public static double ToDouble(this string s)
         {
-            bool b = double.TryParse(s, out var result);
+            double.TryParse(s, out var result);
             return result;
         }
 
@@ -538,7 +538,7 @@ namespace Masuit.Tools
         /// <returns>int类型的数字</returns>
         public static decimal ToDecimal(this string s)
         {
-            var b = decimal.TryParse(s, out var result);
+            decimal.TryParse(s, out var result);
             return result;
         }
 
@@ -711,8 +711,7 @@ namespace Masuit.Tools
                 }
 
                 string birth = s.Substring(6, 8).Insert(6, "-").Insert(4, "-");
-                DateTime time;
-                if (!DateTime.TryParse(birth, out time))
+                if (!DateTime.TryParse(birth, out _))
                 {
                     return false; //生日验证  
                 }
@@ -842,10 +841,10 @@ namespace Masuit.Tools
         /// <summary>
         /// 严格比较两个对象是否是同一对象
         /// </summary>
-        /// <param name="_this">自己</param>
+        /// <param name="this">自己</param>
         /// <param name="o">需要比较的对象</param>
         /// <returns>是否同一对象</returns>
-        public new static bool ReferenceEquals(this object _this, object o) => object.ReferenceEquals(_this, o);
+        public new static bool ReferenceEquals(this object @this, object o) => object.ReferenceEquals(@this, o);
 
         /// <summary>
         /// 判断字符串是否为空
@@ -1060,18 +1059,11 @@ namespace Masuit.Tools
             {
                 case UriHostNameType.Dns:
                     var ipHostEntry = Dns.GetHostEntry(uri.DnsSafeHost);
-                    foreach (IPAddress ipAddress in ipHostEntry.AddressList)
+                    if (ipHostEntry.AddressList.Where(ipAddress => ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).Any(ipAddress => !ipAddress.IsPrivateIP()))
                     {
-                        if (ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
-                        {
-                            if (!ipAddress.IsPrivateIP())
-                            {
-                                return true;
-                            }
-                        }
+                        return true;
                     }
                     break;
-
                 case UriHostNameType.IPv4:
                     return !IPAddress.Parse(uri.DnsSafeHost).IsPrivateIP();
             }
@@ -1144,7 +1136,6 @@ namespace Masuit.Tools
             }
         }
 
-
         /// <summary>
         /// 添加符合条件的多个元素
         /// </summary>

+ 15 - 0
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -31,6 +31,17 @@ github:https://github.com/ldqk/Masuit.Tools</Description>
     <DocumentationFile>.\Masuit.Tools.Core.xml</DocumentationFile>
   </PropertyGroup>
 
+  <ItemGroup>
+    <Compile Include="..\Masuit.Tools\RandomSelector\Algorithm\BinarySearchOptimizer.cs" Link="RandomSelector\Algorithm\BinarySearchOptimizer.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\Algorithm\MultipleSelector.cs" Link="RandomSelector\Algorithm\MultipleSelector.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\Algorithm\SelectorBase.cs" Link="RandomSelector\Algorithm\SelectorBase.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\Algorithm\SingleSelector.cs" Link="RandomSelector\Algorithm\SingleSelector.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\Extensions.cs" Link="RandomSelector\Extensions.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\SelectorOptions.cs" Link="RandomSelector\SelectorOptions.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\WeightedItem.cs" Link="RandomSelector\WeightedItem.cs" />
+    <Compile Include="..\Masuit.Tools\RandomSelector\WeightedSelector.cs" Link="RandomSelector\WeightedSelector.cs" />
+  </ItemGroup>
+
   <ItemGroup>
     <PackageReference Include="HtmlAgilityPack" Version="1.11.17" />
     <PackageReference Include="HtmlSanitizer" Version="4.0.217" />
@@ -51,4 +62,8 @@ github:https://github.com/ldqk/Masuit.Tools</Description>
     </None>
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="RandomSelector\Algorithm\" />
+  </ItemGroup>
+
 </Project>

+ 11 - 32
Masuit.Tools/Extensions.cs

@@ -695,22 +695,21 @@ namespace Masuit.Tools
         /// <returns>是否匹配成功</returns>
         public static bool MatchIdentifyCard(this string s)
         {
+            const string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
             if (s.Length == 18)
             {
-                if (Int64.TryParse(s.Remove(17), out var n) == false || n < Math.Pow(10, 16) || Int64.TryParse(s.Replace('x', '0').Replace('X', '0'), out n) == false)
+                if (long.TryParse(s.Remove(17), out var n) == false || n < Math.Pow(10, 16) || Int64.TryParse(s.Replace('x', '0').Replace('X', '0'), out n) == false)
                 {
                     return false; //数字验证  
                 }
 
-                string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
                 if (address.IndexOf(s.Remove(2), StringComparison.Ordinal) == -1)
                 {
                     return false; //省份验证  
                 }
 
                 string birth = s.Substring(6, 8).Insert(6, "-").Insert(4, "-");
-                DateTime time;
-                if (!DateTime.TryParse(birth, out time))
+                if (!DateTime.TryParse(birth, out _))
                 {
                     return false; //生日验证  
                 }
@@ -724,36 +723,24 @@ namespace Masuit.Tools
                     sum += wi[i].ToInt32() * ai[i].ToString().ToInt32();
                 }
 
-                int y;
-                Math.DivRem(sum, 11, out y);
-                if (arrVarifyCode[y] != s.Substring(17, 1).ToLower())
-                {
-                    return false; //校验码验证  
-                }
-
-                return true; //符合GB11643-1999标准  
+                Math.DivRem(sum, 11, out var y);
+                return arrVarifyCode[y] == s.Substring(17, 1).ToLower();
             }
 
             if (s.Length == 15)
             {
-                if (Int64.TryParse(s, out var n) == false || n < Math.Pow(10, 14))
+                if (long.TryParse(s, out var n) == false || n < Math.Pow(10, 14))
                 {
                     return false; //数字验证  
                 }
 
-                string address = "11x22x35x44x53x12x23x36x45x54x13x31x37x46x61x14x32x41x50x62x15x33x42x51x63x21x34x43x52x64x65x71x81x82x91";
                 if (address.IndexOf(s.Remove(2), StringComparison.Ordinal) == -1)
                 {
                     return false; //省份验证  
                 }
 
                 string birth = s.Substring(6, 6).Insert(4, "-").Insert(2, "-");
-                if (DateTime.TryParse(birth, out _) == false)
-                {
-                    return false; //生日验证  
-                }
-
-                return true;
+                return DateTime.TryParse(birth, out _);
             }
 
             return false;
@@ -840,10 +827,10 @@ namespace Masuit.Tools
         /// <summary>
         /// 严格比较两个对象是否是同一对象
         /// </summary>
-        /// <param name="_this">自己</param>
+        /// <param name="this">自己</param>
         /// <param name="o">需要比较的对象</param>
         /// <returns>是否同一对象</returns>
-        public new static bool ReferenceEquals(this object _this, object o) => object.ReferenceEquals(_this, o);
+        public new static bool ReferenceEquals(this object @this, object o) => object.ReferenceEquals(@this, o);
 
         /// <summary>
         /// 判断字符串是否为空
@@ -1058,18 +1045,11 @@ namespace Masuit.Tools
             {
                 case UriHostNameType.Dns:
                     var ipHostEntry = Dns.GetHostEntry(uri.DnsSafeHost);
-                    foreach (IPAddress ipAddress in ipHostEntry.AddressList)
+                    if (ipHostEntry.AddressList.Where(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork).Any(ipAddress => !ipAddress.IsPrivateIP()))
                     {
-                        if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
-                        {
-                            if (!ipAddress.IsPrivateIP())
-                            {
-                                return true;
-                            }
-                        }
+                        return true;
                     }
                     break;
-
                 case UriHostNameType.IPv4:
                     return !IPAddress.Parse(uri.DnsSafeHost).IsPrivateIP();
             }
@@ -1142,7 +1122,6 @@ namespace Masuit.Tools
             }
         }
 
-
         /// <summary>
         /// 添加符合条件的多个元素
         /// </summary>

+ 9 - 0
Masuit.Tools/Masuit.Tools.csproj

@@ -183,6 +183,14 @@
     <Compile Include="Net\PartialDownloader.cs" />
     <Compile Include="Net\SocketClient.cs" />
     <Compile Include="NoSQL\RedisHelper.cs" />
+    <Compile Include="RandomSelector\Algorithm\BinarySearchOptimizer.cs" />
+    <Compile Include="RandomSelector\Algorithm\MultipleSelector.cs" />
+    <Compile Include="RandomSelector\Algorithm\SelectorBase.cs" />
+    <Compile Include="RandomSelector\Algorithm\SingleSelector.cs" />
+    <Compile Include="RandomSelector\Extensions.cs" />
+    <Compile Include="RandomSelector\SelectorOptions.cs" />
+    <Compile Include="RandomSelector\WeightedItem.cs" />
+    <Compile Include="RandomSelector\WeightedSelector.cs" />
     <Compile Include="Reflection\ClassHelper.cs" />
     <Compile Include="Reflection\ReflectHelper.cs" />
     <Compile Include="Reflection\ReflectionUtil.cs" />
@@ -223,6 +231,7 @@
       <SubType>Designer</SubType>
     </None>
   </ItemGroup>
+  <ItemGroup />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.

+ 29 - 0
Masuit.Tools/RandomSelector/Algorithm/BinarySearchOptimizer.cs

@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+
+namespace Masuit.Tools.RandomSelector.Algorithm
+{
+    public class BinarySearchOptimizer
+    {
+        /// <summary>
+        /// 计算累计权重
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="items"></param>
+        /// <returns></returns>
+        public static int[] GetCumulativeWeights<T>(List<WeightedItem<T>> items)
+        {
+            int TotalWeight = 0;
+            int Index = 0;
+            var ResultArray = new int[items.Count + 1];
+
+            foreach (var Item in items)
+            {
+                TotalWeight += Item.Weight;
+                ResultArray[Index] = TotalWeight;
+                Index++;
+            }
+
+            return ResultArray;
+        }
+    }
+}

+ 54 - 0
Masuit.Tools/RandomSelector/Algorithm/MultipleSelector.cs

@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+
+namespace Masuit.Tools.RandomSelector.Algorithm
+{
+    /// <summary>
+    /// 多选器
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    internal class MultipleSelector<T> : SelectorBase<T>
+    {
+        internal MultipleSelector(WeightedSelector<T> weightedSelector) : base(weightedSelector)
+        {
+        }
+
+        internal List<T> Select(int count)
+        {
+            Validate(ref count);
+            var Items = new List<WeightedItem<T>>(WeightedSelector.Items);
+            var ResultList = new List<T>();
+
+            do
+            {
+                var Item = WeightedSelector.Options.AllowDuplicates ? Select(Items) : SelectWithLinearSearch(Items);
+                ResultList.Add(Item.Value);
+                if (!WeightedSelector.Options.AllowDuplicates)
+                {
+                    Items.Remove(Item);
+                }
+            } while (ResultList.Count < count);
+            return ResultList;
+        }
+
+        private void Validate(ref int count)
+        {
+            if (count <= 0)
+            {
+                throw new InvalidOperationException("筛选个数必须大于0");
+            }
+
+            var Items = WeightedSelector.Items;
+
+            if (Items.Count == 0)
+            {
+                throw new InvalidOperationException("没有元素可以被筛选");
+            }
+
+            if (!WeightedSelector.Options.AllowDuplicates && Items.Count < count)
+            {
+                count = Items.Count;
+            }
+        }
+    }
+}

+ 93 - 0
Masuit.Tools/RandomSelector/Algorithm/SelectorBase.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Masuit.Tools.RandomSelector.Algorithm
+{
+    internal abstract class SelectorBase<T>
+    {
+        protected readonly WeightedSelector<T> WeightedSelector;
+        private readonly System.Random _rng;
+
+        internal SelectorBase(WeightedSelector<T> weightedSelector)
+        {
+            WeightedSelector = weightedSelector;
+            _rng = new System.Random();
+        }
+
+        protected int GetSeed(List<WeightedItem<T>> items)
+        {
+            var TopRange = items.Sum(i => i.Weight) + 1;
+            return _rng.Next(1, TopRange);
+        }
+
+        /// <summary>
+        /// 执行二进制筛选
+        /// </summary>
+        protected WeightedItem<T> Select(List<WeightedItem<T>> items)
+        {
+            if (items.Count == 0)
+            {
+                throw new InvalidOperationException("没有元素可以筛选");
+            }
+
+            var Seed = GetSeed(items);
+            return BinarySearch(items, Seed);
+        }
+
+        /// <summary>
+        /// 线性搜索算法
+        /// </summary>
+        protected WeightedItem<T> SelectWithLinearSearch(List<WeightedItem<T>> items)
+        {
+            // 这只对具有允许重复项的多选功能有用,它会随着时间从列表中删除项目。 在这些条件下没有消耗更多性能让二进制搜索起作用。
+            if (items.Count == 0)
+            {
+                throw new InvalidOperationException("没有元素可以筛选");
+            }
+
+            var Seed = GetSeed(items);
+            return LinearSearch(items, Seed);
+        }
+
+        /// <summary>
+        /// 线性筛选
+        /// </summary>
+        /// <param name="items"></param>
+        /// <param name="seed"></param>
+        /// <returns></returns>
+        private WeightedItem<T> LinearSearch(IEnumerable<WeightedItem<T>> items, int seed)
+        {
+            var RunningCount = 0;
+            foreach (var Item in items)
+            {
+                RunningCount += Item.Weight;
+                if (seed <= RunningCount)
+                {
+                    return Item;
+                }
+            }
+
+            throw new InvalidOperationException("没有任何的筛选结果");
+        }
+
+        /// <summary>
+        /// 二进制筛选
+        /// </summary>
+        /// <param name="items"></param>
+        /// <param name="seed"></param>
+        /// <returns></returns>
+        private WeightedItem<T> BinarySearch(List<WeightedItem<T>> items, int seed)
+        {
+            int Index = Array.BinarySearch(WeightedSelector.CumulativeWeights, seed);
+
+            //如果存在接近匹配项,二进制搜索返回的负数会比搜索的第1个索引少1。
+            if (Index < 0)
+            {
+                Index = -Index - 1;
+            }
+
+            return items[Index];
+        }
+    }
+}

+ 26 - 0
Masuit.Tools/RandomSelector/Algorithm/SingleSelector.cs

@@ -0,0 +1,26 @@
+using System;
+
+namespace Masuit.Tools.RandomSelector.Algorithm
+{
+    /// <summary>
+    /// 单选器
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    internal class SingleSelector<T> : SelectorBase<T>
+    {
+        internal SingleSelector(WeightedSelector<T> weightedSelector) : base(weightedSelector)
+        {
+        }
+
+        internal T Select()
+        {
+            var Items = WeightedSelector.Items;
+            if (Items.Count == 0)
+            {
+                throw new InvalidOperationException("没有元素可以筛选");
+            }
+
+            return Select(Items).Value;
+        }
+    }
+}

+ 33 - 0
Masuit.Tools/RandomSelector/Extensions.cs

@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Masuit.Tools.RandomSelector
+{
+    public static class Extensions
+    {
+        public static int TotalWeight<T>(this WeightedSelector<T> selector)
+        {
+            return selector.Items.Count == 0 ? 0 : selector.Items.Sum(t => t.Weight);
+        }
+
+        public static List<WeightedItem<T>> OrderByWeightDescending<T>(this WeightedSelector<T> selector)
+        {
+            return selector.Items.OrderByDescending(item => item.Weight).ToList();
+        }
+
+        public static List<WeightedItem<T>> OrderByWeightAscending<T>(this WeightedSelector<T> selector)
+        {
+            return selector.Items.OrderBy(item => item.Weight).ToList();
+        }
+
+        public static T WeightedItem<T>(this List<WeightedItem<T>> list)
+        {
+            return new WeightedSelector<T>(list).Select();
+        }
+
+        public static List<T> WeightedItem<T>(this List<WeightedItem<T>> list, int count)
+        {
+            return new WeightedSelector<T>(list).SelectMultiple(count);
+        }
+    }
+}

+ 21 - 0
Masuit.Tools/RandomSelector/SelectorOptions.cs

@@ -0,0 +1,21 @@
+namespace Masuit.Tools.RandomSelector
+{
+    public class SelectorOptions
+    {
+        /// <summary>
+        /// 多选时允许重复项
+        /// </summary>
+        public bool AllowDuplicates { get; set; }
+
+        /// <summary>
+        /// 是否移除权重0的元素
+        /// </summary>
+        public bool DropZeroWeightItems { get; set; }
+
+        public SelectorOptions()
+        {
+            AllowDuplicates = false;
+            DropZeroWeightItems = true;
+        }
+    }
+}

+ 20 - 0
Masuit.Tools/RandomSelector/WeightedItem.cs

@@ -0,0 +1,20 @@
+namespace Masuit.Tools.RandomSelector
+{
+    public class WeightedItem<T>
+    {
+        public int Weight;
+        public readonly T Value;
+
+        /// <summary>
+        /// 累计权重
+        /// </summary>
+        internal int CumulativeWeight;
+
+        public WeightedItem(T value, int weight)
+        {
+            Value = value;
+            Weight = weight;
+            CumulativeWeight = 0;
+        }
+    }
+}

+ 139 - 0
Masuit.Tools/RandomSelector/WeightedSelector.cs

@@ -0,0 +1,139 @@
+using Masuit.Tools.RandomSelector.Algorithm;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+
+namespace Masuit.Tools.RandomSelector
+{
+    /// <summary>
+    /// 权重筛选器
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class WeightedSelector<T> : IEnumerable<T>
+    {
+        internal readonly List<WeightedItem<T>> Items = new List<WeightedItem<T>>();
+        public readonly SelectorOptions Options;
+
+        /// <summary>
+        /// 累计权重集
+        /// </summary>
+        internal int[] CumulativeWeights;
+
+        /// <summary>
+        /// 是否是已经添加过的权重值
+        /// </summary>
+        private bool _isAddedCumulativeWeights;
+
+        public WeightedSelector(SelectorOptions options = null)
+        {
+            Options = options ?? new SelectorOptions();
+        }
+
+        public WeightedSelector(List<WeightedItem<T>> items, SelectorOptions options = null) : this(options)
+        {
+            Items = items;
+        }
+
+        /// <summary>
+        /// 添加元素
+        /// </summary>
+        /// <param name="item"></param>
+        public void Add(WeightedItem<T> item)
+        {
+            if (item.Weight <= 0)
+            {
+                if (Options.DropZeroWeightItems)
+                {
+                    return;
+                }
+
+                throw new InvalidOperationException("权重值不能为0");
+            }
+
+            _isAddedCumulativeWeights = true;
+            Items.Add(item);
+        }
+
+        /// <summary>
+        /// 批量添加元素
+        /// </summary>
+        /// <param name="items"></param>
+        public void Add(IEnumerable<WeightedItem<T>> items)
+        {
+            foreach (var Item in items)
+            {
+                Add(Item);
+            }
+        }
+
+        /// <summary>
+        /// 添加元素
+        /// </summary>
+        /// <param name="item"></param>
+        /// <param name="weight"></param>
+        public void Add(T item, int weight)
+        {
+            Add(new WeightedItem<T>(item, weight));
+        }
+
+        /// <summary>
+        /// 移除元素
+        /// </summary>
+        /// <param name="item"></param>
+        public void Remove(WeightedItem<T> item)
+        {
+            _isAddedCumulativeWeights = true;
+            Items.Remove(item);
+        }
+
+        /// <summary>
+        /// 执行权重筛选,取一个元素
+        /// </summary>
+        public T Select()
+        {
+            CalculateCumulativeWeights();
+            var Selector = new SingleSelector<T>(this);
+            return Selector.Select();
+        }
+
+        /// <summary>
+        /// 执行权重筛选,取多个元素
+        /// </summary>
+        public List<T> SelectMultiple(int count)
+        {
+            CalculateCumulativeWeights();
+            var Selector = new MultipleSelector<T>(this);
+            return Selector.Select(count);
+        }
+
+        /// <summary>
+        /// 计算累计权重
+        /// </summary>
+        private void CalculateCumulativeWeights()
+        {
+            if (!_isAddedCumulativeWeights) //如果没有被添加,则跳过
+            {
+                return;
+            }
+
+            _isAddedCumulativeWeights = false;
+            CumulativeWeights = BinarySearchOptimizer.GetCumulativeWeights(Items);
+        }
+
+        /// <summary>
+        /// 元素的只读集合
+        /// </summary>
+        public ReadOnlyCollection<WeightedItem<T>> ReadOnlyItems => new ReadOnlyCollection<WeightedItem<T>>(Items);
+
+        public IEnumerator<T> GetEnumerator()
+        {
+            return Items.GetEnumerator() as IEnumerator<T>;
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}