├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ └── dotnet.yml
├── .gitignore
├── Directory.Packages.props
├── LICENSE
├── README.md
├── nuget-delete.sh
├── nuget-pack.sh
├── nuget-push.sh
├── rm.Extensions.sln
├── src
└── rm.Extensions
│ ├── ArrayExtension.cs
│ ├── Base16.cs
│ ├── Base16Extension.cs
│ ├── Base32DouglasCrockford.cs
│ ├── Base32Extension.cs
│ ├── Base64Extension.cs
│ ├── BitSet.cs
│ ├── CharExtension.cs
│ ├── CircularQueue.cs
│ ├── CircularStack.cs
│ ├── DateTimeExtension.cs
│ ├── DecimalExtension.cs
│ ├── Deque.cs
│ ├── DequeNode.cs
│ ├── DictionaryExtension.cs
│ ├── EmptyException.cs
│ ├── EncodingExtension.cs
│ ├── EnumExtension.cs
│ ├── EnumInternal.cs
│ ├── EnumerableExtension.cs
│ ├── ExceptionHelper.cs
│ ├── GenericComparer.cs
│ ├── GenericEqualityComparer.cs
│ ├── GraphExtension.cs
│ ├── GuidExtension.cs
│ ├── HashQueue.cs
│ ├── Heap.cs
│ ├── Helper.cs
│ ├── IntExtension.cs
│ ├── ListExtension.cs
│ ├── LruCache.cs
│ ├── NameValueCollectionExtension.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── RandomExtension.cs
│ ├── StopwatchExtension.cs
│ ├── StringBuilderExtension.cs
│ ├── StringExtension.cs
│ ├── ThrowExtension.cs
│ ├── TimeSpanExtension.cs
│ ├── UnsupportedEnumValueException.TEnum.cs
│ ├── UriExtension.cs
│ ├── Wrapped.cs
│ ├── WrappedExtension.cs
│ └── rm.Extensions.csproj
└── tests
└── rm.ExtensionsTest
├── ArrayExtensionTest.cs
├── Base16ExtensionTest.cs
├── Base32ExtensionTest.cs
├── Base64ExtensionTest.cs
├── BitSetTest.cs
├── CircularQueueTest.cs
├── CircularStackTest.cs
├── DateTimeExtensionTest.cs
├── DecimalExtensionTest.cs
├── DequeTest.cs
├── DictionaryExtensionTest.cs
├── EnumExtensionTest.cs
├── EnumerableExtensionTest.cs
├── GraphExtensionTest.cs
├── GuidExtensionTest.cs
├── HashQueueTest.cs
├── HeapTest.cs
├── HelperTest.cs
├── IntExtensionTest.cs
├── ListExtensionTest.cs
├── LruCacheTest.cs
├── NameValueCollectionExtensionTest.cs
├── Properties
└── AssemblyInfo.cs
├── RandomExtensionTest.cs
├── Sample
├── ComparableClass.cs
├── ComparableClass2.cs
├── ComparableClass2Comparer.cs
└── SampleClass.cs
├── StopwatchExtensionTest.cs
├── StringBuilderExtensionTest.cs
├── StringExtensionTest.cs
├── ThrowExtensionTest.cs
├── TimeSpanExtensionTest.cs
├── UriExtensionTest.cs
├── WrappedExtensionTest.cs
├── images
└── d s .jpg
└── rm.ExtensionsTest.csproj
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = crlf
5 | [*.*]
6 | end_of_line = crlf
7 |
8 | [*.cs]
9 | indent_size = 4
10 | indent_style = tab
11 | end_of_line = crlf
12 | insert_final_newline = true
13 |
14 | [*.csproj]
15 | indent_size = 2
16 | indent_style = space
17 | end_of_line = crlf
18 | insert_final_newline = true
19 |
20 | [*.json]
21 | indent_style = space
22 | indent_size = 2
23 | end_of_line = crlf
24 | insert_final_newline = true
25 |
26 | [*.sln]
27 | indent_size = 4
28 | indent_style = tab
29 | end_of_line = crlf
30 | #insert_final_newline = true
31 |
32 | [*.sh]
33 | indent_size = 4
34 | indent_style = tab
35 | end_of_line = crlf
36 | insert_final_newline = true
37 | trim_trailing_whitespace = true
38 |
39 | [*.cs]
40 | csharp_style_namespace_declarations = file_scoped:warning
41 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.github/workflows/dotnet.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on: [ push, pull_request ]
4 |
5 | jobs:
6 | build-workflow:
7 | uses: rmandvikar/workflows/.github/workflows/dotnet.yml@main
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.pch
68 | *.pdb
69 | *.pgc
70 | *.pgd
71 | *.rsp
72 | *.sbr
73 | *.tlb
74 | *.tli
75 | *.tlh
76 | *.tmp
77 | *.tmp_proj
78 | *.log
79 | *.vspscc
80 | *.vssscc
81 | .builds
82 | *.pidb
83 | *.svclog
84 | *.scc
85 |
86 | # Chutzpah Test files
87 | _Chutzpah*
88 |
89 | # Visual C++ cache files
90 | ipch/
91 | *.aps
92 | *.ncb
93 | *.opendb
94 | *.opensdf
95 | *.sdf
96 | *.cachefile
97 | *.VC.db
98 | *.VC.VC.opendb
99 |
100 | # Visual Studio profiler
101 | *.psess
102 | *.vsp
103 | *.vspx
104 | *.sap
105 |
106 | # Visual Studio Trace Files
107 | *.e2e
108 |
109 | # TFS 2012 Local Workspace
110 | $tf/
111 |
112 | # Guidance Automation Toolkit
113 | *.gpState
114 |
115 | # ReSharper is a .NET coding add-in
116 | _ReSharper*/
117 | *.[Rr]e[Ss]harper
118 | *.DotSettings.user
119 |
120 | # JustCode is a .NET coding add-in
121 | .JustCode
122 |
123 | # TeamCity is a build add-in
124 | _TeamCity*
125 |
126 | # DotCover is a Code Coverage Tool
127 | *.dotCover
128 |
129 | # AxoCover is a Code Coverage Tool
130 | .axoCover/*
131 | !.axoCover/settings.json
132 |
133 | # Visual Studio code coverage results
134 | *.coverage
135 | *.coveragexml
136 |
137 | # NCrunch
138 | _NCrunch_*
139 | .*crunch*.local.xml
140 | nCrunchTemp_*
141 |
142 | # MightyMoose
143 | *.mm.*
144 | AutoTest.Net/
145 |
146 | # Web workbench (sass)
147 | .sass-cache/
148 |
149 | # Installshield output folder
150 | [Ee]xpress/
151 |
152 | # DocProject is a documentation generator add-in
153 | DocProject/buildhelp/
154 | DocProject/Help/*.HxT
155 | DocProject/Help/*.HxC
156 | DocProject/Help/*.hhc
157 | DocProject/Help/*.hhk
158 | DocProject/Help/*.hhp
159 | DocProject/Help/Html2
160 | DocProject/Help/html
161 |
162 | # Click-Once directory
163 | publish/
164 |
165 | # Publish Web Output
166 | *.[Pp]ublish.xml
167 | *.azurePubxml
168 | # Note: Comment the next line if you want to checkin your web deploy settings,
169 | # but database connection strings (with potential passwords) will be unencrypted
170 | *.pubxml
171 | *.publishproj
172 |
173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
174 | # checkin your Azure Web App publish settings, but sensitive information contained
175 | # in these scripts will be unencrypted
176 | PublishScripts/
177 |
178 | # NuGet Packages
179 | *.nupkg
180 | # The packages folder can be ignored because of Package Restore
181 | **/[Pp]ackages/*
182 | # except build/, which is used as an MSBuild target.
183 | !**/[Pp]ackages/build/
184 | # Uncomment if necessary however generally it will be regenerated when needed
185 | #!**/[Pp]ackages/repositories.config
186 | # NuGet v3's project.json files produces more ignorable files
187 | *.nuget.props
188 | *.nuget.targets
189 | *.snupkg
190 |
191 | # Microsoft Azure Build Output
192 | csx/
193 | *.build.csdef
194 |
195 | # Microsoft Azure Emulator
196 | ecf/
197 | rcf/
198 |
199 | # Windows Store app package directories and files
200 | AppPackages/
201 | BundleArtifacts/
202 | Package.StoreAssociation.xml
203 | _pkginfo.txt
204 | *.appx
205 |
206 | # Visual Studio cache files
207 | # files ending in .cache can be ignored
208 | *.[Cc]ache
209 | # but keep track of directories ending in .cache
210 | !*.[Cc]ache/
211 |
212 | # Others
213 | ClientBin/
214 | ~$*
215 | *~
216 | *.dbmdl
217 | *.dbproj.schemaview
218 | *.jfm
219 | *.pfx
220 | *.publishsettings
221 | orleans.codegen.cs
222 |
223 | # Since there are multiple workflows, uncomment next line to ignore bower_components
224 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
225 | #bower_components/
226 |
227 | # RIA/Silverlight projects
228 | Generated_Code/
229 |
230 | # Backup & report files from converting an old project file
231 | # to a newer Visual Studio version. Backup files are not needed,
232 | # because we have git ;-)
233 | _UpgradeReport_Files/
234 | Backup*/
235 | UpgradeLog*.XML
236 | UpgradeLog*.htm
237 |
238 | # SQL Server files
239 | *.mdf
240 | *.ldf
241 | *.ndf
242 |
243 | # Business Intelligence projects
244 | *.rdl.data
245 | *.bim.layout
246 | *.bim_*.settings
247 |
248 | # Microsoft Fakes
249 | FakesAssemblies/
250 |
251 | # GhostDoc plugin setting file
252 | *.GhostDoc.xml
253 |
254 | # Node.js Tools for Visual Studio
255 | .ntvs_analysis.dat
256 | node_modules/
257 |
258 | # TypeScript v1 declaration files
259 | typings/
260 |
261 | # Visual Studio 6 build log
262 | *.plg
263 |
264 | # Visual Studio 6 workspace options file
265 | *.opt
266 |
267 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
268 | *.vbw
269 |
270 | # Visual Studio LightSwitch build output
271 | **/*.HTMLClient/GeneratedArtifacts
272 | **/*.DesktopClient/GeneratedArtifacts
273 | **/*.DesktopClient/ModelManifest.xml
274 | **/*.Server/GeneratedArtifacts
275 | **/*.Server/ModelManifest.xml
276 | _Pvt_Extensions
277 |
278 | # Paket dependency manager
279 | .paket/paket.exe
280 | paket-files/
281 |
282 | # FAKE - F# Make
283 | .fake/
284 |
285 | # JetBrains Rider
286 | .idea/
287 | *.sln.iml
288 |
289 | # CodeRush
290 | .cr/
291 |
292 | # Python Tools for Visual Studio (PTVS)
293 | __pycache__/
294 | *.pyc
295 |
296 | # Cake - Uncomment if you are using it
297 | # tools/**
298 | # !tools/packages.config
299 |
300 | # Tabs Studio
301 | *.tss
302 |
303 | # Telerik's JustMock configuration file
304 | *.jmconfig
305 |
306 | # BizTalk build output
307 | *.btp.cs
308 | *.btm.cs
309 | *.odx.cs
310 | *.xsd.cs
311 |
312 | # OpenCover UI analysis results
313 | OpenCover/
314 |
315 | # Azure Stream Analytics local run output
316 | ASALocalRun/
317 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 | false
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | https://github.com/rmandvikar/csharp-extensions
4 | Copyright (c) 2019 hippy
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy of
7 | this software and associated documentation files (the "Software"), to deal in
8 | the Software without restriction, including without limitation the rights to
9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10 | the Software, and to permit persons to whom the Software is furnished to do so,
11 | subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/nuget-delete.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #usage: nuget-delete
4 |
5 | if [[ "$1" == "-h" ]]; then print-file-comments "$0"; exit; fi
6 |
7 | version="$1"
8 | if [[ -z "$version" ]]; then
9 | echo 1>&2 "fatal: version required"
10 | exec print-file-comments "$0"
11 | exit 1
12 | fi
13 |
14 | dotnet nuget delete rmandvikar.Extensions "$version" \
15 | -k $(< ~/dump/.nuget.apikey) \
16 | -s https://api.nuget.org/v3/index.json \
17 | --non-interactive
18 |
--------------------------------------------------------------------------------
/nuget-pack.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #usage: nuget-pack
4 |
5 | if [[ "$1" == "-h" ]]; then print-file-comments "$0"; exit; fi
6 |
7 | version="$1"
8 | if [[ -z "$version" ]]; then
9 | echo 1>&2 "fatal: version required"
10 | exec print-file-comments "$0"
11 | exit 1
12 | fi
13 |
14 | tag="v$version"
15 | commit_hash=$(git rev-parse @)
16 | metadata="$commit_hash"
17 |
18 | dotnet pack src/rm.Extensions/rm.Extensions.csproj \
19 | -c Release \
20 | --include-symbols //p:SymbolPackageFormat=snupkg \
21 | -o .nupkg/ \
22 | //p:Version="$version+$metadata" \
23 | //p:PackageVersion="$version+$metadata" \
24 | //p:PackageReleaseNotes="tag: $tag" \
25 | && git tag "$tag" -m "Create nuget tag $tag"
26 |
--------------------------------------------------------------------------------
/nuget-push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #usage: nuget-push
4 |
5 | if [[ "$1" == "-h" ]]; then print-file-comments "$0"; exit; fi
6 |
7 | version="$1"
8 | if [[ -z "$version" ]]; then
9 | echo 1>&2 "fatal: version required"
10 | exec print-file-comments "$0"
11 | exit 1
12 | fi
13 |
14 | tag="v$version"
15 |
16 | dotnet nuget push .nupkg/rmandvikar.Extensions."$version".nupkg \
17 | -k $(< ~/dump/.nuget.apikey) \
18 | -s https://api.nuget.org/v3/index.json \
19 | && git push $(git remote) "$tag"
20 |
--------------------------------------------------------------------------------
/rm.Extensions.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.5.33502.453
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "rm.Extensions", "src\rm.Extensions\rm.Extensions.csproj", "{CDD966D6-4370-4745-9B0E-F8D7E252620E}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "rm.ExtensionsTest", "tests\rm.ExtensionsTest\rm.ExtensionsTest.csproj", "{3C36D356-3426-4951-88D9-073233CBFDB9}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{51F18744-8BB4-4636-B916-51DA6EBFBD48}"
11 | ProjectSection(SolutionItems) = preProject
12 | Directory.Packages.props = Directory.Packages.props
13 | LICENSE = LICENSE
14 | README.md = README.md
15 | EndProjectSection
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {CDD966D6-4370-4745-9B0E-F8D7E252620E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {CDD966D6-4370-4745-9B0E-F8D7E252620E}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {CDD966D6-4370-4745-9B0E-F8D7E252620E}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {CDD966D6-4370-4745-9B0E-F8D7E252620E}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {3C36D356-3426-4951-88D9-073233CBFDB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {3C36D356-3426-4951-88D9-073233CBFDB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {3C36D356-3426-4951-88D9-073233CBFDB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {3C36D356-3426-4951-88D9-073233CBFDB9}.Release|Any CPU.Build.0 = Release|Any CPU
31 | EndGlobalSection
32 | GlobalSection(SolutionProperties) = preSolution
33 | HideSolutionNode = FALSE
34 | EndGlobalSection
35 | EndGlobal
36 |
--------------------------------------------------------------------------------
/src/rm.Extensions/ArrayExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Array{T} extensions.
9 | ///
10 | public static class Array
11 | {
12 | ///
13 | /// Returns empty T array.
14 | ///
15 | public static T[] Empty => Array.Empty();
16 | }
17 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Base16.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace rm.Extensions;
4 |
5 | public class Base16
6 | {
7 | // note: use int to avoid implicit conversions
8 | public const int MinValue = 0;
9 | public const int MaxValue = 15;
10 |
11 | private const int bitsInBase16Char = 4;
12 | private const int decodeMapLength = sbyte.MaxValue + 1;
13 | private const int defaultValue = byte.MaxValue;
14 |
15 | ///
16 | /// 4-bit int value -> base16 char map.
17 | ///
18 | ///
19 | /// Array index lookup is faster than hashmap, so.
20 | /// It provides a good balance of speed, readability, and maintainability.
21 | ///
22 | /// [https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa]
23 | private static readonly char[] encodeUppercaseMap = GetEncodeUppercaseMap();
24 | private static char[] GetEncodeUppercaseMap()
25 | {
26 | return new char[]
27 | {
28 | '0', // 0
29 | '1', // 1
30 | '2', // 2
31 | '3', // 3
32 | '4', // 4
33 | '5', // 5
34 | '6', // 6
35 | '7', // 7
36 | '8', // 8
37 | '9', // 9
38 | 'A', // 10
39 | 'B', // 11
40 | 'C', // 12
41 | 'D', // 13
42 | 'E', // 14
43 | 'F', // 15
44 | };
45 | }
46 |
47 | ///
48 | private static readonly char[] encodeLowercaseMap = GetEncodeLowercaseMap();
49 | private static char[] GetEncodeLowercaseMap()
50 | {
51 | return new char[]
52 | {
53 | '0', // 0
54 | '1', // 1
55 | '2', // 2
56 | '3', // 3
57 | '4', // 4
58 | '5', // 5
59 | '6', // 6
60 | '7', // 7
61 | '8', // 8
62 | '9', // 9
63 | 'a', // 10
64 | 'b', // 11
65 | 'c', // 12
66 | 'd', // 13
67 | 'e', // 14
68 | 'f', // 15
69 | };
70 | }
71 |
72 | ///
73 | /// Base16 char -> 4-bit int value map.
74 | ///
75 | ///
76 | /// Array index lookup is faster than hashmap, so.
77 | /// It provides a good balance of speed, readability, and maintainability.
78 | ///
79 | /// [https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa]
80 | private static readonly int[] decodeMap = GetDecodeMap();
81 | private static int[] GetDecodeMap()
82 | {
83 | var decodeMap = new int[decodeMapLength];
84 | for (int i = 0; i < decodeMap.Length; i++)
85 | {
86 | decodeMap[i] = defaultValue;
87 | }
88 |
89 | decodeMap['0'] = 0;
90 | decodeMap['1'] = 1;
91 | decodeMap['2'] = 2;
92 | decodeMap['3'] = 3;
93 | decodeMap['4'] = 4;
94 | decodeMap['5'] = 5;
95 | decodeMap['6'] = 6;
96 | decodeMap['7'] = 7;
97 | decodeMap['8'] = 8;
98 | decodeMap['9'] = 9;
99 | decodeMap['A'] = 10;
100 | decodeMap['a'] = 10; // lowercase
101 | decodeMap['B'] = 11;
102 | decodeMap['b'] = 11; // lowercase
103 | decodeMap['C'] = 12;
104 | decodeMap['c'] = 12; // lowercase
105 | decodeMap['D'] = 13;
106 | decodeMap['d'] = 13; // lowercase
107 | decodeMap['E'] = 14;
108 | decodeMap['e'] = 14; // lowercase
109 | decodeMap['F'] = 15;
110 | decodeMap['f'] = 15; // lowercase
111 |
112 | return decodeMap;
113 | }
114 |
115 | ///
116 | /// Returns Base16 encoded (hex) string from .
117 | ///
118 | public string Encode(byte[] bytes)
119 | {
120 | return EncodeInner(bytes, encodeUppercaseMap);
121 | }
122 |
123 | ///
124 | public string EncodeUppercase(byte[] bytes)
125 | {
126 | return EncodeInner(bytes, encodeUppercaseMap);
127 | }
128 |
129 | ///
130 | public string EncodeLowercase(byte[] bytes)
131 | {
132 | return EncodeInner(bytes, encodeLowercaseMap);
133 | }
134 |
135 | private string EncodeInner(byte[] bytes, char[] encodeMap)
136 | {
137 | _ = bytes
138 | ?? throw new ArgumentNullException(nameof(bytes));
139 |
140 | var bytesLength = bytes.Length;
141 | var base16 = new char[bytesLength << 1];
142 | for (int i = 0; i < bytesLength; i++)
143 | {
144 | var @byte = bytes[i];
145 | var itarget = i << 1;
146 | base16[itarget] = encodeMap[(@byte >> bitsInBase16Char) & 0b_1111];
147 | base16[itarget + 1] = encodeMap[(@byte) & 0b_1111];
148 | }
149 | return new string(base16);
150 | }
151 |
152 | ///
153 | /// Returns decoded bytes from encoded (hex) string.
154 | ///
155 | public byte[] Decode(string base16)
156 | {
157 | _ = base16
158 | ?? throw new ArgumentNullException(nameof(base16));
159 |
160 | var base16Length = base16.Length;
161 | if ((base16Length & 0b_1) != 0)
162 | {
163 | throw new ArgumentException($"Invalid length: {base16}", nameof(base16));
164 | }
165 |
166 | var bytes = new byte[base16Length >> 1];
167 | for (int i = 0; i < base16Length; i += 2)
168 | {
169 | var msNibble = decodeMap[base16[i]];
170 | var lsNibble = decodeMap[base16[i + 1]];
171 | if (msNibble == defaultValue)
172 | {
173 | var base16Char = base16[i];
174 | throw new ArgumentOutOfRangeException(nameof(base16), base16Char, base16);
175 | }
176 | if (lsNibble == defaultValue)
177 | {
178 | var base16Char = base16[i + 1];
179 | throw new ArgumentOutOfRangeException(nameof(base16), base16Char, base16);
180 | }
181 | bytes[i >> 1] = (byte)((msNibble << bitsInBase16Char) | lsNibble);
182 | }
183 |
184 | return bytes;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Base16Extension.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Base16 extensions.
7 | ///
8 | public static class Base16Extension
9 | {
10 | private static readonly Base16 base16Converter = new Base16();
11 |
12 | /// .
13 | [DebuggerStepThrough]
14 | public static string Base16Encode(this byte[] bytes)
15 | {
16 | return base16Converter.Encode(bytes);
17 | }
18 |
19 | /// .
20 | [DebuggerStepThrough]
21 | public static string Base16EncodeUppercase(this byte[] bytes)
22 | {
23 | return base16Converter.EncodeUppercase(bytes);
24 | }
25 |
26 | /// .
27 | [DebuggerStepThrough]
28 | public static string Base16EncodeLowercase(this byte[] bytes)
29 | {
30 | return base16Converter.EncodeLowercase(bytes);
31 | }
32 |
33 | /// .
34 | [DebuggerStepThrough]
35 | public static string ToHexString(this byte[] bytes)
36 | {
37 | return base16Converter.Encode(bytes);
38 | }
39 |
40 | /// .
41 | [DebuggerStepThrough]
42 | public static byte[] Base16Decode(this string base16)
43 | {
44 | return base16Converter.Decode(base16);
45 | }
46 |
47 | /// .
48 | [DebuggerStepThrough]
49 | public static byte[] FromHexString(this string base16)
50 | {
51 | return base16Converter.Decode(base16);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Base32DouglasCrockford.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Linq;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Base32 encoding, decoding.
9 | ///
10 | /// Douglas Crockford variant.
11 | ///
12 | /// See https://www.crockford.com/base32.html
13 | ///
14 | public class Base32DouglasCrockford
15 | {
16 | // note: use int to avoid implicit conversions
17 | public const int MinValue = 0;
18 | public const int MaxValue = 31;
19 | public const int MaxValueIncludingCheckSymbol = 36;
20 |
21 | private const int bitsInByte = 8;
22 | private const int bitsInInteger = 32;
23 | private const int bitsInBase32Char = 5;
24 |
25 | ///
26 | /// Returns Base32 encoded string from .
27 | ///
28 | public string Encode(byte[] bytes)
29 | {
30 | _ = bytes
31 | ?? throw new ArgumentNullException(nameof(bytes));
32 |
33 | var base32 = new StringBuilder((bytes.Length * bitsInByte / bitsInBase32Char) + 1);
34 |
35 | // bitwise impl of 5B/40b for speed
36 | var slices = bytes.Length / bitsInBase32Char;
37 | int sliceStartIndex;
38 | for (int iSlice = 0; iSlice < slices; iSlice++)
39 | {
40 | sliceStartIndex = iSlice * bitsInBase32Char;
41 |
42 | // read bits as string representation, L->R within byte
43 | var base32CharOfSlice1 = (bytes[sliceStartIndex + 0] >> 3) & 0b1_1111;
44 | var base32CharOfSlice2 = ((bytes[sliceStartIndex + 0] << 2) & 0b1_1100) | ((bytes[sliceStartIndex + 1] >> 6) & 0b0_0011);
45 | var base32CharOfSlice3 = (bytes[sliceStartIndex + 1] >> 1) & 0b1_1111;
46 | var base32CharOfSlice4 = ((bytes[sliceStartIndex + 1] << 4) & 0b1_0000) | ((bytes[sliceStartIndex + 2] >> 4) & 0b0_1111);
47 | var base32CharOfSlice5 = ((bytes[sliceStartIndex + 2] << 1) & 0b1_1110) | ((bytes[sliceStartIndex + 3] >> 7) & 0b0_0001);
48 | var base32CharOfSlice6 = (bytes[sliceStartIndex + 3] >> 2) & 0b1_1111;
49 | var base32CharOfSlice7 = ((bytes[sliceStartIndex + 3] << 3) & 0b1_1000) | ((bytes[sliceStartIndex + 4] >> 5) & 0b0_0111);
50 | var base32CharOfSlice8 = bytes[sliceStartIndex + 4] & 0b1_1111;
51 |
52 | base32.Append(base32CharOfSlice1.GetBase32Char());
53 | base32.Append(base32CharOfSlice2.GetBase32Char());
54 | base32.Append(base32CharOfSlice3.GetBase32Char());
55 | base32.Append(base32CharOfSlice4.GetBase32Char());
56 | base32.Append(base32CharOfSlice5.GetBase32Char());
57 | base32.Append(base32CharOfSlice6.GetBase32Char());
58 | base32.Append(base32CharOfSlice7.GetBase32Char());
59 | base32.Append(base32CharOfSlice8.GetBase32Char());
60 | }
61 |
62 | // handle surplus bytes (max 4) if any
63 | var surplusBytes = bytes.Length % bitsInBase32Char;
64 | sliceStartIndex = bytes.Length - surplusBytes;
65 | if (surplusBytes > 0)
66 | {
67 | int buffer = 0;
68 | var iSurplusByte = sliceStartIndex;
69 | for (int ibuffer = (bitsInBase32Char - 1) - 1; ibuffer >= 0 && iSurplusByte < bytes.Length; ibuffer--)
70 | {
71 | buffer = buffer | bytes[iSurplusByte] << (ibuffer * bitsInByte);
72 | iSurplusByte++;
73 | }
74 | var surplusBase32Chars = (surplusBytes * bitsInByte) / bitsInBase32Char + 1;
75 | for (int iSurplusBase32Char = 0; iSurplusBase32Char < surplusBase32Chars; iSurplusBase32Char++)
76 | {
77 | var surplusBase32Char = ((buffer >> (bitsInInteger - bitsInBase32Char)) & 0b_1_1111).GetBase32Char();
78 | base32.Append(surplusBase32Char);
79 | buffer = buffer << bitsInBase32Char;
80 | }
81 | }
82 |
83 | return base32.ToString();
84 | }
85 |
86 | ///
87 | /// Returns decoded bytes from encoded string.
88 | ///
89 | public byte[] Decode(string base32)
90 | {
91 | _ = base32
92 | ?? throw new ArgumentNullException(nameof(base32));
93 |
94 | // HACK: Improve '-' symbol check perf
95 | var base32Tokens = base32.Split('-');
96 | var base32TokenBytes = base32Tokens.Select(DecodeInner);
97 | var bytes = Helper.Concat(base32TokenBytes.ToArray());
98 | return bytes;
99 | }
100 |
101 | private byte[] DecodeInner(string base32)
102 | {
103 | var size = base32.Length * bitsInBase32Char / bitsInByte;
104 | var bytes = new byte[size];
105 |
106 | // bitwise impl of 5B/40b for speed
107 | var slices = base32.Length / bitsInByte;
108 | int sliceStartIndex;
109 | var iByte = 0;
110 | for (int iSlice = 0; iSlice < slices; iSlice++)
111 | {
112 | sliceStartIndex = iSlice * bitsInByte;
113 |
114 | // read bits as string representation, L->R within char
115 | var base32Value1 = base32[sliceStartIndex + 0].GetBase32Value();
116 | var base32Value2 = base32[sliceStartIndex + 1].GetBase32Value();
117 | var base32Value3 = base32[sliceStartIndex + 2].GetBase32Value();
118 | var base32Value4 = base32[sliceStartIndex + 3].GetBase32Value();
119 | var base32Value5 = base32[sliceStartIndex + 4].GetBase32Value();
120 | var base32Value6 = base32[sliceStartIndex + 5].GetBase32Value();
121 | var base32Value7 = base32[sliceStartIndex + 6].GetBase32Value();
122 | var base32Value8 = base32[sliceStartIndex + 7].GetBase32Value();
123 |
124 | bytes[iByte++] = (byte)(base32Value1 << 3 | base32Value2 >> 2);
125 | bytes[iByte++] = (byte)(base32Value2 << 6 | base32Value3 << 1 | base32Value4 >> 4);
126 | bytes[iByte++] = (byte)(base32Value4 << 4 | base32Value5 >> 1);
127 | bytes[iByte++] = (byte)(base32Value5 << 7 | base32Value6 << 2 | base32Value7 >> 3);
128 | bytes[iByte++] = (byte)(base32Value7 << 5 | base32Value8);
129 | }
130 |
131 | // handle surplus chars (max 7) if any
132 | var surplusChars = base32.Length % bitsInByte;
133 | sliceStartIndex = base32.Length - surplusChars;
134 | if (surplusChars > 0)
135 | {
136 | int buffer = 0;
137 | var shiftSurplusChar = bitsInInteger;
138 | for (int iSurplusChar = sliceStartIndex; iSurplusChar < base32.Length; iSurplusChar++)
139 | {
140 | var base32Value = base32[iSurplusChar].GetBase32Value();
141 | shiftSurplusChar -= bitsInBase32Char;
142 | buffer = buffer | (shiftSurplusChar > 0 ? base32Value << shiftSurplusChar : base32Value >> -shiftSurplusChar);
143 | }
144 | var shiftBuffer = (bitsInBase32Char - 1) - 1;
145 | while (iByte < bytes.Length)
146 | {
147 | var @byte = buffer >> (shiftBuffer * bitsInByte);
148 | bytes[iByte] = (byte)@byte;
149 | iByte++;
150 | shiftBuffer--;
151 | }
152 | }
153 |
154 | return bytes;
155 | }
156 | }
157 |
158 | ///
159 | /// Helper class.
160 | ///
161 | internal static class Base32DouglasCrockfordExtension
162 | {
163 | // note: use int to avoid implicit conversions
164 | private const int minValue = Base32DouglasCrockford.MinValue;
165 | private const int maxValue = Base32DouglasCrockford.MaxValue;
166 | private const int maxValueIncludingCheckSymbol = Base32DouglasCrockford.MaxValueIncludingCheckSymbol;
167 | private const int defaultValue = byte.MaxValue;
168 |
169 | ///
170 | /// 5-bit int value -> base32 char map.
171 | ///
172 | /// Array index lookup is faster than hashmap, so.
173 | private static readonly char[] encodeMap = GetEncodeMap();
174 |
175 | private static char[] GetEncodeMap()
176 | {
177 | return new char[]
178 | {
179 | '0', // 0
180 | '1', // 1
181 | '2', // 2
182 | '3', // 3
183 | '4', // 4
184 | '5', // 5
185 | '6', // 6
186 | '7', // 7
187 | '8', // 8
188 | '9', // 9
189 | 'A', // 10
190 | 'B', // 11
191 | 'C', // 12
192 | 'D', // 13
193 | 'E', // 14
194 | 'F', // 15
195 | 'G', // 16
196 | 'H', // 17
197 | 'J', // 18 // skip I
198 | 'K', // 19
199 | 'M', // 20 // skip L
200 | 'N', // 21
201 | 'P', // 22 // skip O
202 | 'Q', // 23
203 | 'R', // 24
204 | 'S', // 25
205 | 'T', // 26
206 | 'V', // 27 // skip U
207 | 'W', // 28
208 | 'X', // 29
209 | 'Y', // 30
210 | 'Z', // 31
211 | '*', // 32 // check symbol
212 | '~', // 33 // check symbol
213 | '$', // 34 // check symbol
214 | '=', // 35 // check symbol
215 | 'U', // 36 // check symbol
216 | };
217 | }
218 |
219 | ///
220 | /// Base32 char -> 5-bit int value map.
221 | ///
222 | /// Array index lookup is faster than hashmap, so.
223 | private static readonly int[] decodeMap = GetDecodeMap();
224 |
225 | private const int decodeMapLength = sbyte.MaxValue + 1;
226 |
227 | private static int[] GetDecodeMap()
228 | {
229 | var decodeMap = new int[decodeMapLength];
230 | for (int i = 0; i < decodeMap.Length; i++)
231 | {
232 | decodeMap[i] = defaultValue;
233 | }
234 |
235 | decodeMap['0'] = 0; // 0 O o
236 | decodeMap['O'] = 0; // 0 O o
237 | decodeMap['o'] = 0; // 0 O o
238 | decodeMap['1'] = 1; // 1 I i L l
239 | decodeMap['I'] = 1; // 1 I i L l
240 | decodeMap['i'] = 1; // 1 I i L l
241 | decodeMap['L'] = 1; // 1 I i L l
242 | decodeMap['l'] = 1; // 1 I i L l
243 | decodeMap['2'] = 2;
244 | decodeMap['3'] = 3;
245 | decodeMap['4'] = 4;
246 | decodeMap['5'] = 5;
247 | decodeMap['6'] = 6;
248 | decodeMap['7'] = 7;
249 | decodeMap['8'] = 8;
250 | decodeMap['9'] = 9;
251 | decodeMap['A'] = 10;
252 | decodeMap['a'] = 10;
253 | decodeMap['B'] = 11;
254 | decodeMap['b'] = 11;
255 | decodeMap['C'] = 12;
256 | decodeMap['c'] = 12;
257 | decodeMap['D'] = 13;
258 | decodeMap['d'] = 13;
259 | decodeMap['E'] = 14;
260 | decodeMap['e'] = 14;
261 | decodeMap['F'] = 15;
262 | decodeMap['f'] = 15;
263 | decodeMap['G'] = 16;
264 | decodeMap['g'] = 16;
265 | decodeMap['H'] = 17;
266 | decodeMap['h'] = 17;
267 | decodeMap['J'] = 18; // skip I
268 | decodeMap['j'] = 18;
269 | decodeMap['K'] = 19;
270 | decodeMap['k'] = 19;
271 | decodeMap['M'] = 20; // skip L
272 | decodeMap['m'] = 20;
273 | decodeMap['N'] = 21;
274 | decodeMap['n'] = 21;
275 | decodeMap['P'] = 22; // skip O
276 | decodeMap['p'] = 22;
277 | decodeMap['Q'] = 23;
278 | decodeMap['q'] = 23;
279 | decodeMap['R'] = 24;
280 | decodeMap['r'] = 24;
281 | decodeMap['S'] = 25;
282 | decodeMap['s'] = 25;
283 | decodeMap['T'] = 26;
284 | decodeMap['t'] = 26;
285 | decodeMap['V'] = 27; // skip U
286 | decodeMap['v'] = 27;
287 | decodeMap['W'] = 28;
288 | decodeMap['w'] = 28;
289 | decodeMap['X'] = 29;
290 | decodeMap['x'] = 29;
291 | decodeMap['Y'] = 30;
292 | decodeMap['y'] = 30;
293 | decodeMap['Z'] = 31;
294 | decodeMap['z'] = 31;
295 | decodeMap['*'] = 32; // check symbol
296 | decodeMap['~'] = 33; // check symbol
297 | decodeMap['$'] = 34; // check symbol
298 | decodeMap['='] = 35; // check symbol
299 | decodeMap['U'] = 36; // check symbol
300 | decodeMap['u'] = 36; // check symbol
301 |
302 | return decodeMap;
303 | }
304 |
305 | ///
306 | /// Returns Base32 encoded char for 5-bit int .
307 | ///
308 | internal static char GetBase32Char(this int value)
309 | {
310 | if (!(minValue <= value && value <= maxValue))
311 | {
312 | throw new ArgumentOutOfRangeException(nameof(value), value, null);
313 | }
314 | return encodeMap[value];
315 | }
316 |
317 | ///
318 | /// Returns 5-bit int decoded value for .
319 | ///
320 | internal static int GetBase32Value(this char base32Char)
321 | {
322 | int value;
323 | if (base32Char >= decodeMapLength ||
324 | (value = decodeMap[base32Char]) == defaultValue ||
325 | !(minValue <= value && value <= maxValue))
326 | {
327 | throw new ArgumentOutOfRangeException(nameof(base32Char), base32Char, null);
328 | }
329 | return value;
330 | }
331 |
332 | ///
333 | /// Returns 5-bit int decoded value for .
334 | ///
335 | internal static int GetBase32ValueIncludingCheckSymbol(this char base32Char)
336 | {
337 | int value;
338 | if (base32Char >= decodeMapLength ||
339 | (value = decodeMap[base32Char]) == defaultValue ||
340 | !(minValue <= value && value <= maxValueIncludingCheckSymbol))
341 | {
342 | throw new ArgumentOutOfRangeException(nameof(base32Char), base32Char, null);
343 | }
344 | return value;
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Base32Extension.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Base32 extensions.
7 | ///
8 | public static class Base32Extension
9 | {
10 | private static readonly Base32DouglasCrockford base32ConverterDouglasCrockford = new Base32DouglasCrockford();
11 |
12 | /// .
13 | [DebuggerStepThrough]
14 | public static string Base32Encode(this byte[] bytes)
15 | {
16 | return base32ConverterDouglasCrockford.Encode(bytes);
17 | }
18 |
19 | /// .
20 | [DebuggerStepThrough]
21 | public static byte[] Base32Decode(this string base32)
22 | {
23 | return base32ConverterDouglasCrockford.Decode(base32);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Base64Extension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// Base64 extensions.
8 | ///
9 | public static class Base64Extension
10 | {
11 | public static string Base64Encode(this byte[] bytes)
12 | {
13 | return Convert.ToBase64String(bytes);
14 | }
15 |
16 | public static byte[] Base64Decode(this string base64)
17 | {
18 | return Convert.FromBase64String(base64);
19 | }
20 |
21 | public static string Base64UrlEncode(this byte[] bytes)
22 | {
23 | var base64 = bytes.Base64Encode();
24 | return new StringBuilder(base64)
25 | .Replace('+', '-')
26 | .Replace('/', '_')
27 | .Replace("=", "")
28 | .ToString();
29 | }
30 |
31 | public static byte[] Base64UrlDecode(this string base64Url)
32 | {
33 | const int maxPad = 0b_0100;
34 | var pad = new string('=', (maxPad - (base64Url.Length & 0b_0011)) & 0b_0011);
35 | return new StringBuilder(base64Url)
36 | .Replace('-', '+')
37 | .Replace('_', '/')
38 | .Append(pad)
39 | .ToString()
40 | .Base64Decode();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/rm.Extensions/BitSet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using Ex = rm.Extensions.ExceptionHelper;
5 |
6 | namespace rm.Extensions;
7 |
8 | ///
9 | /// BitSet is a set that uses bit arithmatic to save space.
10 | ///
11 | ///
12 | /// BitSet is space efficient compared to bool[] if the set is dense and not sparse.
13 | ///
14 | /// Note: Cannot implement IEnumerable{int}.GetEnumerator() without loss of information.
15 | ///
16 | ///
17 | public class BitSet : IEnumerable, ICollection, IReadOnlyCollection
18 | {
19 | #region data members
20 |
21 | ///
22 | /// int array used as bit array for flags (32 bits for each int).
23 | ///
24 | /// 32, bytes: 4, binary: 11111 (+1), mask: 0x1f, power: 2^5
25 | internal readonly int[] flags;
26 |
27 | ///
28 | /// The max int allowed in BitSet.
29 | ///
30 | public uint Max { get; }
31 |
32 | ///
33 | /// The number of elements in BitSet.
34 | ///
35 | public int Count { get; private set; }
36 |
37 | #endregion
38 |
39 | #region ctors
40 |
41 | ///
42 | /// Creates a BitSet for 0 to max, both inclusive.
43 | ///
44 | /// Max (inclusive), including 0.
45 | public BitSet(uint max)
46 | {
47 | max.ThrowIfArgumentOutOfRange(nameof(max));
48 | Max = max;
49 | int capacity = (int)((max >> 5) + 1);
50 | flags = new int[capacity];
51 | }
52 |
53 | ///
54 | /// Creates a BitSet for 0 to max, both inclusive.
55 | ///
56 | /// Max (inclusive), including 0.
57 | public BitSet(int max)
58 | : this((uint)(max.ThrowIfArgumentOutOfRange(nameof(max))))
59 | { }
60 |
61 | #endregion
62 |
63 | #region methods
64 |
65 | ///
66 | /// Returns index and offset for .
67 | ///
68 | private (uint, int) GetIndexOffset(uint n)
69 | {
70 | var index = (n >> 5);
71 | var offset = (int)(n & 0b1_1111);
72 | return (index, offset);
73 | }
74 |
75 | ///
76 | /// Returns true if BitSet contains .
77 | ///
78 | public bool Has(uint n)
79 | {
80 | n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
81 | (uint index, int offset) = GetIndexOffset(n);
82 | return Has(index, offset);
83 | }
84 |
85 | ///
86 | /// Returns true if BitSet has the flags[]'s
87 | /// bit set.
88 | ///
89 | private bool Has(uint index, int offset)
90 | {
91 | return ((flags[index] >> offset) & 1) == 1;
92 | }
93 |
94 | ///
95 | /// Returns true if BitSet contains .
96 | ///
97 | public bool Has(int n)
98 | {
99 | Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
100 | return Has((uint)n);
101 | }
102 |
103 | ///
104 | /// Adds .
105 | ///
106 | public void Add(uint n)
107 | {
108 | n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
109 | (uint index, int offset) = GetIndexOffset(n);
110 | if (Has(index, offset))
111 | {
112 | return;
113 | }
114 | flags[index] |= (1 << offset);
115 | Count++;
116 | }
117 |
118 | ///
119 | /// Adds .
120 | ///
121 | public void Add(int n)
122 | {
123 | Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
124 | Add((uint)n);
125 | }
126 |
127 | ///
128 | /// Removes .
129 | ///
130 | /// Returns true if removed else false.
131 | public bool Remove(uint n)
132 | {
133 | n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
134 | (uint index, int offset) = GetIndexOffset(n);
135 | if (!Has(index, offset))
136 | {
137 | return false;
138 | }
139 | flags[index] &= ~(1 << offset);
140 | Count--;
141 | return true;
142 | }
143 |
144 | ///
145 | /// Removes .
146 | ///
147 | /// Returns true if removed else false.
148 | public bool Remove(int n)
149 | {
150 | Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
151 | return Remove((uint)n);
152 | }
153 |
154 | ///
155 | /// Toggles .
156 | ///
157 | public void Toggle(uint n)
158 | {
159 | n.ThrowIfArgumentOutOfRange(nameof(n), maxRange: Max);
160 | (uint index, int offset) = GetIndexOffset(n);
161 | flags[index] ^= (1 << offset);
162 | Count += Has(index, offset) ? +1 : -1;
163 | }
164 |
165 | ///
166 | /// Toggles .
167 | ///
168 | public void Toggle(int n)
169 | {
170 | Ex.ThrowIfArgumentOutOfRange(!(0 <= n && n <= Max), nameof(n));
171 | Toggle((uint)n);
172 | }
173 |
174 | ///
175 | /// Clears the BitSet.
176 | ///
177 | /// This is expensive.
178 | public void Clear()
179 | {
180 | for (int i = 0; i < flags.Length; i++)
181 | {
182 | flags[i] = 0;
183 | }
184 | Count = 0;
185 | }
186 |
187 | #region IEnumerable methods
188 |
189 | public IEnumerator GetEnumerator()
190 | {
191 | var ycount = 0;
192 | for (uint i = 0; i <= Max; i++)
193 | {
194 | if (ycount == Count)
195 | {
196 | yield break;
197 | }
198 | if (Has(i))
199 | {
200 | yield return i;
201 | ycount++;
202 | }
203 | }
204 | }
205 |
206 | IEnumerator IEnumerable.GetEnumerator()
207 | {
208 | return GetEnumerator();
209 | }
210 |
211 | // Note: Cannot implement IEnumerable.GetEnumerator() without loss of information
212 |
213 | #endregion
214 |
215 | #region ICollection methods
216 |
217 | public bool Contains(uint n)
218 | {
219 | return Has(n);
220 | }
221 |
222 | public bool Contains(int n)
223 | {
224 | return Has((uint)n);
225 | }
226 |
227 | public bool IsReadOnly => false;
228 |
229 | [Obsolete("Not implemented", true)]
230 | public void CopyTo(uint[] array, int arrayIndex)
231 | {
232 | throw new NotImplementedException();
233 | }
234 |
235 | #endregion
236 |
237 | #endregion
238 | }
239 |
--------------------------------------------------------------------------------
/src/rm.Extensions/CharExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Char extensions.
7 | ///
8 | public static class CharExtension
9 | {
10 | ///
11 | public static bool IsDigit(this char c)
12 | {
13 | return char.IsDigit(c);
14 | }
15 |
16 | ///
17 | public static bool IsLetter(this char c)
18 | {
19 | return char.IsLetter(c);
20 | }
21 |
22 | ///
23 | public static bool IsLetterOrDigit(this char c)
24 | {
25 | return char.IsLetterOrDigit(c);
26 | }
27 |
28 | ///
29 | public static bool IsNumber(this char c)
30 | {
31 | return char.IsNumber(c);
32 | }
33 |
34 | ///
35 | public static bool IsWhiteSpace(this char c)
36 | {
37 | return char.IsWhiteSpace(c);
38 | }
39 |
40 | ///
41 | public static bool IsPunctuation(this char c)
42 | {
43 | return char.IsPunctuation(c);
44 | }
45 |
46 | ///
47 | public static bool IsSymbol(this char c)
48 | {
49 | return char.IsSymbol(c);
50 | }
51 |
52 | ///
53 | public static bool IsControl(this char c)
54 | {
55 | return char.IsControl(c);
56 | }
57 |
58 | ///
59 | public static bool IsSeparator(this char c)
60 | {
61 | return char.IsSeparator(c);
62 | }
63 |
64 | ///
65 | public static bool IsSurrogate(this char c)
66 | {
67 | return char.IsSurrogate(c);
68 | }
69 |
70 | ///
71 | public static bool IsHighSurrogate(this char c)
72 | {
73 | return char.IsHighSurrogate(c);
74 | }
75 |
76 | ///
77 | public static bool IsLowSurrogate(this char c)
78 | {
79 | return char.IsLowSurrogate(c);
80 | }
81 |
82 | ///
83 | /// Returns true for all characters below or equal U+00ff, which is ASCII + Latin-1 Supplement.
84 | ///
85 | public static bool IsLatin1(this char c)
86 | {
87 | return (uint)c <= '\x00ff';
88 | }
89 |
90 | ///
91 | /// Returns true for all characters below or equal U+007f, which is ASCII.
92 | ///
93 | public static bool IsAscii(this char c)
94 | {
95 | return (uint)c <= '\x007f';
96 | }
97 |
98 | ///
99 | public static bool IsUpper(this char c)
100 | {
101 | return char.IsUpper(c);
102 | }
103 |
104 | ///
105 | public static bool IsLower(this char c)
106 | {
107 | return char.IsLower(c);
108 | }
109 |
110 | ///
111 | public static char ToUpper(this char c)
112 | {
113 | return char.ToUpper(c);
114 | }
115 |
116 | ///
117 | public static char ToUpper(this char c, CultureInfo cultureInfo)
118 | {
119 | return char.ToUpper(c, cultureInfo);
120 | }
121 |
122 | ///
123 | public static char ToUpperInvariant(this char c)
124 | {
125 | return char.ToUpperInvariant(c);
126 | }
127 |
128 | ///
129 | public static char ToLower(this char c)
130 | {
131 | return char.ToLower(c);
132 | }
133 |
134 | ///
135 | public static char ToLower(this char c, CultureInfo cultureInfo)
136 | {
137 | return char.ToLower(c, cultureInfo);
138 | }
139 |
140 | ///
141 | public static char ToLowerInvariant(this char c)
142 | {
143 | return char.ToLowerInvariant(c);
144 | }
145 |
146 | ///
147 | public static UnicodeCategory GetUnicodeCategory(this char c)
148 | {
149 | return char.GetUnicodeCategory(c);
150 | }
151 |
152 | ///
153 | public static double GetNumericValue(char c)
154 | {
155 | return char.GetNumericValue(c);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/rm.Extensions/CircularQueue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Defines circular queue methods.
9 | ///
10 | public interface ICircularQueue
11 | {
12 | ///
13 | /// Enqueues into queue.
14 | ///
15 | void Enqueue(T x);
16 |
17 | ///
18 | /// Dequeues head from queue.
19 | ///
20 | T Dequeue();
21 |
22 | ///
23 | /// Peeks head of queue.
24 | ///
25 | T Peek();
26 |
27 | ///
28 | /// Peeks tail of queue.
29 | ///
30 | T PeekTail();
31 |
32 | ///
33 | /// Returns true if queue is empty.
34 | ///
35 | bool IsEmpty();
36 |
37 | ///
38 | /// Returns count of queue.
39 | ///
40 | int Count();
41 |
42 | ///
43 | /// Returns capacity of queue.
44 | ///
45 | int Capacity();
46 |
47 | ///
48 | /// Clears queue.
49 | ///
50 | void Clear();
51 | }
52 |
53 | ///
54 | /// Circular queue.
55 | ///
56 | /// Uses array as backing store.
57 | public class CircularQueue : ICircularQueue, IEnumerable
58 | {
59 | #region members
60 |
61 | private readonly T[] store;
62 | private readonly int capacity;
63 | private int count;
64 | private int head;
65 | private int tail;
66 |
67 | #endregion
68 |
69 | #region ctors
70 |
71 | public CircularQueue(int capacity = 8)
72 | {
73 | capacity.ThrowIfArgumentOutOfRange(minRange: 1, exMessage: nameof(capacity));
74 | store = new T[capacity];
75 | this.capacity = capacity;
76 | head = tail = count = 0;
77 | }
78 |
79 | #endregion
80 |
81 | #region ICircularQueue methods
82 |
83 | ///
84 | /// Enqueues into queue.
85 | ///
86 | public void Enqueue(T x)
87 | {
88 | store[tail] = x;
89 | tail = WrapIndex(tail + 1);
90 | if (count == capacity)
91 | {
92 | head = tail;
93 | }
94 | if (count < capacity)
95 | {
96 | count++;
97 | }
98 | }
99 |
100 | ///
101 | /// Dequeues head from queue.
102 | ///
103 | public T Dequeue()
104 | {
105 | if (IsEmpty())
106 | {
107 | throw new InvalidOperationException("Queue is empty.");
108 | }
109 | var item = store[head];
110 | head = WrapIndex(head + 1);
111 | count--;
112 | return item;
113 | }
114 |
115 | ///
116 | /// Peeks head of queue.
117 | ///
118 | public T Peek()
119 | {
120 | if (IsEmpty())
121 | {
122 | throw new InvalidOperationException("Queue is empty.");
123 | }
124 | return store[head];
125 | }
126 |
127 | ///
128 | /// Peeks tail of queue.
129 | ///
130 | public T PeekTail()
131 | {
132 | if (IsEmpty())
133 | {
134 | throw new InvalidOperationException("Queue is empty.");
135 | }
136 | return store[WrapIndex(tail - 1)];
137 | }
138 |
139 | ///
140 | /// Returns true if queue is empty.
141 | ///
142 | public bool IsEmpty()
143 | {
144 | return count == 0;
145 | }
146 |
147 | ///
148 | /// Returns count of queue.
149 | ///
150 | public int Count()
151 | {
152 | return count;
153 | }
154 |
155 | ///
156 | /// Returns capacity of queue.
157 | ///
158 | public int Capacity()
159 | {
160 | return capacity;
161 | }
162 |
163 | ///
164 | /// Clears queue.
165 | ///
166 | public void Clear()
167 | {
168 | head = tail = count = 0;
169 | }
170 |
171 | #endregion
172 |
173 | private int WrapIndex(int index)
174 | {
175 | var wIndex = index % capacity;
176 | if (wIndex < 0)
177 | {
178 | wIndex += capacity;
179 | }
180 | return wIndex;
181 | }
182 |
183 | #region IEnumerable methods
184 |
185 | public IEnumerator GetEnumerator()
186 | {
187 | var index = head;
188 | for (int c = 0; c < count; c++)
189 | {
190 | yield return store[index];
191 | index = WrapIndex(index + 1);
192 | }
193 | }
194 |
195 | IEnumerator IEnumerable.GetEnumerator()
196 | {
197 | return GetEnumerator();
198 | }
199 |
200 | #endregion
201 | }
202 |
--------------------------------------------------------------------------------
/src/rm.Extensions/CircularStack.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Defines circular stack methods.
9 | ///
10 | public interface ICircularStack
11 | {
12 | ///
13 | /// Pushes into stack.
14 | ///
15 | void Push(T x);
16 |
17 | ///
18 | /// Pops top from stack.
19 | ///
20 | T Pop();
21 |
22 | ///
23 | /// Peeks top of stack.
24 | ///
25 | T Peek();
26 |
27 | ///
28 | /// Peeks bottom of stack.
29 | ///
30 | T PeekBottom();
31 |
32 | ///
33 | /// Returns true if stack is empty.
34 | ///
35 | bool IsEmpty();
36 |
37 | ///
38 | /// Returns count of stack.
39 | ///
40 | int Count();
41 |
42 | ///
43 | /// Returns capacity of stack.
44 | ///
45 | int Capacity();
46 |
47 | ///
48 | /// Clears stack.
49 | ///
50 | void Clear();
51 | }
52 |
53 | ///
54 | /// Circular stack.
55 | ///
56 | /// Uses array as backing store.
57 | public class CircularStack : ICircularStack, IEnumerable
58 | {
59 | #region members
60 |
61 | private readonly T[] store;
62 | private readonly int capacity;
63 | private int count;
64 | private int top;
65 |
66 | #endregion
67 |
68 | #region ctors
69 |
70 | public CircularStack(int capacity = 8)
71 | {
72 | capacity.ThrowIfArgumentOutOfRange(minRange: 1, exMessage: nameof(capacity));
73 | store = new T[capacity];
74 | this.capacity = capacity;
75 | top = count = 0;
76 | }
77 |
78 | #endregion
79 |
80 | #region ICircularStack methods
81 |
82 | ///
83 | /// Pushes into stack.
84 | ///
85 | public void Push(T x)
86 | {
87 | store[top] = x;
88 | top = WrapIndex(top + 1);
89 | if (count < capacity)
90 | {
91 | count++;
92 | }
93 | }
94 |
95 | ///
96 | /// Pops top from stack.
97 | ///
98 | public T Pop()
99 | {
100 | if (IsEmpty())
101 | {
102 | throw new InvalidOperationException("Stack is empty.");
103 | }
104 | top = WrapIndex(top - 1);
105 | var item = store[top];
106 | count--;
107 | return item;
108 | }
109 |
110 | ///
111 | /// Peeks top of stack.
112 | ///
113 | public T Peek()
114 | {
115 | if (IsEmpty())
116 | {
117 | throw new InvalidOperationException("Stack is empty.");
118 | }
119 | return store[WrapIndex(top - 1)];
120 | }
121 |
122 | ///
123 | /// Peeks bottom of stack.
124 | ///
125 | public T PeekBottom()
126 | {
127 | if (IsEmpty())
128 | {
129 | throw new InvalidOperationException("Stack is empty.");
130 | }
131 | return store[WrapIndex(top - count)];
132 | }
133 |
134 | ///
135 | /// Returns true if stack is empty.
136 | ///
137 | public bool IsEmpty()
138 | {
139 | return count == 0;
140 | }
141 |
142 | ///
143 | /// Returns count of stack.
144 | ///
145 | public int Count()
146 | {
147 | return count;
148 | }
149 |
150 | ///
151 | /// Returns capacity of stack.
152 | ///
153 | public int Capacity()
154 | {
155 | return capacity;
156 | }
157 |
158 | ///
159 | /// Clears stack.
160 | ///
161 | public void Clear()
162 | {
163 | top = count = 0;
164 | }
165 |
166 | #endregion
167 |
168 | private int WrapIndex(int index)
169 | {
170 | var wIndex = index % capacity;
171 | if (wIndex < 0)
172 | {
173 | wIndex += capacity;
174 | }
175 | return wIndex;
176 | }
177 |
178 | #region IEnumerable methods
179 |
180 | public IEnumerator GetEnumerator()
181 | {
182 | var index = top;
183 | for (int c = 0; c < count; c++)
184 | {
185 | index = WrapIndex(index - 1);
186 | yield return store[index];
187 | }
188 | }
189 |
190 | IEnumerator IEnumerable.GetEnumerator()
191 | {
192 | return GetEnumerator();
193 | }
194 |
195 | #endregion
196 | }
197 |
--------------------------------------------------------------------------------
/src/rm.Extensions/DateTimeExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.SqlTypes;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// DateTime extensions.
8 | ///
9 | public static class DateTimeExtension
10 | {
11 | ///
12 | /// UTC date format string.
13 | ///
14 | public static readonly string UtcDateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'";
15 |
16 | ///
17 | /// Min value for Sql datetime to save in sql db.
18 | ///
19 | public static readonly DateTime SqlDateTimeMinUtc = SqlDateTime.MinValue.Value.AsUtcKind();
20 |
21 | ///
22 | /// Gets the UTC datetime format for the date.
23 | ///
24 | ///
25 | ///
26 | public static string ToUtcFormatString(this DateTime date)
27 | {
28 | return date.ToUniversalTime().ToString(UtcDateFormat);
29 | }
30 |
31 | ///
32 | /// Gets the min value for Sql datetime.
33 | ///
34 | public static DateTime ToSqlDateTimeMinUtc(this DateTime date)
35 | {
36 | return SqlDateTimeMinUtc;
37 | }
38 |
39 | ///
40 | /// Specifies datetime's kind as UTC.
41 | ///
42 | ///
43 | ///
44 | ///
45 | /// Date read from db or parsed from string has its Kind as Unspecified.
46 | /// Specifying its kind as UTC is needed if date is expected to be UTC.
47 | /// ToUniversalTime() assumes that the kind is local while converting it and is undesirable.
48 | ///
49 | public static DateTime AsUtcKind(this DateTime datetime)
50 | {
51 | return DateTime.SpecifyKind(datetime, DateTimeKind.Utc);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/rm.Extensions/DecimalExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Decimal extensions.
7 | ///
8 | public static class DecimalExtension
9 | {
10 | ///
11 | /// Truncates decimal to .
12 | ///
13 | public static decimal TruncateTo(this decimal n, uint digits)
14 | {
15 | // note: Math.Round(n, digits, ...) doesn't work
16 | var wholePart = decimal.Truncate(n);
17 |
18 | var decimalPart = n - wholePart;
19 | var factor = checked(Math.Pow(10, digits));
20 | var decimalPartTruncated = Math.Truncate(decimalPart * (decimal)factor) / (decimal)factor;
21 |
22 | return wholePart + decimalPartTruncated;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Deque.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using rm.Extensions.Deque;
5 | using Ex = rm.Extensions.ExceptionHelper;
6 |
7 | namespace rm.Extensions;
8 |
9 | ///
10 | /// Defines deque methods.
11 | ///
12 | interface IDeque : IEnumerable
13 | {
14 | ///
15 | /// Enqueues into deque.
16 | ///
17 | Node Enqueue(T x);
18 |
19 | ///
20 | /// Dequeues head from deque.
21 | ///
22 | T Dequeue();
23 |
24 | ///
25 | /// Peeks head of deque.
26 | ///
27 | T Peek();
28 |
29 | ///
30 | /// Peeks tail of deque.
31 | ///
32 | T PeekTail();
33 |
34 | ///
35 | /// Deletes given from deque.
36 | ///
37 | void Delete(Node node);
38 |
39 | ///
40 | /// Returns true if deque is empty.
41 | ///
42 | bool IsEmpty();
43 |
44 | ///
45 | /// Returns count of deque.
46 | ///
47 | int Count();
48 |
49 | ///
50 | /// Returns long count of deque.
51 | ///
52 | long LongCount();
53 |
54 | ///
55 | /// Clears deque.
56 | ///
57 | void Clear();
58 |
59 | ///
60 | /// Inserts before given .
61 | ///
62 | Node InsertBefore(Node node, T x);
63 |
64 | ///
65 | /// Inserts after given .
66 | ///
67 | Node InsertAfter(Node node, T x);
68 |
69 | ///
70 | /// Inserts as head.
71 | ///
72 | void InsertHead(Node node);
73 |
74 | ///
75 | /// Inserts as tail.
76 | ///
77 | void InsertTail(Node node);
78 |
79 | ///
80 | /// Make as head.
81 | ///
82 | void MakeHead(Node node);
83 |
84 | ///
85 | /// Make as tail.
86 | ///
87 | void MakeTail(Node node);
88 |
89 | // HACK: Either encapsulate or refactor with Remove(T x).
90 | ///
91 | /// Iterates over all nodes.
92 | ///
93 | IEnumerable> Nodes();
94 | }
95 |
96 | ///
97 | /// Deque.
98 | ///
99 | ///
100 | /// Uses linked list as backing store.
101 | ///
102 | /// All methods are O(1) time unless noted.
103 | ///
104 | public class Deque : IDeque
105 | {
106 | #region members
107 |
108 | private Node head;
109 | private Node tail;
110 | private long count;
111 |
112 | #endregion
113 |
114 | #region ctors
115 |
116 | public Deque()
117 | { }
118 |
119 | #endregion
120 |
121 | #region IDeque methods
122 |
123 | ///
124 | /// Enqueues into deque.
125 | ///
126 | /// The node for .
127 | public Node Enqueue(T x)
128 | {
129 | var node = new Node(x, this);
130 | if (IsEmpty())
131 | {
132 | head = tail = node;
133 | }
134 | else
135 | {
136 | tail.next = node;
137 | node.prev = tail;
138 | tail = node;
139 | }
140 | count++;
141 | return node;
142 | }
143 |
144 | ///
145 | /// Dequeues head from deque.
146 | ///
147 | public T Dequeue()
148 | {
149 | Ex.ThrowIfEmpty(IsEmpty(), "Deque is empty.");
150 | count--;
151 | var node = head;
152 | node.owner = null;
153 | if (head == tail)
154 | {
155 | head = tail = null;
156 | }
157 | else
158 | {
159 | head = head.next;
160 | node.next = head.prev = null;
161 | }
162 | return node.Value;
163 | }
164 |
165 | ///
166 | /// Peeks head of deque.
167 | ///
168 | public T Peek()
169 | {
170 | Ex.ThrowIfEmpty(IsEmpty(), "Deque is empty.");
171 | return head.Value;
172 | }
173 |
174 | ///
175 | /// Peeks tail of deque.
176 | ///
177 | public T PeekTail()
178 | {
179 | Ex.ThrowIfEmpty(IsEmpty(), "Deque is empty.");
180 | return tail.Value;
181 | }
182 |
183 | ///
184 | /// Deletes given from deque.
185 | ///
186 | public void Delete(Node node)
187 | {
188 | node.ThrowIfArgumentNull(nameof(node));
189 | if (node.owner != this)
190 | {
191 | throw new InvalidOperationException("Node does not belong to the deque.");
192 | }
193 | Ex.ThrowIfEmpty(IsEmpty(), "Deque is empty.");
194 | count--;
195 | node.owner = null;
196 | if (node == head && node == tail)
197 | {
198 | head = tail = null;
199 | }
200 | else if (node == head)
201 | {
202 | head = head.next;
203 | node.next = head.prev = null;
204 | }
205 | else if (node == tail)
206 | {
207 | tail = tail.prev;
208 | tail.next = node.prev = null;
209 | }
210 | else
211 | {
212 | node.prev.next = node.next;
213 | node.next.prev = node.prev;
214 | node.next = node.prev = null;
215 | }
216 | }
217 |
218 | ///
219 | /// Returns true if deque is empty.
220 | ///
221 | public bool IsEmpty()
222 | {
223 | return count == 0;
224 | }
225 |
226 | ///
227 | /// Returns count of deque.
228 | ///
229 | public int Count()
230 | {
231 | return checked((int)count);
232 | }
233 |
234 | ///
235 | /// Returns long count of deque.
236 | ///
237 | public long LongCount()
238 | {
239 | return count;
240 | }
241 |
242 | ///
243 | /// Clears deque.
244 | ///
245 | public void Clear()
246 | {
247 | head = tail = null;
248 | count = 0;
249 | }
250 |
251 | ///
252 | /// Inserts before given .
253 | ///
254 | /// The node for .
255 | public Node InsertBefore(Node node, T x)
256 | {
257 | node.ThrowIfArgumentNull(nameof(node));
258 | if (node.owner != this)
259 | {
260 | throw new InvalidOperationException("Node does not belong to the deque.");
261 | }
262 | count++;
263 | var prev = node.prev;
264 | var xnode = new Node(x, this);
265 | var next = node;
266 | xnode.next = next;
267 | next.prev = xnode;
268 | if (next == head)
269 | {
270 | head = xnode;
271 | }
272 | else
273 | {
274 | prev.next = xnode;
275 | xnode.prev = prev;
276 | }
277 | return xnode;
278 | }
279 |
280 | ///
281 | /// Inserts after given .
282 | ///
283 | /// The node for .
284 | public Node InsertAfter(Node node, T x)
285 | {
286 | node.ThrowIfArgumentNull(nameof(node));
287 | if (node.owner != this)
288 | {
289 | throw new InvalidOperationException("Node does not belong to the deque.");
290 | }
291 | count++;
292 | var prev = node;
293 | var xnode = new Node(x, this);
294 | var next = node.next;
295 | xnode.prev = prev;
296 | prev.next = xnode;
297 | if (prev == tail)
298 | {
299 | tail = xnode;
300 | }
301 | else
302 | {
303 | xnode.next = next;
304 | next.prev = xnode;
305 | }
306 | return xnode;
307 | }
308 |
309 | ///
310 | /// Inserts as head.
311 | ///
312 | public void InsertHead(Node node)
313 | {
314 | node.ThrowIfArgumentNull(nameof(node));
315 | if (node.owner != null)
316 | {
317 | throw new InvalidOperationException("Node is already in deque.");
318 | }
319 | node.owner = this;
320 | if (IsEmpty())
321 | {
322 | head = tail = node;
323 | }
324 | else
325 | {
326 | node.next = head;
327 | head.prev = node;
328 | head = node;
329 | }
330 | count++;
331 | }
332 |
333 | ///
334 | /// Inserts as tail.
335 | ///
336 | public void InsertTail(Node node)
337 | {
338 | node.ThrowIfArgumentNull(nameof(node));
339 | if (node.owner != null)
340 | {
341 | throw new InvalidOperationException("Node is already in deque.");
342 | }
343 | node.owner = this;
344 | if (IsEmpty())
345 | {
346 | head = tail = node;
347 | }
348 | else
349 | {
350 | tail.next = node;
351 | node.prev = tail;
352 | tail = node;
353 | }
354 | count++;
355 | }
356 |
357 | ///
358 | /// Make as head.
359 | ///
360 | public void MakeHead(Node node)
361 | {
362 | node.ThrowIfArgumentNull(nameof(node));
363 | if (node.owner != this)
364 | {
365 | throw new InvalidOperationException("Node does not belong to the deque.");
366 | }
367 | if (node == head)
368 | {
369 | return;
370 | }
371 | if (node == tail)
372 | {
373 | tail = node.prev;
374 | tail.next = node.prev = null;
375 | }
376 | else
377 | {
378 | node.prev.next = node.next;
379 | node.next.prev = node.prev;
380 | node.next = node.prev = null;
381 | }
382 | head.prev = node;
383 | node.next = head;
384 | head = node;
385 | }
386 |
387 | ///
388 | /// Make as tail.
389 | ///
390 | public void MakeTail(Node node)
391 | {
392 | node.ThrowIfArgumentNull(nameof(node));
393 | if (node.owner != this)
394 | {
395 | throw new InvalidOperationException("Node does not belong to the deque.");
396 | }
397 | if (node == tail)
398 | {
399 | return;
400 | }
401 | if (node == head)
402 | {
403 | head = node.next;
404 | node.next = head.prev = null;
405 | }
406 | else
407 | {
408 | node.prev.next = node.next;
409 | node.next.prev = node.prev;
410 | node.next = node.prev = null;
411 | }
412 | tail.next = node;
413 | node.prev = tail;
414 | tail = node;
415 | }
416 |
417 | ///
418 | /// Iterates over all nodes.
419 | ///
420 | public IEnumerable> Nodes()
421 | {
422 | var node = head;
423 | while (node != null)
424 | {
425 | yield return node;
426 | node = node.next;
427 | }
428 | }
429 |
430 | #endregion
431 |
432 | #region IEnumerable methods
433 |
434 | public IEnumerator GetEnumerator()
435 | {
436 | var node = head;
437 | while (node != null)
438 | {
439 | yield return node.Value;
440 | node = node.next;
441 | }
442 | }
443 |
444 | IEnumerator IEnumerable.GetEnumerator()
445 | {
446 | return GetEnumerator();
447 | }
448 |
449 | #endregion
450 | }
451 |
--------------------------------------------------------------------------------
/src/rm.Extensions/DequeNode.cs:
--------------------------------------------------------------------------------
1 | namespace rm.Extensions.Deque;
2 |
3 | ///
4 | /// Deque node.
5 | ///
6 | public class Node
7 | {
8 | public readonly T Value;
9 | internal Node prev;
10 | internal Node next;
11 | internal Deque owner;
12 |
13 | internal Node(T value, Deque owner)
14 | {
15 | this.Value = value;
16 | this.owner = owner;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/rm.Extensions/DictionaryExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// Dictionary extensions.
8 | ///
9 | public static class DictionaryExtension
10 | {
11 | ///
12 | /// Returns value for key if exists or default{}.
13 | ///
14 | public static TValue GetValueOrDefault(this IDictionary dictionary,
15 | TKey key)
16 | {
17 | return dictionary.GetValueOrDefault(key, default(TValue));
18 | }
19 |
20 | ///
21 | /// Returns value for key if exists or .
22 | ///
23 | public static TValue GetValueOrDefault(this IDictionary dictionary,
24 | TKey key, TValue defaultValue)
25 | {
26 | TValue value;
27 | if (dictionary.TryGetValue(key, out value))
28 | {
29 | return value;
30 | }
31 | return defaultValue;
32 | }
33 |
34 | ///
35 | /// Returns the as read-only.
36 | ///
37 | public static IDictionary AsReadOnly(this IDictionary dictionary)
38 | {
39 | return new ReadOnlyDictionary(dictionary);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/rm.Extensions/EmptyException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.Serialization;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// The exception that is thrown when the object is empty.
8 | ///
9 | [Serializable]
10 | public class EmptyException : Exception
11 | {
12 | public EmptyException() { }
13 | public EmptyException(string message)
14 | : base(message) { }
15 | public EmptyException(string message, Exception inner)
16 | : base(message, inner) { }
17 | protected EmptyException(SerializationInfo info, StreamingContext context)
18 | : base(info, context) { }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rm.Extensions/EncodingExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Encoding extensions.
7 | ///
8 | public static class EncodingExtension
9 | {
10 | public static byte[] ToUtf8Bytes(this string s)
11 | {
12 | return Encoding.UTF8.GetBytes(s);
13 | }
14 |
15 | public static string ToUtf8String(this byte[] utf8Bytes)
16 | {
17 | return Encoding.UTF8.GetString(utf8Bytes);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/rm.Extensions/EnumExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Enum extensions.
9 | ///
10 | public static class EnumExtension
11 | {
12 | ///
13 | /// TryParses the string to enum of type .
14 | ///
15 | public static bool TryParse(this string name, out T result, bool ignoreCase = false)
16 | where T : struct
17 | {
18 | return Enum.TryParse(name, ignoreCase, out result);
19 | }
20 |
21 | ///
22 | /// Parses the string to enum of type .
23 | ///
24 | public static T Parse(this string name, bool ignoreCase = false)
25 | where T : struct
26 | {
27 | return (T)Enum.Parse(typeof(T), name, ignoreCase);
28 | }
29 |
30 | ///
31 | /// Gets values for type enum.
32 | ///
33 | public static T[] GetEnumValues()
34 | where T : struct
35 | {
36 | return EnumInternal.ValueToNameMap.Select(x => x.Key).ToArray();
37 | }
38 |
39 | ///
40 | /// Gets names (strings) for type enum.
41 | ///
42 | public static string[] GetEnumNames()
43 | where T : struct
44 | {
45 | return EnumInternal.NameToValueMap.Select(x => x.Key).ToArray();
46 | }
47 |
48 | ///
49 | /// Gets enum name (string) -> description (string) map for
50 | /// type enum.
51 | ///
52 | public static IDictionary GetEnumNameToDescriptionMap()
53 | where T : struct
54 | {
55 | return EnumInternal.ValueToDescriptionMap
56 | .ToDictionary(x => x.Key.GetEnumName(), x => x.Value).AsReadOnly();
57 | }
58 |
59 | ///
60 | /// Gets the name (string) for the enum value or throws exception if not exists.
61 | ///
62 | public static string GetEnumName(this T enumValue)
63 | where T : struct
64 | {
65 | string enumName;
66 | if (EnumInternal.ValueToNameMap.TryGetValue(enumValue, out enumName))
67 | {
68 | return enumName;
69 | }
70 | throw new UnsupportedEnumValueException(enumValue);
71 | }
72 |
73 | ///
74 | /// Gets enum name (string) for description or throws exception if not exists.
75 | ///
76 | public static string GetEnumNameFromDescription(this string description)
77 | where T : struct
78 | {
79 | T enumValue;
80 | if (EnumInternal.DescriptionToValueMap.TryGetValue(description, out enumValue))
81 | {
82 | return enumValue.GetEnumName();
83 | }
84 | throw new UnsupportedEnumValueException(
85 | $"{nameof(description)} {description} of enum {typeof(T).Name} is not supported.");
86 | }
87 |
88 | ///
89 | /// Gets the value for the enum name (string) or throws exception if not exists.
90 | ///
91 | public static T GetEnumValue(this string name)
92 | where T : struct
93 | {
94 | T enumValue;
95 | if (EnumInternal.NameToValueMap.TryGetValue(name, out enumValue))
96 | {
97 | return enumValue;
98 | }
99 | throw new UnsupportedEnumValueException(
100 | $"{nameof(name)} {name} of enum {typeof(T).Name} is not supported.");
101 | }
102 |
103 | ///
104 | /// Gets the value for the enum description (string) or throws exception if not exists.
105 | ///
106 | public static T GetEnumValueFromDescription(this string description)
107 | where T : struct
108 | {
109 | T enumValue;
110 | if (EnumInternal.DescriptionToValueMap.TryGetValue(description, out enumValue))
111 | {
112 | return enumValue;
113 | }
114 | throw new UnsupportedEnumValueException(
115 | $"{nameof(description)} {description} of enum {typeof(T).Name} is not supported.");
116 | }
117 |
118 | ///
119 | /// Gets the description (DescriptionAttribute) for the enum value
120 | /// or throws exception if not exists.
121 | ///
122 | public static string GetDescription(this T enumValue)
123 | where T : struct
124 | {
125 | string description;
126 | if (EnumInternal.ValueToDescriptionMap.TryGetValue(enumValue, out description))
127 | {
128 | return description;
129 | }
130 | throw new UnsupportedEnumValueException(enumValue);
131 | }
132 |
133 | ///
134 | /// Generic method for .
135 | ///
136 | public static bool IsDefined(this int value)
137 | where T : struct
138 | {
139 | // avoid Enum.IsDefined(typeof(T), value) due to boxing
140 | return EnumInternal.ValueIntToValueMap.ContainsKey(value);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/rm.Extensions/EnumInternal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace rm.Extensions;
8 |
9 | ///
10 | /// Enum helper class.
11 | ///
12 | /// https://code.google.com/p/unconstrained-melody/source/browse/trunk/UnconstrainedMelody/EnumInternals.cs
13 | public static class EnumInternal
14 | where T : struct
15 | {
16 | ///
17 | /// enum name -> enum value
18 | ///
19 | internal static readonly IDictionary NameToValueMap =
20 | new Dictionary();
21 |
22 | ///
23 | /// enum value -> enum name
24 | ///
25 | internal static readonly IDictionary ValueToNameMap =
26 | new Dictionary();
27 |
28 | ///
29 | /// enum value -> description
30 | ///
31 | internal static readonly IDictionary ValueToDescriptionMap =
32 | new Dictionary();
33 |
34 | ///
35 | /// enum description -> value
36 | ///
37 | internal static readonly IDictionary DescriptionToValueMap =
38 | new Dictionary();
39 |
40 | ///
41 | /// value int -> enum value
42 | ///
43 | internal static readonly IDictionary ValueIntToValueMap =
44 | new Dictionary();
45 |
46 | ///
47 | /// Builds maps.
48 | ///
49 | static EnumInternal()
50 | {
51 | foreach (T enumValue in Enum.GetValues(typeof(T)))
52 | {
53 | var enumName = Enum.GetName(typeof(T), enumValue);
54 | NameToValueMap.Add(enumName, enumValue);
55 | ValueToNameMap.Add(enumValue, enumName);
56 | var description = GetDescription(enumValue);
57 | ValueToDescriptionMap.Add(enumValue, description);
58 | DescriptionToValueMap.Add(description, enumValue);
59 | var valueInt = (int)Convert.ChangeType(enumValue, typeof(T));
60 | ValueIntToValueMap.Add(valueInt, enumValue);
61 | }
62 | }
63 |
64 | ///
65 | /// Gets description (DescriptionAttribute) for enum value or string representation if not exists.
66 | ///
67 | private static string GetDescription(T enumValue)
68 | {
69 | var field = enumValue.GetType().GetField(enumValue.ToString());
70 | var description =
71 | field.GetCustomAttributes(typeof(DescriptionAttribute), false)
72 | .Cast()
73 | .Select(x => x.Description)
74 | .SingleOrDefault();
75 | if (description.IsNullOrEmpty())
76 | {
77 | description = enumValue.ToString();
78 | }
79 | return description;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/rm.Extensions/ExceptionHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Exception helper class.
7 | ///
8 | internal class ExceptionHelper
9 | {
10 | ///
11 | /// Throws NullReferenceException if true with message.
12 | ///
13 | internal static void ThrowIfNull(bool throwEx, string exMessage)
14 | {
15 | if (throwEx)
16 | {
17 | throw new NullReferenceException(exMessage);
18 | }
19 | }
20 |
21 | ///
22 | /// Throws ArgumentNullException if true with message.
23 | ///
24 | internal static void ThrowIfArgumentNull(bool throwEx, string exMessage)
25 | {
26 | if (throwEx)
27 | {
28 | throw new ArgumentNullException(exMessage);
29 | }
30 | }
31 |
32 | ///
33 | /// Throws EmptyException if true with message.
34 | ///
35 | internal static void ThrowIfEmpty(bool throwEx, string exMessage)
36 | {
37 | if (throwEx)
38 | {
39 | throw new EmptyException(exMessage);
40 | }
41 | }
42 |
43 | ///
44 | /// Throws ArgumentOutOfRangeException if true with message.
45 | ///
46 | internal static void ThrowIfArgumentOutOfRange(bool throwEx, string exMessage)
47 | {
48 | if (throwEx)
49 | {
50 | throw new ArgumentOutOfRangeException(exMessage);
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/rm.Extensions/GenericComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// Generic class that implements IComparer{T}.
8 | ///
9 | public class GenericComparer : IComparer
10 | {
11 | private Func compare;
12 |
13 | public GenericComparer(Func compare)
14 | {
15 | compare.ThrowIfArgumentNull(nameof(compare));
16 | this.compare = compare;
17 | }
18 |
19 | #region IComparer methods
20 |
21 | public int Compare(T x, T y)
22 | {
23 | return compare(x, y);
24 | }
25 |
26 | #endregion
27 | }
28 |
--------------------------------------------------------------------------------
/src/rm.Extensions/GenericEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// Generic class that implements IEqualityComparer{T} for {TKey} key selector.
8 | ///
9 | /// http://stackoverflow.com/questions/188120/can-i-specify-my-explicit-type-comparator-inline
10 | public class GenericEqualityComparer : IEqualityComparer
11 | {
12 | private Func projection;
13 |
14 | public GenericEqualityComparer(Func projection)
15 | {
16 | projection.ThrowIfArgumentNull(nameof(projection));
17 | this.projection = projection;
18 | }
19 |
20 | #region IEqualityComparer methods
21 |
22 | public bool Equals(T x, T y)
23 | {
24 | if (x == null && y == null)
25 | {
26 | return true;
27 | }
28 | if (x == null || y == null)
29 | {
30 | return false;
31 | }
32 | // note: projection(x).Equals(projection(y)) uses object.Equals(object),
33 | // instead of TKey.Equals(Tkey), which gives incorrect results.
34 | return EqualityComparer.Default.Equals(projection(x), projection(y));
35 | }
36 |
37 | public int GetHashCode(T obj)
38 | {
39 | return projection(obj).GetHashCode();
40 | }
41 |
42 | #endregion
43 | }
44 |
45 | ///
46 | /// Helper class to create GenericEqualityComparer{T, TKey}.
47 | ///
48 | public static class GenericEqualityComparer
49 | {
50 | public static IEqualityComparer By(Func projection)
51 | {
52 | return new GenericEqualityComparer(projection);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/rm.Extensions/GraphExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Graph extensions.
7 | ///
8 | public static class GraphExtension
9 | {
10 | ///
11 | /// Returns true if graph is cyclic.
12 | ///
13 | public static bool IsCyclic(this IGraph graph)
14 | {
15 | graph.ThrowIfArgumentNull(nameof(graph));
16 | graph.Nodes.ThrowIfArgumentNull(nameof(graph.Nodes));
17 | var acyclicNodes = new HashSet();
18 | var path = new HashSet();
19 | foreach (var node in graph.Nodes)
20 | {
21 | if (IsCyclic(node, path, acyclicNodes))
22 | {
23 | return true;
24 | }
25 | }
26 | return false;
27 | }
28 |
29 | ///
30 | /// Returns true if graph node is cyclic.
31 | ///
32 | /// Graph node.
33 | /// Path from a graph node to this .
34 | /// Nodes from which a cycle does not exist.
35 | /// Returns true if graph node is cyclic.
36 | private static bool IsCyclic(IGraphNode node, ISet path, ISet acyclicNodes)
37 | {
38 | node.ThrowIfArgumentNull(nameof(node));
39 | node.Id.ThrowIfArgumentNull(nameof(node.Id));
40 | node.Neighbors.ThrowIfArgumentNull(nameof(node.Neighbors));
41 | if (acyclicNodes.Contains(node.Id))
42 | {
43 | return false;
44 | }
45 | if (path.Contains(node.Id))
46 | {
47 | return true;
48 | }
49 | path.Add(node.Id);
50 | foreach (var neighbor in node.Neighbors)
51 | {
52 | if (IsCyclic(neighbor, path, acyclicNodes))
53 | {
54 | return true;
55 | }
56 | }
57 | path.Remove(node.Id);
58 | acyclicNodes.Add(node.Id);
59 | return false;
60 | }
61 | }
62 |
63 | #region Graph interfaces
64 |
65 | ///
66 | /// Defines Graph.
67 | ///
68 | public interface IGraph
69 | {
70 | ///
71 | /// Nodes in the graph.
72 | ///
73 | IEnumerable Nodes { get; }
74 | }
75 |
76 | ///
77 | /// Defines Graph node.
78 | ///
79 | public interface IGraphNode
80 | {
81 | ///
82 | /// Node's Id.
83 | ///
84 | string Id { get; }
85 |
86 | ///
87 | /// Node's neighbors.
88 | ///
89 | IEnumerable Neighbors { get; }
90 | }
91 |
92 | #endregion
93 |
--------------------------------------------------------------------------------
/src/rm.Extensions/GuidExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Guid extensions.
7 | ///
8 | public static class GuidExtension
9 | {
10 | ///
11 | /// Returns a 16-element byte array that contains the value of this instance
12 | /// matching its string representation (endian-agnostic).
13 | ///
14 | /// See https://stackoverflow.com/questions/9195551/why-does-guid-tobytearray-order-the-bytes-the-way-it-does
15 | ///
16 | ///
17 | /// Note: The byte[] returned by will not yield
18 | /// the original Guid with ctor.
19 | ///
20 | ///
21 | public static byte[] ToByteArrayMatchingStringRepresentation(this Guid guid)
22 | {
23 | var bytes = guid.ToByteArray();
24 | TweakOrderOfGuidBytesToMatchStringRepresentation(bytes);
25 | return bytes;
26 | }
27 |
28 | ///
29 | /// Initializes a new instance of the structure by using the specified array of bytes
30 | /// matching its string representation (endian-agnostic).
31 | ///
32 | /// See https://stackoverflow.com/questions/9195551/why-does-guid-tobytearray-order-the-bytes-the-way-it-does
33 | ///
34 | ///
35 | /// Note: The Guid returned by will not yield
36 | /// the original byte[] with .
37 | ///
38 | ///
39 | public static Guid ToGuidMatchingStringRepresentation(this byte[] bytes)
40 | {
41 | _ = bytes ??
42 | throw new ArgumentNullException(nameof(bytes));
43 | if (bytes.Length != 16)
44 | {
45 | throw new ArgumentException("Length should be 16.", nameof(bytes));
46 | }
47 | TweakOrderOfGuidBytesToMatchStringRepresentation(bytes);
48 | return new Guid(bytes);
49 | }
50 |
51 | ///
52 | /// Tweaks the order of in-place as per endianness.
53 | ///
54 | ///
55 | /// This method should be kept private to avoid confusion as it tweaks in-place.
56 | ///
57 | private static void TweakOrderOfGuidBytesToMatchStringRepresentation(byte[] guidBytes)
58 | {
59 | if (BitConverter.IsLittleEndian)
60 | {
61 | Array.Reverse(guidBytes, 0, 4);
62 | Array.Reverse(guidBytes, 4, 2);
63 | Array.Reverse(guidBytes, 6, 2);
64 | }
65 | }
66 |
67 | ///
68 | /// Returns guid's bytes (matching string representation) in Base64 format.
69 | ///
70 | ///
71 | /// See https://cryptii.com/pipes/text-to-base64.
72 | ///
73 | ///
74 | public static string ToBase64String(this Guid guid)
75 | {
76 | return guid.ToByteArrayMatchingStringRepresentation().Base64Encode();
77 | }
78 |
79 | ///
80 | /// Returns guid from Base64 format (matching string representation).
81 | ///
82 | ///
83 | /// See https://cryptii.com/pipes/text-to-base64.
84 | ///
85 | ///
86 | public static Guid FromBase64String(this string guidString)
87 | {
88 | return guidString.Base64Decode().ToGuidMatchingStringRepresentation();
89 | }
90 |
91 | ///
92 | /// Returns guid's bytes (matching string representation) in Base64 Url format.
93 | ///
94 | ///
95 | /// See https://cryptii.com/pipes/text-to-base64.
96 | ///
97 | ///
98 | public static string ToBase64UrlString(this Guid guid)
99 | {
100 | return guid.ToByteArrayMatchingStringRepresentation().Base64UrlEncode();
101 | }
102 |
103 | ///
104 | /// Returns guid from Base64 Url format (matching string representation).
105 | ///
106 | ///
107 | /// See https://cryptii.com/pipes/text-to-base64.
108 | ///
109 | ///
110 | public static Guid FromBase64UrlString(this string guidString)
111 | {
112 | return guidString.Base64UrlDecode().ToGuidMatchingStringRepresentation();
113 | }
114 |
115 | ///
116 | /// Returns guid's bytes (matching string representation) in Base32 format.
117 | ///
118 | ///
119 | /// See https://cryptii.com/pipes/base32-to-hex.
120 | ///
121 | ///
122 | public static string ToBase32String(this Guid guid)
123 | {
124 | return guid.ToByteArrayMatchingStringRepresentation().Base32Encode();
125 | }
126 |
127 | ///
128 | /// Returns guid from Base32 format (matching string representation).
129 | ///
130 | ///
131 | /// See https://cryptii.com/pipes/base32-to-hex.
132 | ///
133 | ///
134 | public static Guid FromBase32String(this string guidString)
135 | {
136 | return guidString.Base32Decode().ToGuidMatchingStringRepresentation();
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/rm.Extensions/HashQueue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using rm.Extensions.Deque;
5 | using Ex = rm.Extensions.ExceptionHelper;
6 |
7 | namespace rm.Extensions;
8 |
9 | ///
10 | /// Defines hashqueue methods.
11 | ///
12 | public interface IHashQueue : IEnumerable
13 | {
14 | ///
15 | /// Enqueues into hashqueue.
16 | ///
17 | void Enqueue(T x);
18 |
19 | ///
20 | /// Dequeues head from hashqueue.
21 | ///
22 | T Dequeue();
23 |
24 | ///
25 | /// Deletes given from hashqueue.
26 | /// Returns true if successful.
27 | ///
28 | bool Delete(T x);
29 |
30 | ///
31 | /// Peeks head of hashqueue.
32 | ///
33 | T Peek();
34 |
35 | ///
36 | /// Peeks tail of hashqueue.
37 | ///
38 | T PeekTail();
39 |
40 | ///
41 | /// Returns true if is in hashqueue.
42 | ///
43 | bool Has(T x);
44 |
45 | ///
46 | /// Returns true if hashqueue is empty.
47 | ///
48 | bool IsEmpty();
49 |
50 | ///
51 | /// Returns count of hashqueue.
52 | ///
53 | int Count();
54 |
55 | ///
56 | /// Returns long count of hashqueue.
57 | ///
58 | long LongCount();
59 |
60 | ///
61 | /// Clears hashqueue.
62 | ///
63 | void Clear();
64 | }
65 |
66 | ///
67 | /// Hashqueue.
68 | ///
69 | ///
70 | /// Uses deque and map as backing store.
71 | ///
72 | /// All methods are O(1) time.
73 | ///
74 | public class HashQueue : IHashQueue
75 | {
76 | #region members
77 |
78 | IDeque dq;
79 | IDictionary>> map;
80 |
81 | #endregion
82 |
83 | #region ctors
84 |
85 | public HashQueue()
86 | {
87 | dq = new Deque();
88 | map = new Dictionary>>();
89 | }
90 |
91 | #endregion
92 |
93 | #region IHashQueue methods
94 |
95 | ///
96 | /// Enqueues into hashqueue.
97 | ///
98 | public void Enqueue(T x)
99 | {
100 | x.ThrowIfArgumentNull(nameof(x));
101 | if (!map.TryGetValue(x, out Deque> nodeq))
102 | {
103 | nodeq = new Deque>();
104 | map[x] = nodeq;
105 | }
106 | nodeq.Enqueue(dq.Enqueue(x));
107 | }
108 |
109 | ///
110 | /// Dequeues head from hashqueue.
111 | ///
112 | public T Dequeue()
113 | {
114 | Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
115 | var x = dq.Dequeue();
116 | DeleteFromMap(x);
117 | return x;
118 | }
119 |
120 | ///
121 | /// Deletes given from hashqueue.
122 | /// Returns true if successful.
123 | ///
124 | public bool Delete(T x)
125 | {
126 | Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
127 | x.ThrowIfArgumentNull(nameof(x));
128 | if (!Has(x))
129 | {
130 | return false;
131 | }
132 | dq.Delete(DeleteFromMap(x));
133 | return true;
134 | }
135 |
136 | ///
137 | /// Deletes from and
138 | /// returns the node.
139 | ///
140 | private Node DeleteFromMap(T x)
141 | {
142 | var nodeq = map[x];
143 | foreach (var node in nodeq.Nodes())
144 | {
145 | var xnode = node.Value;
146 | if (EqualityComparer.Default.Equals(xnode.Value, x))
147 | {
148 | nodeq.Delete(node);
149 | if (nodeq.IsEmpty())
150 | {
151 | map.Remove(x);
152 | }
153 | return node.Value;
154 | }
155 | }
156 | throw new InvalidOperationException(
157 | $"Node not found for item {x} in HashQueue.");
158 | }
159 |
160 | ///
161 | /// Peeks head of hashqueue.
162 | ///
163 | public T Peek()
164 | {
165 | Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
166 | return dq.Peek();
167 | }
168 |
169 | ///
170 | /// Peeks tail of hashqueue.
171 | ///
172 | public T PeekTail()
173 | {
174 | Ex.ThrowIfEmpty(IsEmpty(), "HashQueue is empty.");
175 | return dq.PeekTail();
176 | }
177 |
178 | ///
179 | /// Returns true if is in hashqueue.
180 | ///
181 | public bool Has(T x)
182 | {
183 | x.ThrowIfArgumentNull(nameof(x));
184 | return map.ContainsKey(x);
185 | }
186 |
187 | ///
188 | /// Returns true if hashqueue is empty.
189 | ///
190 | public bool IsEmpty()
191 | {
192 | return dq.IsEmpty();
193 | }
194 |
195 | ///
196 | /// Returns count of hashqueue.
197 | ///
198 | public int Count()
199 | {
200 | return dq.Count();
201 | }
202 |
203 | ///
204 | /// Returns long count of hashqueue.
205 | ///
206 | public long LongCount()
207 | {
208 | return dq.LongCount();
209 | }
210 |
211 | ///
212 | /// Clears hashqueue.
213 | ///
214 | public void Clear()
215 | {
216 | dq.Clear();
217 | map.Clear();
218 | }
219 |
220 | #endregion
221 |
222 | #region IEnumerable methods
223 |
224 | public IEnumerator GetEnumerator()
225 | {
226 | return dq.GetEnumerator();
227 | }
228 |
229 | IEnumerator IEnumerable.GetEnumerator()
230 | {
231 | return GetEnumerator();
232 | }
233 |
234 | #endregion
235 | }
236 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Helper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// Helper class.
8 | ///
9 | public static class Helper
10 | {
11 | ///
12 | /// Swaps parameters.
13 | ///
14 | /// Helper.Swap(ref a, ref b);
15 | public static void Swap(ref T t1, ref T t2)
16 | {
17 | var temp = t1;
18 | t1 = t2;
19 | t2 = temp;
20 | }
21 |
22 | ///
23 | /// Swaps array elements for given indices.
24 | ///
25 | /// Helper.Swap(array, i, j);
26 | public static void Swap(T[] a, int i, int j)
27 | {
28 | T t = a[i];
29 | a[i] = a[j];
30 | a[j] = t;
31 | }
32 |
33 | ///
34 | /// Concats muliple arrays into an array.
35 | ///
36 | public static T[] Concat(params T[][] arrayOfArrays)
37 | {
38 | arrayOfArrays.ThrowIfNull(nameof(arrayOfArrays));
39 | if (arrayOfArrays.Length == 0)
40 | {
41 | return Array.Empty();
42 | }
43 | if (arrayOfArrays.Length == 1)
44 | {
45 | return arrayOfArrays[0];
46 | }
47 | var array = new T[arrayOfArrays.Sum(arr => arr.Length)];
48 | int offset = 0;
49 | for (int i = 0; i < arrayOfArrays.Length; i++)
50 | {
51 | arrayOfArrays[i].CopyTo(array, offset);
52 | offset += arrayOfArrays[i].Length;
53 | }
54 | return array;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/rm.Extensions/IntExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Numerics;
3 |
4 | namespace rm.Extensions;
5 |
6 | ///
7 | /// Int extensions.
8 | ///
9 | public static class IntExtension
10 | {
11 | ///
12 | /// Gets n!.
13 | ///
14 | public static BigInteger Factorial(this int n)
15 | {
16 | n.ThrowIfArgumentOutOfRange(nameof(n));
17 | BigInteger product = 1;
18 | for (int i = 1; i <= n; i++)
19 | {
20 | product *= i;
21 | }
22 | return product;
23 | }
24 |
25 | ///
26 | /// Gets nPr.
27 | ///
28 | public static BigInteger Permutation(this int n, int r)
29 | {
30 | n.ThrowIfArgumentOutOfRange(nameof(n));
31 | r.ThrowIfArgumentOutOfRange(nameof(r), maxRange: n);
32 | BigInteger result = (n.Factorial() / (n - r).Factorial());
33 | return result;
34 | }
35 |
36 | ///
37 | /// Gets nCr.
38 | ///
39 | public static BigInteger Combination(this int n, int r)
40 | {
41 | n.ThrowIfArgumentOutOfRange(nameof(n));
42 | r.ThrowIfArgumentOutOfRange(nameof(r), maxRange: n);
43 | BigInteger result = (n.Factorial() / ((n - r).Factorial() * r.Factorial()));
44 | return result;
45 | }
46 |
47 | ///
48 | /// Gets scrabble count for n.
49 | ///
50 | /// nP1 + nP2 + ... + nPn
51 | public static BigInteger ScrabbleCount(this int n)
52 | {
53 | return ScrabbleCount(n, n);
54 | }
55 |
56 | ///
57 | /// Gets scrabble count for n with limit.
58 | ///
59 | /// nP1 + nP2 + ... + nPlimit, where limit is up to n
60 | public static BigInteger ScrabbleCount(this int n, int limit)
61 | {
62 | BigInteger sum = 0;
63 | for (int i = 1; i <= limit; i++)
64 | {
65 | sum += n.Permutation(i);
66 | }
67 | return sum;
68 | }
69 |
70 | ///
71 | /// Rounds int as "k" for kilo, "m" for mega, "g" for giga.
72 | ///
73 | public static string Round(this int n, uint digits = 0)
74 | {
75 | string s;
76 | var nabs = Math.Abs(n);
77 | if (nabs < 1000)
78 | {
79 | s = n + "";
80 | }
81 | else if (nabs < 1000000)
82 | {
83 | s = ((decimal)n / 1000).TruncateTo(digits) + "k";
84 | }
85 | else if (nabs < 1000000000)
86 | {
87 | s = ((decimal)n / 1000000).TruncateTo(digits) + "m";
88 | }
89 | else
90 | {
91 | s = ((decimal)n / 1000000000).TruncateTo(digits) + "g";
92 | }
93 | return s;
94 | }
95 |
96 | ///
97 | /// Rounds value down to an integer multiple of a given bin size.
98 | ///
99 | ///
100 | /// See https://docs.microsoft.com/en-us/azure/kusto/query/binfunction
101 | ///
102 | public static int Bin(this int n, int binSize)
103 | {
104 | var result = (n / binSize) * binSize;
105 | if (n < 0)
106 | {
107 | result -= binSize;
108 | }
109 | return result;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/rm.Extensions/ListExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// List extensions.
7 | ///
8 | public static class ListExtension
9 | {
10 | ///
11 | /// Removes the last item(s) in the list.
12 | ///
13 | public static void RemoveLast(this IList source, int n = 1)
14 | {
15 | for (int i = 0; i < n; i++)
16 | {
17 | source.RemoveAt(source.Count - 1);
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/rm.Extensions/LruCache.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Defines LRU cache methods.
7 | ///
8 | interface ILruCache
9 | {
10 | ///
11 | /// Gets value for from cache.
12 | ///
13 | ///
14 | /// Makes item MRU.
15 | ///
16 | TValue Get(TKey key);
17 |
18 | ///
19 | /// Inserts for in cache.
20 | ///
21 | ///
22 | /// Makes item MRU (also if already exists).
23 | ///
24 | void Insert(TKey key, TValue value);
25 |
26 | ///
27 | /// Removes from cache.
28 | ///
29 | bool Remove(TKey key);
30 |
31 | ///
32 | /// Returns true if exists in cache.
33 | ///
34 | bool HasKey(TKey key);
35 |
36 | ///
37 | /// Returns true if cache is empty.
38 | ///
39 | bool IsEmpty();
40 |
41 | ///
42 | /// Returns true if cache is full.
43 | ///
44 | bool IsFull();
45 |
46 | ///
47 | /// Returns capacity of cache.
48 | ///
49 | int Capacity();
50 |
51 | ///
52 | /// Returns count of cache.
53 | ///
54 | int Count();
55 |
56 | ///
57 | /// Clears cache.
58 | ///
59 | void Clear();
60 | }
61 |
62 | ///
63 | /// LRU cache.
64 | ///
65 | ///
66 | /// Uses deque and map as backing store.
67 | ///
68 | /// All methods are O(1) time.
69 | ///
70 | public class LruCache : ILruCache
71 | {
72 | #region members
73 |
74 | private readonly int n;
75 | private readonly IDeque dq;
76 | private readonly IDictionary, TValue)> map;
77 |
78 | #endregion
79 |
80 | #region ctors
81 |
82 | public LruCache(int n)
83 | {
84 | n.ThrowIfArgumentOutOfRange(nameof(n));
85 | this.n = n;
86 | dq = new Deque();
87 | map = new Dictionary, TValue)>(capacity: n);
88 | }
89 |
90 | #endregion
91 |
92 | #region ILruCache methods
93 |
94 | ///
95 | /// Gets value for from cache.
96 | ///
97 | ///
98 | /// Makes item MRU.
99 | ///
100 | public TValue Get(TKey key)
101 | {
102 | if (!map.ContainsKey(key))
103 | {
104 | return default(TValue);
105 | }
106 | var (node, value) = map[key];
107 | dq.MakeTail(node);
108 | return value;
109 | }
110 |
111 | ///
112 | /// Inserts for in cache.
113 | ///
114 | ///
115 | /// Makes item MRU (also if already exists).
116 | ///
117 | public void Insert(TKey key, TValue value)
118 | {
119 | key.ThrowIfArgumentNull(nameof(key));
120 | if (Capacity() == 0)
121 | {
122 | return;
123 | }
124 | if (map.ContainsKey(key))
125 | {
126 | var (node, _) = map[key];
127 | dq.MakeTail(node);
128 | map[key] = (node, value);
129 | return;
130 | }
131 | if (IsFull())
132 | {
133 | // remove lru
134 | map.Remove(dq.Dequeue());
135 | }
136 | map[key] = (dq.Enqueue(key), value);
137 | }
138 |
139 | ///
140 | /// Removes from cache.
141 | ///
142 | public bool Remove(TKey key)
143 | {
144 | if (!map.ContainsKey(key))
145 | {
146 | return false;
147 | }
148 | var (node, _) = map[key];
149 | dq.Delete(node);
150 | map.Remove(key);
151 | return true;
152 | }
153 |
154 | ///
155 | /// Returns true if exists in cache.
156 | ///
157 | public bool HasKey(TKey key)
158 | {
159 | return map.ContainsKey(key);
160 | }
161 |
162 | ///
163 | /// Returns true if cache is empty.
164 | ///
165 | public bool IsEmpty()
166 | {
167 | return Count() == 0;
168 | }
169 |
170 | ///
171 | /// Returns true if cache is full.
172 | ///
173 | public bool IsFull()
174 | {
175 | return Count() == Capacity();
176 | }
177 |
178 | ///
179 | /// Returns capacity of cache.
180 | ///
181 | public int Capacity()
182 | {
183 | return n;
184 | }
185 |
186 | ///
187 | /// Returns count of cache.
188 | ///
189 | public int Count()
190 | {
191 | return map.Count;
192 | }
193 |
194 | ///
195 | /// Clears cache.
196 | ///
197 | public void Clear()
198 | {
199 | dq.Clear();
200 | map.Clear();
201 | }
202 |
203 | #endregion
204 | }
205 |
--------------------------------------------------------------------------------
/src/rm.Extensions/NameValueCollectionExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Specialized;
2 | using System.Text;
3 | using System.Web;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// NameValueCollection extensions.
9 | ///
10 | public static class NameValueCollectionExtension
11 | {
12 | ///
13 | /// Gets query string for name value collection.
14 | ///
15 | public static string ToQueryString(this NameValueCollection collection,
16 | bool prefixQuestionMark = true)
17 | {
18 | collection.ThrowIfArgumentNull(nameof(collection));
19 | if (collection.Keys.Count == 0)
20 | {
21 | return "";
22 | }
23 | var buffer = new StringBuilder();
24 | if (prefixQuestionMark)
25 | {
26 | buffer.Append("?");
27 | }
28 | var append = false;
29 | for (int i = 0; i < collection.Keys.Count; i++)
30 | {
31 | var key = collection.Keys[i];
32 | var values = collection.GetValues(key);
33 | key.ThrowIfNull(nameof(key));
34 | values.ThrowIfNull(nameof(values));
35 | foreach (var value in values)
36 | {
37 | if (append)
38 | {
39 | buffer.Append("&");
40 | }
41 | append = true;
42 | buffer.AppendFormat("{0}={1}", key.UrlEncode(), value.UrlEncode());
43 | }
44 | }
45 | return buffer.ToString();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | [assembly: InternalsVisibleTo("rm.ExtensionsTest")]
4 |
--------------------------------------------------------------------------------
/src/rm.Extensions/RandomExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Random extensions.
7 | ///
8 | public static class RandomExtension
9 | {
10 | ///
11 | /// Returns a random item from .
12 | ///
13 | public static T NextItem(this Random random, T[] source)
14 | {
15 | source.ThrowIfArgumentNull(nameof(source));
16 | if (source.IsNullOrEmpty())
17 | {
18 | throw new ArgumentOutOfRangeException(nameof(source.Length), source.Length, null);
19 | }
20 |
21 | return source[random.Next(source.Length)];
22 | }
23 |
24 | ///
25 | /// source
26 | ///
27 | public static double NextGaussian(this Random random, double mu = 0, double sigma = 1)
28 | {
29 | // uniform(0,1] random doubles
30 | double u1 = 1.0 - random.NextDouble();
31 | double u2 = 1.0 - random.NextDouble();
32 | // random normal(0,1)
33 | double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2);
34 | // random normal(mean,stdDev^2)
35 | double randNormal = mu + sigma * randStdNormal;
36 | return randNormal;
37 | }
38 |
39 | ///
40 | /// Generates random string of for .
41 | ///
42 | ///
43 | /// source
44 | ///
45 | public static string NextString(this Random random, int length, string charset)
46 | {
47 | length.ThrowIfArgumentOutOfRange(nameof(length));
48 | if (charset.IsNullOrEmpty())
49 | {
50 | throw new ArgumentException(nameof(charset));
51 | }
52 | var randomString = new char[length];
53 | for (int i = 0; i < length; i++)
54 | {
55 | randomString[i] = charset[random.Next(charset.Length)];
56 | }
57 | return new string(randomString);
58 | }
59 |
60 | ///
61 | /// Generates random double between and .
62 | ///
63 | /// Note: This is a scaled implementation: (random.nextDouble() * (max - min)) + min
64 | ///
65 | ///
66 | /// source
67 | ///
68 | public static double NextDouble(this Random random, double minValue = 0d, double maxValue = double.MaxValue)
69 | {
70 | if (minValue > maxValue)
71 | {
72 | throw new ArgumentOutOfRangeException(nameof(minValue), "'minValue' cannot be greater than 'maxValue'");
73 | }
74 | // scaled
75 | return (random.NextDouble() * (maxValue - minValue)) + minValue;
76 | }
77 |
78 | ///
79 | /// Generates random decimal between and .
80 | ///
81 | /// Note: This is a scaled implementation: (random.nextDouble() * (max - min)) + min
82 | ///
83 | ///
84 | /// source
85 | ///
86 | public static decimal NextDecimal(this Random random, decimal minValue = decimal.Zero, decimal maxValue = decimal.MaxValue)
87 | {
88 | if (minValue > maxValue)
89 | {
90 | throw new ArgumentOutOfRangeException(nameof(minValue), "'minValue' cannot be greater than 'maxValue'");
91 | }
92 | // scaled
93 | return ((decimal)random.NextDouble() * (maxValue - minValue)) + minValue;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/rm.Extensions/StopwatchExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// Stopwatch extensions.
7 | ///
8 | public static class StopwatchExtension
9 | {
10 | public static long ElapsedTicks(this Stopwatch sw)
11 | {
12 | return sw.ElapsedTicks;
13 | }
14 | public static long ElapsedMilliseconds(this Stopwatch sw)
15 | {
16 | return sw.ElapsedMilliseconds;
17 | }
18 | public static long ElapsedSeconds(this Stopwatch sw)
19 | {
20 | return sw.ElapsedMilliseconds / 1000;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/rm.Extensions/StringBuilderExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// StringBuilder extensions.
7 | ///
8 | public static class StringBuilderExtension
9 | {
10 | ///
11 | /// Appends formatted args followed by newline.
12 | ///
13 | public static StringBuilder AppendLine(this StringBuilder sb, string format, params object[] args)
14 | {
15 | sb.ThrowIfArgumentNull(nameof(sb));
16 | sb.AppendLine(string.Format(format, args));
17 | return sb;
18 | }
19 |
20 | ///
21 | /// Reverses this instance in-place.
22 | ///
23 | ///
24 | /// Does not work for string with surrogate pairs as,
25 | /// "Les Misérables"
26 | /// See https://stackoverflow.com/questions/228038/best-way-to-reverse-a-string
27 | ///
28 | public static StringBuilder Reverse(this StringBuilder sb)
29 | {
30 | sb.ThrowIfArgumentNull(nameof(sb));
31 | var start = 0;
32 | var end = sb.Length - 1;
33 | while (start < end)
34 | {
35 | var temp = sb[start];
36 | sb[start] = sb[end];
37 | sb[end] = temp;
38 | start++;
39 | end--;
40 | }
41 | return sb;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/rm.Extensions/ThrowExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Ex = rm.Extensions.ExceptionHelper;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Throw extensions.
9 | ///
10 | public static class ThrowExtension
11 | {
12 | ///
13 | /// Throws exception if is null.
14 | ///
15 | /// Input.
16 | /// Exception message.
17 | /// Input.
18 | public static T ThrowIfNull(this T t, string exMessage = "")
19 | {
20 | Ex.ThrowIfNull(t == null, exMessage);
21 | return t;
22 | }
23 |
24 | ///
25 | /// Throws exception if argument is null.
26 | ///
27 | /// Input.
28 | /// Exception message.
29 | /// Input.
30 | public static T ThrowIfArgumentNull(this T t, string exMessage = "")
31 | {
32 | Ex.ThrowIfArgumentNull(t == null, exMessage);
33 | return t;
34 | }
35 |
36 | ///
37 | /// Throws exception if any of the is null.
38 | ///
39 | /// Input.
40 | /// Input.
41 | public static IEnumerable ThrowIfAnyNull(this IEnumerable objects)
42 | {
43 | foreach (var o in objects)
44 | {
45 | o.ThrowIfNull();
46 | }
47 | return objects;
48 | }
49 |
50 | ///
51 | /// Throws exception if any of the arguments is null.
52 | ///
53 | /// Input.
54 | /// Input.
55 | public static IEnumerable ThrowIfAnyArgumentNull(this IEnumerable objects)
56 | {
57 | foreach (var o in objects)
58 | {
59 | o.ThrowIfArgumentNull();
60 | }
61 | return objects;
62 | }
63 |
64 | ///
65 | /// Throws exception if the string is null or empty.
66 | ///
67 | /// Input.
68 | /// Exception message.
69 | /// Input.
70 | public static string ThrowIfNullOrEmpty(this string s, string exMessage = "")
71 | {
72 | Ex.ThrowIfNull(s == null, exMessage);
73 | Ex.ThrowIfEmpty(s.Length == 0, exMessage);
74 | return s;
75 | }
76 |
77 | ///
78 | /// Throws exception if the string argument is null or empty.
79 | ///
80 | /// Input.
81 | /// Exception message.
82 | /// Input.
83 | public static string ThrowIfNullOrEmptyArgument(this string s, string exMessage = "")
84 | {
85 | Ex.ThrowIfArgumentNull(s == null, exMessage);
86 | Ex.ThrowIfEmpty(s.Length == 0, exMessage);
87 | return s;
88 | }
89 |
90 | ///
91 | /// Throws exception if any of the strings is null or empty.
92 | ///
93 | /// Input.
94 | /// Input.
95 | public static IEnumerable ThrowIfNullOrEmpty(this IEnumerable strings)
96 | {
97 | foreach (var s in strings)
98 | {
99 | s.ThrowIfNullOrEmpty();
100 | }
101 | return strings;
102 | }
103 |
104 | ///
105 | /// Throws exception if any of the string arguments is null or empty.
106 | ///
107 | /// Input.
108 | /// Input.
109 | public static IEnumerable ThrowIfNullOrEmptyArgument(this IEnumerable strings)
110 | {
111 | foreach (var s in strings)
112 | {
113 | s.ThrowIfNullOrEmptyArgument();
114 | }
115 | return strings;
116 | }
117 |
118 | ///
119 | /// Throws exception if the string is null or whitespace.
120 | ///
121 | /// Input.
122 | /// Exception message.
123 | /// Input.
124 | public static string ThrowIfNullOrWhiteSpace(this string s, string exMessage = "")
125 | {
126 | Ex.ThrowIfNull(s == null, exMessage);
127 | Ex.ThrowIfEmpty(s.Trim().Length == 0, exMessage);
128 | return s;
129 | }
130 |
131 | ///
132 | /// Throws exception if the string argument is null or whitespace.
133 | ///
134 | /// Input.
135 | /// Exception message.
136 | /// Input.
137 | public static string ThrowIfNullOrWhiteSpaceArgument(this string s, string exMessage = "")
138 | {
139 | Ex.ThrowIfArgumentNull(s == null, exMessage);
140 | Ex.ThrowIfEmpty(s.Trim().Length == 0, exMessage);
141 | return s;
142 | }
143 |
144 | ///
145 | /// Throws exception if is out of range.
146 | ///
147 | /// Input.
148 | /// Exception message.
149 | /// Min range value.
150 | /// Max range value.
151 | /// Input.
152 | public static int ThrowIfArgumentOutOfRange(this int x, string exMessage = "",
153 | int minRange = 0, int maxRange = int.MaxValue)
154 | {
155 | Ex.ThrowIfArgumentOutOfRange(x < minRange || x > maxRange,
156 | exMessage);
157 | return x;
158 | }
159 |
160 | ///
161 | /// Throws exception if is out of range (uint).
162 | ///
163 | /// Input.
164 | /// Exception message.
165 | /// Min range value.
166 | /// Max range value.
167 | /// Input.
168 | public static uint ThrowIfArgumentOutOfRange(this uint x, string exMessage = "",
169 | uint minRange = 0, uint maxRange = uint.MaxValue)
170 | {
171 | Ex.ThrowIfArgumentOutOfRange(x < minRange || x > maxRange,
172 | exMessage);
173 | return x;
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/rm.Extensions/TimeSpanExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace rm.Extensions;
4 |
5 | ///
6 | /// TimeSpan extensions.
7 | ///
8 | public static class TimespanExtension
9 | {
10 | ///
11 | /// Rounds timespan.
12 | ///
13 | /// Ex: ms, s, h, d, wk, mth, y.
14 | ///
15 | ///
16 | public static string Round(this TimeSpan ts)
17 | {
18 | if (ts.Days >= 365)
19 | {
20 | return "{0}y".Format(ts.Days / 365);
21 | }
22 | if (ts.Days >= 30)
23 | {
24 | return "{0}mth".Format(ts.Days / 30);
25 | }
26 | if (ts.Days >= 7)
27 | {
28 | return "{0}wk".Format(ts.Days / 7);
29 | }
30 | if (ts.Days > 0)
31 | {
32 | return "{0}d".Format(ts.Days);
33 | }
34 | if (ts.Hours > 0)
35 | {
36 | return "{0}h".Format(ts.Hours);
37 | }
38 | if (ts.Minutes > 0)
39 | {
40 | return "{0}m".Format(ts.Minutes);
41 | }
42 | if (ts.Seconds > 0)
43 | {
44 | return "{0}s".Format(ts.Seconds);
45 | }
46 | return "{0}ms".Format(ts.Milliseconds);
47 | }
48 |
49 | ///
50 | /// Gets timespan with days.
51 | ///
52 | public static TimeSpan Days(this int n)
53 | {
54 | return TimeSpan.FromDays(n);
55 | }
56 |
57 | ///
58 | /// Gets timespan with hours.
59 | ///
60 | public static TimeSpan Hours(this int n)
61 | {
62 | return TimeSpan.FromHours(n);
63 | }
64 |
65 | ///
66 | /// Gets timespan with minutes.
67 | ///
68 | public static TimeSpan Minutes(this int n)
69 | {
70 | return TimeSpan.FromMinutes(n);
71 | }
72 |
73 | ///
74 | /// Gets timespan with seconds.
75 | ///
76 | public static TimeSpan Seconds(this int n)
77 | {
78 | return TimeSpan.FromSeconds(n);
79 | }
80 |
81 | ///
82 | /// Gets timespan with milliseconds.
83 | ///
84 | public static TimeSpan Milliseconds(this int n)
85 | {
86 | return TimeSpan.FromMilliseconds(n);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/rm.Extensions/UnsupportedEnumValueException.TEnum.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.Serialization;
4 | using System.Text;
5 |
6 | namespace rm.Extensions;
7 |
8 | ///
9 | /// The exception that is thrown when the enum value is not supported.
10 | ///
11 | public class UnsupportedEnumValueException : Exception
12 | {
13 | public UnsupportedEnumValueException(string message)
14 | : base(message) { }
15 | public UnsupportedEnumValueException(string message, Exception inner)
16 | : base(message, inner) { }
17 | protected UnsupportedEnumValueException(SerializationInfo info, StreamingContext context)
18 | : base(info, context) { }
19 |
20 | public UnsupportedEnumValueException(TEnum enumValue)
21 | : base($"Value {enumValue} of enum {typeof(TEnum).Name} is not supported.")
22 | { }
23 | }
24 |
--------------------------------------------------------------------------------
/src/rm.Extensions/UriExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Security.Cryptography;
4 |
5 | namespace rm.Extensions;
6 |
7 | ///
8 | /// Hash algorithm type.
9 | ///
10 | public enum Hasher
11 | {
12 | sha1 = 1,
13 | md5,
14 | }
15 |
16 | ///
17 | /// Uri extensions.
18 | ///
19 | public static class UriExtension
20 | {
21 | ///
22 | /// Calculates checksum / hash (sha1 or md5) for uri (url or local file).
23 | ///
24 | /// hash: sha1 20 bytes, md5 hash 16 bytes
25 | ///
26 | ///
27 | /// uri (url or local file).
28 | /// Hasher.sha1 or Hasher.md5.
29 | /// hash
30 | /// http://hash.online-convert.com/
31 | public static string Checksum(this Uri uri, Hasher type = Hasher.sha1)
32 | {
33 | uri.ThrowIfArgumentNull(nameof(uri));
34 | switch (type)
35 | {
36 | case Hasher.sha1:
37 | using (var hasher = SHA1.Create())
38 | {
39 | return Checksum(hasher, uri);
40 | }
41 | case Hasher.md5:
42 | using (var hasher = MD5.Create())
43 | {
44 | return Checksum(hasher, uri);
45 | }
46 | default:
47 | throw new ArgumentOutOfRangeException(nameof(type), type, "Unknown hash algorithm type.");
48 | }
49 | }
50 |
51 | private static string Checksum(HashAlgorithm hasher, Uri uri)
52 | {
53 | // webclient works for uris and local files
54 | using (var webclient = new WebClient())
55 | {
56 | using (var stream = webclient.OpenRead(uri))
57 | {
58 | stream.ThrowIfNull(nameof(stream));
59 | var hash = BitConverter.ToString(hasher.ComputeHash(stream)).Replace("-", "").ToLower();
60 | return hash;
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/rm.Extensions/Wrapped.cs:
--------------------------------------------------------------------------------
1 | namespace rm.Extensions;
2 |
3 | ///
4 | /// Wrapped type.
5 | ///
6 | /// Useful to avoid pass by reference parameters.
7 | public class Wrapped
8 | {
9 | public T Value { get; set; }
10 |
11 | public Wrapped(T value)
12 | {
13 | Value = value;
14 | }
15 |
16 | /////
17 | ///// Converts T to Wrapped{T}.
18 | /////
19 | /////
20 | ///// This does NOT work as a new instance is returned.
21 | ///// And cannot overload the assignment "=" operator in C#.
22 | /////
23 | ///// It WOULD allow this:
24 | ///// // same as wrappedT.Value = value;
25 | ///// wrappedT = value;
26 | /////
27 | //public static implicit operator Wrapped(T value)
28 | //{
29 | // return new Wrapped(value);
30 | //}
31 |
32 | /////
33 | ///// Converts Wrapped{T} to T.
34 | /////
35 | /////
36 | ///// This works. But not allowing due to inconsistency (see above conversion operator limitation).
37 | /////
38 | ///// It allows this:
39 | ///// // same as value = wrappedT.Value;
40 | ///// value = wrappedT;
41 | /////
42 | //public static implicit operator T(Wrapped wrappedT)
43 | //{
44 | // return wrappedT.Value;
45 | //}
46 | }
47 |
--------------------------------------------------------------------------------
/src/rm.Extensions/WrappedExtension.cs:
--------------------------------------------------------------------------------
1 | namespace rm.Extensions;
2 |
3 | ///
4 | /// Wrapped extensions.
5 | ///
6 | public static class WrappedExtension
7 | {
8 | ///
9 | /// Returns a Wrapped{T} instance for .
10 | ///
11 | public static Wrapped Wrap(this T t)
12 | {
13 | return new Wrapped(t);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/rm.Extensions/rm.Extensions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0;netstandard2.1;netstandard2.0
5 | false
6 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
7 | false
8 | true
9 | 1591;NU1507
10 | latest
11 | rmandvikar.Extensions
12 | A collection of utility C# extension methods.
13 | utility extensions
14 | https://github.com/rmandvikar/csharp-extensions
15 | https://github.com/rmandvikar/csharp-extensions
16 | README.md
17 | LICENSE
18 | hippy
19 | hippy
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/ArrayExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using rm.Extensions;
4 |
5 | namespace rm.ExtensionsTest;
6 |
7 | [TestFixture]
8 | public class ArrayExtensionTest
9 | {
10 | [Test]
11 | public void EmptyTest01()
12 | {
13 | var empty = Array.Empty;
14 | var expected = Array.Empty();
15 | Assert.AreSame(expected, empty);
16 | Assert.AreSame(Array.Empty, Array.Empty);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/Base16ExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using System.Text;
5 | using NUnit.Framework;
6 | using rm.Extensions;
7 |
8 | namespace rm.ExtensionsTest;
9 |
10 | public class Base16ExtensionTest
11 | {
12 | private const int iterations = 1_000_000;
13 |
14 | [Test]
15 | [TestCase("Man", "4D616E")]
16 | [TestCase("Woman", "576F6D616E")]
17 | [TestCase("The quick brown fox jumps over the lazy dog.", "54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F672E")]
18 | public void Base16Encode_01(string s, string base16)
19 | {
20 | Assert.AreEqual(base16, s.ToUtf8Bytes().Base16Encode());
21 | }
22 |
23 | [Test]
24 | [TestCase("4D616E", "Man")]
25 | [TestCase("576F6D616E", "Woman")]
26 | [TestCase("54686520717569636B2062726F776E20666F78206A756D7073206F76657220746865206C617A7920646F672E", "The quick brown fox jumps over the lazy dog.")]
27 | public void Base16Decode_01(string base16, string s)
28 | {
29 | Assert.AreEqual(s, base16.Base16Decode().ToUtf8String());
30 | }
31 |
32 | [Test]
33 | [TestCase("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e", "The quick brown fox jumps over the lazy dog.")]
34 | public void Base16Decode_Lowercase_01(string base16, string s)
35 | {
36 | Assert.AreEqual(s, base16.Base16Decode().ToUtf8String());
37 | }
38 |
39 | [Test]
40 | [TestCase("F")]
41 | [TestCase("f")]
42 | public void Base16Decode_Invalid_01(string base16)
43 | {
44 | var ex = Assert.Throws(() => base16.Base16Decode());
45 | }
46 |
47 | [Test]
48 | [TestCase("FU")]
49 | [TestCase("fu")]
50 | public void Base16Decode_Invalid_02(string base16)
51 | {
52 | var ex = Assert.Throws(() => base16.Base16Decode());
53 | }
54 |
55 | [Test]
56 | [TestCase("base16")]
57 | public void Base16_Roundtrip_01(string s)
58 | {
59 | var base16 = s.ToUtf8Bytes().Base16Encode();
60 | var roundtrip = base16.Base16Decode().ToUtf8String();
61 | Assert.AreEqual(s, roundtrip);
62 | }
63 |
64 | [Explicit]
65 | [Test]
66 | [Category("slow")]
67 | public void Perf_Base16Encode()
68 | {
69 | var bytes = "The quick brown fox jumps over the lazy dog.".ToUtf8Bytes();
70 | var sw = Stopwatch.StartNew();
71 | for (int i = 0; i < iterations; i++)
72 | {
73 | var base16 = bytes.Base16Encode();
74 | }
75 | sw.Stop();
76 | Console.WriteLine(sw.ElapsedMilliseconds);
77 | }
78 |
79 | [Explicit]
80 | [Test]
81 | [Category("slow")]
82 | public void Perf_Base16EncodeUppercase()
83 | {
84 | var bytes = "The quick brown fox jumps over the lazy dog.".ToUtf8Bytes();
85 | var sw = Stopwatch.StartNew();
86 | for (int i = 0; i < iterations; i++)
87 | {
88 | var base16 = bytes.Base16EncodeUppercase();
89 | }
90 | sw.Stop();
91 | Console.WriteLine(sw.ElapsedMilliseconds);
92 | }
93 |
94 | [Explicit]
95 | [Test]
96 | [Category("slow")]
97 | public void Perf_Base16EncodeLowercase()
98 | {
99 | var bytes = "The quick brown fox jumps over the lazy dog.".ToUtf8Bytes();
100 | var sw = Stopwatch.StartNew();
101 | for (int i = 0; i < iterations; i++)
102 | {
103 | var base16 = bytes.Base16EncodeLowercase();
104 | }
105 | sw.Stop();
106 | Console.WriteLine(sw.ElapsedMilliseconds);
107 | }
108 |
109 | [Explicit]
110 | [Test]
111 | [Category("slow")]
112 | public void Perf_Base16Encode_SampleImpl()
113 | {
114 | var bytes = "The quick brown fox jumps over the lazy dog.".ToUtf8Bytes();
115 | var sw = Stopwatch.StartNew();
116 | for (int i = 0; i < iterations; i++)
117 | {
118 | var hex = Base16Encode_SampleImpl(bytes);
119 | }
120 | sw.Stop();
121 | Console.WriteLine(sw.ElapsedMilliseconds);
122 | }
123 |
124 | public static string Base16Encode_SampleImpl(byte[] bytes)
125 | {
126 | #if NET5_0_OR_GREATER
127 | return Convert.ToHexString(bytes);
128 | #else
129 | // slower
130 | //StringBuilder hex = new StringBuilder(bytes.Length * 2);
131 | //foreach (byte b in bytes)
132 | //{
133 | // hex.AppendFormat("{0:x2}", b);
134 | //}
135 | //return hex.ToString();
136 |
137 | return BitConverter.ToString(bytes).Replace("-", "");
138 | #endif
139 | }
140 |
141 | [Explicit]
142 | [Test]
143 | [Category("slow")]
144 | public void Perf_Base16Decode()
145 | {
146 | var base16 = "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e";
147 | var sw = Stopwatch.StartNew();
148 | for (int i = 0; i < iterations; i++)
149 | {
150 | var bytes = base16.Base16Decode();
151 | }
152 | sw.Stop();
153 | Console.WriteLine(sw.ElapsedMilliseconds);
154 | }
155 |
156 | [Explicit]
157 | [Test]
158 | [Category("slow")]
159 | public void Perf_Base16Decode_SampleImpl()
160 | {
161 | var base16 = "54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e";
162 | var sw = Stopwatch.StartNew();
163 | for (int i = 0; i < iterations; i++)
164 | {
165 | var bytes = Base16Decode_SampleImpl(base16);
166 | }
167 | sw.Stop();
168 | Console.WriteLine(sw.ElapsedMilliseconds);
169 | }
170 |
171 | ///net5.0+ has Convert.FromHexString(string)
172 | public static byte[] Base16Decode_SampleImpl(string hex)
173 | {
174 | #if NET5_0_OR_GREATER
175 | return Convert.FromHexString(hex);
176 | #else
177 | // skip validations
178 | var bytes = new byte[hex.Length >> 1];
179 | for (int i = 0; i < hex.Length; i += 2)
180 | {
181 | bytes[i >> 1] = Convert.ToByte(hex.Substring(i, 2), 16);
182 | }
183 | return bytes;
184 | #endif
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/Base32ExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using NUnit.Framework;
4 | using rm.Extensions;
5 |
6 | namespace rm.ExtensionsTest;
7 |
8 | // See https://cryptii.com/pipes/crockford-base32
9 | public class Base32ExtensionTest
10 | {
11 | private const int iterations = 1_000_000;
12 |
13 | [Test]
14 | [TestCase("Man", "9NGPW")]
15 | [TestCase("Woman", "AXQPTRBE")]
16 | [TestCase("The quick brown fox jumps over the lazy dog.", "AHM6A83HENMP6TS0C9S6YXVE41K6YY10D9TPTW3K41QQCSBJ41T6GS90DHGQMY90CHQPEBG")]
17 | public void Base32Encode_01(string s, string base32)
18 | {
19 | Assert.AreEqual(base32, s.ToUtf8Bytes().Base32Encode());
20 | }
21 |
22 | [Test]
23 | [TestCase("9NGPW", "Man")]
24 | [TestCase("AXQPTRBE", "Woman")]
25 | [TestCase("AHM6A83HENMP6TS0C9S6YXVE41K6YY10D9TPTW3K41QQCSBJ41T6GS90DHGQMY90CHQPEBG", "The quick brown fox jumps over the lazy dog.")]
26 | public void Base32Decode_01(string base32, string s)
27 | {
28 | Assert.AreEqual(s, base32.Base32Decode().ToUtf8String());
29 | }
30 |
31 | [Test]
32 | [TestCase("ahm6a83henmp6ts0c9s6yxve41k6yy10d9tptw3k41qqcsbj41t6gs90dhgqmy90chqpebg", "The quick brown fox jumps over the lazy dog.")] // lowercase
33 | [TestCase("ahm6a83henmp6tsOc9s6yxve41k6yy1Od9tptw3k41qqcsbj41t6gs9Odhgqmy9Ochqpebg", "The quick brown fox jumps over the lazy dog.")] // O
34 | [TestCase("ahm6a83henmp6tsoc9s6yxve41k6yy1od9tptw3k41qqcsbj41t6gs9odhgqmy9ochqpebg", "The quick brown fox jumps over the lazy dog.")] // o
35 | [TestCase("ahm6a83henmp6ts0c9s6yxve4Ik6yyI0d9tptw3k4Iqqcsbj4It6gs90dhgqmy90chqpebg", "The quick brown fox jumps over the lazy dog.")] // I
36 | [TestCase("ahm6a83henmp6ts0c9s6yxve4ik6yyi0d9tptw3k4iqqcsbj4it6gs90dhgqmy90chqpebg", "The quick brown fox jumps over the lazy dog.")] // i
37 | [TestCase("ahm6a83henmp6ts0c9s6yxve4Lk6yyL0d9tptw3k4Lqqcsbj4Lt6gs90dhgqmy90chqpebg", "The quick brown fox jumps over the lazy dog.")] // L
38 | [TestCase("ahm6a83henmp6ts0c9s6yxve4lk6yyl0d9tptw3k4lqqcsbj4lt6gs90dhgqmy90chqpebg", "The quick brown fox jumps over the lazy dog.")] // l
39 | [TestCase("ahm6a83henmp6ts0c9s6yxve4lk6yyl0-d9tptw3k4lqqcsbj-4lt6gs90dhgqmy90chqpebg", "The quick brown fox jumps over the lazy dog.")] // -
40 | public void Base32Decode_Variations_01(string base32, string s)
41 | {
42 | Assert.AreEqual(s, base32.Base32Decode().ToUtf8String());
43 | }
44 |
45 | [Test]
46 | [TestCase("base3")]
47 | [TestCase("base32")]
48 | [TestCase("base32!")]
49 | [TestCase("base32!!")]
50 | [TestCase("base32!!!")]
51 | [TestCase("base32!!!!")]
52 | [TestCase("base32!!!!!")]
53 | [TestCase("base32!!!!!!")]
54 | [TestCase("base32!!!!!!!")]
55 | public void Base32_Roundtrip_01(string s)
56 | {
57 | var base32 = s.ToUtf8Bytes().Base32Encode();
58 | var roundtrip = base32.Base32Decode().ToUtf8String();
59 | Assert.AreEqual(s, roundtrip);
60 | }
61 |
62 | [Test]
63 | [TestCase("FU")]
64 | [TestCase("F®")]
65 | public void Base32Decode_Invalid_02(string base32)
66 | {
67 | Assert.Throws(() =>
68 | base32.Base32Decode().ToUtf8String());
69 | }
70 |
71 | [Test]
72 | [TestCase("a", "")]
73 | [TestCase("ah", "T")]
74 | [TestCase("ahm", "T")]
75 | [TestCase("ahm6", "Th")]
76 | [TestCase("ahm6a", "The")]
77 | [TestCase("ahm6a8", "The")]
78 | [TestCase("ahm6a83", "The ")]
79 | [TestCase("ahm6a83h", "The q")]
80 | public void Base32Decode_Surplus_Chars_Do_Not_Throw_01(string base32, string s)
81 | {
82 | Assert.AreEqual(s, base32.Base32Decode().ToUtf8String());
83 | }
84 |
85 | [Test]
86 | [TestCase("8843EB1845D94528A4E862E277A26629", "H11YP625V52JH978CBH7F8K654")]
87 | public void Base32Encode_Hex_01(string guid, string base32)
88 | {
89 | Assert.AreEqual(base32, guid.Base16Decode().Base32Encode());
90 | }
91 |
92 | [Test]
93 | [TestCase("H11YP625V52JH978CBH7F8K654", "8843EB1845D94528A4E862E277A26629")]
94 | public void Base32Decode_Hex_01(string base32, string guid)
95 | {
96 | Assert.AreEqual(guid, base32.Base32Decode().Base16Encode());
97 | }
98 |
99 | [Explicit]
100 | [Test]
101 | [Category("slow")]
102 | [TestCase("The quick brown fox jumps over the lazy dog.")]
103 | [TestCase("The quick brown fox jumps over the lazy dog. ")]
104 | [TestCase("The quick brown fox jumps over the lazy ")]
105 | public void Perf_Base32Encode(string s)
106 | {
107 | var bytes = s.ToUtf8Bytes();
108 | var sw = Stopwatch.StartNew();
109 | for (int i = 0; i < iterations; i++)
110 | {
111 | var base32 = bytes.Base32Encode();
112 | }
113 | sw.Stop();
114 | Console.WriteLine(sw.ElapsedMilliseconds);
115 | }
116 |
117 | [Explicit]
118 | [Test]
119 | [Category("slow")]
120 | [TestCase("ahm6a83henmp6ts0c9s6yxve41k6yy10d9tptw3k41qqcsbj41t6gs90dhgqmy90chqpebg")]
121 | [TestCase("ahm6a83henmp6ts0c9s6yxve41k6yy10d9tptw3k41qqcsbj41t6gs90dhgqmy90chqpebh0")]
122 | [TestCase("ahm6a83henmp6ts0c9s6yxve41k6yy10d9tptw3k41qqcsbj41t6gs90dhgqmy90")]
123 | [TestCase("ahm6a83henmp6ts0c9s6yxve4lk6yyl0-d9tptw3k4lqqcsbj-4lt6gs90dhgqmy90chqpebg")]
124 | public void Perf_Base32Decode(string base32)
125 | {
126 | var sw = Stopwatch.StartNew();
127 | for (int i = 0; i < iterations; i++)
128 | {
129 | var bytes = base32.Base32Decode();
130 | }
131 | sw.Stop();
132 | Console.WriteLine(sw.ElapsedMilliseconds);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/Base64ExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using NUnit.Framework;
4 | using rm.Extensions;
5 |
6 | namespace rm.ExtensionsTest;
7 |
8 | [TestFixture]
9 | public class Base64ExtensionTest
10 | {
11 | private const int iterations = 1_000_000;
12 |
13 | [Test]
14 | [TestCase("Man", "TWFu")]
15 | [TestCase("Woman", "V29tYW4=")]
16 | [TestCase("light work.", "bGlnaHQgd29yay4=")]
17 | [TestCase("light work", "bGlnaHQgd29yaw==")]
18 | public void Base64Encode_01(string s, string base64)
19 | {
20 | Assert.AreEqual(base64, s.ToUtf8Bytes().Base64Encode());
21 | }
22 |
23 | [Test]
24 | [TestCase("TWFu", "Man")]
25 | [TestCase("V29tYW4=", "Woman")]
26 | [TestCase("bGlnaHQgd29yay4=", "light work.")]
27 | [TestCase("bGlnaHQgd29yaw==", "light work")]
28 | public void Base64Decode_01(string base64, string s)
29 | {
30 | Assert.AreEqual(s, base64.Base64Decode().ToUtf8String());
31 | }
32 |
33 | [Test]
34 | [TestCase("Man", "TWFu")]
35 | [TestCase("Woman", "V29tYW4")]
36 | [TestCase("light work.", "bGlnaHQgd29yay4")]
37 | [TestCase("light work", "bGlnaHQgd29yaw")]
38 | public void Base64UrlEncode_01(string s, string base64Url)
39 | {
40 | Assert.AreEqual(base64Url, s.ToUtf8Bytes().Base64UrlEncode());
41 | }
42 |
43 | [Test]
44 | [TestCase("TWFu", "Man")]
45 | [TestCase("V29tYW4", "Woman")]
46 | [TestCase("bGlnaHQgd29yay4", "light work.")]
47 | [TestCase("bGlnaHQgd29yaw", "light work")]
48 | public void Base64UrlDecode_01(string base64Url, string s)
49 | {
50 | Assert.AreEqual(s, base64Url.Base64UrlDecode().ToUtf8String());
51 | }
52 |
53 | [Explicit]
54 | [Test]
55 | [Category("slow")]
56 | public void Perf_Base64Encode()
57 | {
58 | var bytes = "The quick brown fox jumps over the lazy dog.".ToUtf8Bytes();
59 | var sw = Stopwatch.StartNew();
60 | for (int i = 0; i < iterations; i++)
61 | {
62 | var base64 = bytes.Base64Encode();
63 | }
64 | sw.Stop();
65 | Console.WriteLine(sw.ElapsedMilliseconds);
66 | }
67 |
68 | [Explicit]
69 | [Test]
70 | [Category("slow")]
71 | public void Perf_Base64Decode()
72 | {
73 | var base64 = "ahm6a83henmp6ts0c9s6yxve41k6yy10d9tptw3k41qqcsbj41t6gs90dhgqmy90chqpebg=";
74 | var sw = Stopwatch.StartNew();
75 | for (int i = 0; i < iterations; i++)
76 | {
77 | var bytes = base64.Base64Decode();
78 | }
79 | sw.Stop();
80 | Console.WriteLine(sw.ElapsedMilliseconds);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/BitSetTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using rm.Extensions;
4 |
5 | namespace rm.ExtensionsTest;
6 |
7 | [TestFixture]
8 | public class BitSetTest
9 | {
10 | [Test]
11 | public void Add01()
12 | {
13 | var bitset = new BitSet(1023);
14 |
15 | Assert.IsFalse(bitset.Has(250));
16 | bitset.Add(250);
17 | Assert.IsTrue(bitset.Has(250));
18 |
19 | Assert.IsFalse(bitset.Has(0));
20 | bitset.Add(0);
21 | Assert.IsTrue(bitset.Has(0));
22 |
23 | Assert.IsFalse(bitset.Has(1023));
24 | bitset.Add(1023);
25 | Assert.IsTrue(bitset.Has(1023));
26 |
27 | bitset.Add(250);
28 | Assert.IsTrue(bitset.Has(250));
29 | }
30 |
31 | [Test]
32 | public void Add01u()
33 | {
34 | var bitset = new BitSet((uint)1023);
35 |
36 | Assert.IsFalse(bitset.Has((uint)250));
37 | bitset.Add((uint)250);
38 | Assert.IsTrue(bitset.Has((uint)250));
39 |
40 | Assert.IsFalse(bitset.Has((uint)0));
41 | bitset.Add((uint)0);
42 | Assert.IsTrue(bitset.Has((uint)0));
43 |
44 | Assert.IsFalse(bitset.Has((uint)1023));
45 | bitset.Add((uint)1023);
46 | Assert.IsTrue(bitset.Has((uint)1023));
47 |
48 | bitset.Add((uint)250);
49 | Assert.IsTrue(bitset.Has((uint)250));
50 | }
51 |
52 | [Test]
53 | public void Remove01()
54 | {
55 | var bitset = new BitSet(2000);
56 |
57 | bitset.Remove(750);
58 | Assert.IsFalse(bitset.Has(750));
59 | bitset.Add(750);
60 | Assert.IsTrue(bitset.Has(750));
61 | bitset.Remove(750);
62 | Assert.IsFalse(bitset.Has(750));
63 |
64 | bitset.Remove(0);
65 | Assert.IsFalse(bitset.Has(0));
66 | bitset.Add(0);
67 | Assert.IsTrue(bitset.Has(0));
68 | bitset.Remove(0);
69 | Assert.IsFalse(bitset.Has(0));
70 |
71 | bitset.Remove(1023);
72 | Assert.IsFalse(bitset.Has(1023));
73 | bitset.Add(1023);
74 | Assert.IsTrue(bitset.Has(1023));
75 | bitset.Remove(1023);
76 | Assert.IsFalse(bitset.Has(1023));
77 |
78 | bitset.Remove(750);
79 | Assert.IsFalse(bitset.Has(750));
80 | }
81 |
82 | [Test]
83 | public void Remove01u()
84 | {
85 | var bitset = new BitSet((uint)2000);
86 |
87 | bitset.Remove((uint)750);
88 | Assert.IsFalse(bitset.Has((uint)750));
89 | bitset.Add((uint)750);
90 | Assert.IsTrue(bitset.Has((uint)750));
91 | bitset.Remove((uint)750);
92 | Assert.IsFalse(bitset.Has((uint)750));
93 |
94 | bitset.Remove((uint)0);
95 | Assert.IsFalse(bitset.Has((uint)0));
96 | bitset.Add((uint)0);
97 | Assert.IsTrue(bitset.Has((uint)0));
98 | bitset.Remove((uint)0);
99 | Assert.IsFalse(bitset.Has((uint)0));
100 |
101 | bitset.Remove((uint)1023);
102 | Assert.IsFalse(bitset.Has((uint)1023));
103 | bitset.Add((uint)1023);
104 | Assert.IsTrue(bitset.Has((uint)1023));
105 | bitset.Remove((uint)1023);
106 | Assert.IsFalse(bitset.Has((uint)1023));
107 |
108 | bitset.Remove((uint)750);
109 | Assert.IsFalse(bitset.Has((uint)750));
110 | }
111 |
112 | [Test]
113 | public void Toggle01()
114 | {
115 | var bitset = new BitSet(1500);
116 |
117 | Assert.IsFalse(bitset.Has(500));
118 | bitset.Toggle(500);
119 | Assert.IsTrue(bitset.Has(500));
120 | bitset.Toggle(500);
121 | Assert.IsFalse(bitset.Has(500));
122 |
123 | Assert.IsFalse(bitset.Has(0));
124 | bitset.Toggle(0);
125 | Assert.IsTrue(bitset.Has(0));
126 | bitset.Toggle(0);
127 | Assert.IsFalse(bitset.Has(0));
128 |
129 | Assert.IsFalse(bitset.Has(1023));
130 | bitset.Toggle(1023);
131 | Assert.IsTrue(bitset.Has(1023));
132 | bitset.Toggle(1023);
133 | Assert.IsFalse(bitset.Has(1023));
134 | }
135 |
136 | [Test]
137 | public void Toggle01u()
138 | {
139 | var bitset = new BitSet((uint)1500);
140 |
141 | Assert.IsFalse(bitset.Has((uint)500));
142 | bitset.Toggle((uint)500);
143 | Assert.IsTrue(bitset.Has((uint)500));
144 | bitset.Toggle((uint)500);
145 | Assert.IsFalse(bitset.Has((uint)500));
146 |
147 | Assert.IsFalse(bitset.Has((uint)0));
148 | bitset.Toggle((uint)0);
149 | Assert.IsTrue(bitset.Has((uint)0));
150 | bitset.Toggle((uint)0);
151 | Assert.IsFalse(bitset.Has((uint)0));
152 |
153 | Assert.IsFalse(bitset.Has((uint)1023));
154 | bitset.Toggle((uint)1023);
155 | Assert.IsTrue(bitset.Has((uint)1023));
156 | bitset.Toggle((uint)1023);
157 | Assert.IsFalse(bitset.Has((uint)1023));
158 | }
159 |
160 | [Test]
161 | public void IEnumerable01()
162 | {
163 | var bitset = new BitSet(255);
164 | var inputs = new[] { 0, 30, 31, 32, 33, 100, 200, 255 };
165 | foreach (var item in inputs)
166 | {
167 | bitset.Add(item);
168 | }
169 | var size = 0;
170 | foreach (int item in bitset)
171 | {
172 | size++;
173 | }
174 | Assert.AreEqual(inputs.Length, size);
175 | }
176 |
177 | [Test]
178 | public void IEnumerable01u()
179 | {
180 | var bitset = new BitSet((uint)255);
181 | var inputs = new uint[] { 0, 30, 31, 32, 33, 100, 200, 255 };
182 | foreach (var item in inputs)
183 | {
184 | bitset.Add(item);
185 | }
186 | var size = 0;
187 | foreach (var item in bitset)
188 | {
189 | size++;
190 | }
191 | Assert.AreEqual(inputs.Length, size);
192 | }
193 |
194 | [Test]
195 | public void Ctor01()
196 | {
197 | Assert.DoesNotThrow(() => { var bitset = new BitSet(0); });
198 | Assert.Throws(() => { var bitset = new BitSet(-1); });
199 | }
200 |
201 | [Test]
202 | public void Ctor01u()
203 | {
204 | Assert.DoesNotThrow(() => { var bitset = new BitSet((uint)0); });
205 | }
206 |
207 | [Test]
208 | public void Count01()
209 | {
210 | var bitset = new BitSet(15);
211 | var inputs = new[] { 0, 1, 2, 3, 4 };
212 | foreach (var item in inputs)
213 | {
214 | bitset.Add(item);
215 | bitset.Add(item);
216 | }
217 | Assert.AreEqual(bitset.Count, 5);
218 | for (int i = 0; i < 2; i++)
219 | {
220 | bitset.Remove(inputs[i]);
221 | bitset.Remove(inputs[i]);
222 | }
223 | Assert.AreEqual(bitset.Count, 3);
224 | foreach (var item in inputs)
225 | {
226 | bitset.Toggle(item);
227 | }
228 | Assert.AreEqual(bitset.Count, 2);
229 | }
230 |
231 | [Test]
232 | public void Count01u()
233 | {
234 | var bitset = new BitSet(15);
235 | var inputs = new uint[] { 0, 1, 2, 3, 4 };
236 | foreach (var item in inputs)
237 | {
238 | bitset.Add(item);
239 | bitset.Add(item);
240 | }
241 | Assert.AreEqual(bitset.Count, 5);
242 | for (int i = 0; i < 2; i++)
243 | {
244 | bitset.Remove(inputs[i]);
245 | bitset.Remove(inputs[i]);
246 | }
247 | Assert.AreEqual(bitset.Count, 3);
248 | foreach (var item in inputs)
249 | {
250 | bitset.Toggle(item);
251 | }
252 | Assert.AreEqual(bitset.Count, 2);
253 | }
254 |
255 | [Test]
256 | public void Clear01()
257 | {
258 | var bitset = new BitSet(20);
259 | foreach (var item in new[] { 0, 1, 2, 3, 4 })
260 | {
261 | bitset.Add(item);
262 | }
263 | Assert.AreNotEqual(0, bitset.Count);
264 | bitset.Clear();
265 | Assert.AreEqual(0, bitset.Count);
266 | }
267 |
268 | [Test]
269 | public void FullCapacity01()
270 | {
271 | int max = 50;
272 | var bitset = new BitSet(max);
273 | for (int i = 0; i <= max; i++)
274 | {
275 | bitset.Add(i);
276 | }
277 | Assert.AreEqual(max + 1, bitset.Count);
278 | for (int i = 0; i <= max; i++)
279 | {
280 | Assert.IsTrue(bitset.Has(i));
281 | }
282 | }
283 |
284 | [Test]
285 | public void FullCapacity01u()
286 | {
287 | uint max = 50;
288 | var bitset = new BitSet(max);
289 | for (uint i = 0; i <= max; i++)
290 | {
291 | bitset.Add(i);
292 | }
293 | Assert.AreEqual(max + 1, bitset.Count);
294 | for (uint i = 0; i <= max; i++)
295 | {
296 | Assert.IsTrue(bitset.Has(i));
297 | }
298 | }
299 |
300 | [Test]
301 | public void Capacity01()
302 | {
303 | Assert.AreEqual(1, new BitSet(30).flags.Length);
304 | Assert.AreEqual(1, new BitSet(31).flags.Length);
305 | Assert.AreEqual(2, new BitSet(32).flags.Length);
306 | Assert.AreEqual(2, new BitSet(62).flags.Length);
307 | Assert.AreEqual(2, new BitSet(63).flags.Length);
308 | Assert.AreEqual(3, new BitSet(64).flags.Length);
309 | }
310 |
311 | [Test]
312 | public void Capacity01u()
313 | {
314 | Assert.AreEqual(1, new BitSet((uint)30).flags.Length);
315 | Assert.AreEqual(1, new BitSet((uint)31).flags.Length);
316 | Assert.AreEqual(2, new BitSet((uint)32).flags.Length);
317 | Assert.AreEqual(2, new BitSet((uint)62).flags.Length);
318 | Assert.AreEqual(2, new BitSet((uint)63).flags.Length);
319 | Assert.AreEqual(3, new BitSet((uint)64).flags.Length);
320 | }
321 |
322 | [Test]
323 | public void Capacity02()
324 | {
325 | Assert.AreEqual((int.MaxValue / 32) + 1, new BitSet(int.MaxValue).flags.Length);
326 | }
327 |
328 | [Test]
329 | public void Capacity02u()
330 | {
331 | Assert.AreEqual((uint.MaxValue / 32) + 1, new BitSet(uint.MaxValue).flags.Length);
332 | }
333 |
334 | [Test]
335 | public void OutOfRange01()
336 | {
337 | var bitset = new BitSet(1023);
338 | Assert.Throws(() => bitset.Has(-1));
339 | Assert.Throws(() => bitset.Has(1024));
340 | Assert.Throws(() => bitset.Add(-1));
341 | Assert.Throws(() => bitset.Add(1024));
342 | Assert.Throws(() => bitset.Remove(-1));
343 | Assert.Throws(() => bitset.Remove(1024));
344 | Assert.Throws(() => bitset.Toggle(-1));
345 | Assert.Throws(() => bitset.Toggle(1024));
346 | }
347 |
348 | [Test]
349 | public void OutOfRange01u()
350 | {
351 | var bitset = new BitSet(1023);
352 | Assert.Throws(() => bitset.Has((uint)1024));
353 | Assert.Throws(() => bitset.Add((uint)1024));
354 | Assert.Throws(() => bitset.Remove((uint)1024));
355 | Assert.Throws(() => bitset.Toggle((uint)1024));
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/CircularQueueTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using rm.Extensions;
4 |
5 | namespace rm.ExtensionsTest;
6 |
7 | [TestFixture]
8 | public class CircularQueueTest
9 | {
10 | [Test]
11 | public void Enqueue01()
12 | {
13 | var capacity = 4;
14 | var cq = new CircularQueue(capacity);
15 | for (int i = 0; i < 6; i++)
16 | { cq.Enqueue(i); }
17 | Assert.AreEqual(capacity, cq.Count());
18 | }
19 |
20 | [Test]
21 | public void Dequeue01()
22 | {
23 | var capacity = 4;
24 | var cq = new CircularQueue(capacity);
25 | for (int i = 0; i < capacity; i++)
26 | { cq.Enqueue(i); }
27 | for (int i = 0; i < capacity; i++)
28 | {
29 | Assert.AreEqual(i, cq.Dequeue());
30 | Assert.AreEqual(capacity - (i + 1), cq.Count());
31 | }
32 | Assert.Throws(() => cq.Dequeue());
33 | }
34 |
35 | [Test]
36 | public void EnqueueDequeue01()
37 | {
38 | var capacity = 4;
39 | var cq = new CircularQueue(capacity);
40 | cq.Enqueue(0);
41 | cq.Enqueue(1);
42 | cq.Enqueue(2);
43 | cq.Enqueue(3);
44 | cq.Dequeue();
45 | cq.Dequeue();
46 | Assert.AreEqual(2, cq.Peek());
47 | Assert.AreEqual(3, cq.PeekTail());
48 | cq.Enqueue(4);
49 | cq.Enqueue(5);
50 | Assert.AreEqual(2, cq.Peek());
51 | Assert.AreEqual(5, cq.PeekTail());
52 | cq.Enqueue(6);
53 | cq.Enqueue(7);
54 | Assert.AreEqual(4, cq.Peek());
55 | Assert.AreEqual(7, cq.PeekTail());
56 | }
57 |
58 | [Test]
59 | public void PeekPeekTail01()
60 | {
61 | var capacity = 4;
62 | var cq = new CircularQueue(capacity);
63 | for (int i = 0; i < 6; i++)
64 | { cq.Enqueue(i); }
65 | Assert.AreEqual(capacity, cq.Count());
66 | Assert.AreEqual(2, cq.Peek());
67 | Assert.AreEqual(5, cq.PeekTail());
68 | }
69 |
70 | [Test]
71 | public void IsEmpty01()
72 | {
73 | var cq = new CircularQueue(1);
74 | Assert.IsTrue(cq.IsEmpty());
75 | cq.Enqueue(1);
76 | Assert.IsFalse(cq.IsEmpty());
77 | cq.Dequeue();
78 | Assert.IsTrue(cq.IsEmpty());
79 | }
80 |
81 | [Test]
82 | public void Count01()
83 | {
84 | var cq = new CircularQueue(4);
85 | Assert.AreEqual(0, cq.Count());
86 | cq.Enqueue(1);
87 | cq.Enqueue(1);
88 | Assert.AreEqual(2, cq.Count());
89 | cq.Dequeue();
90 | Assert.AreEqual(1, cq.Count());
91 | cq.Dequeue();
92 | Assert.AreEqual(0, cq.Count());
93 | }
94 |
95 | [Test]
96 | public void Capacity01()
97 | {
98 | var capacity = 4;
99 | var cq = new CircularQueue(capacity);
100 | Assert.AreEqual(capacity, cq.Capacity());
101 | for (int i = 0; i < 6; i++)
102 | { cq.Enqueue(i); }
103 | Assert.AreEqual(capacity, cq.Capacity());
104 | }
105 |
106 | [Test]
107 | public void Clear01()
108 | {
109 | var capacity = 4;
110 | var cq = new CircularQueue(capacity);
111 | Assert.AreEqual(capacity, cq.Capacity());
112 | for (int i = 0; i < 6; i++)
113 | { cq.Enqueue(i); }
114 | Assert.AreEqual(capacity, cq.Count());
115 | cq.Clear();
116 | Assert.AreEqual(0, cq.Count());
117 | for (int i = 0; i < 6; i++)
118 | { cq.Enqueue(i); }
119 | Assert.AreEqual(capacity, cq.Count());
120 | }
121 |
122 | [Test]
123 | public void ArgumentOutOfRange01()
124 | {
125 | Assert.Throws(() => new CircularQueue(0));
126 | Assert.Throws(() => new CircularQueue(-1));
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/CircularStackTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using NUnit.Framework;
3 | using rm.Extensions;
4 |
5 | namespace rm.ExtensionsTest;
6 |
7 | [TestFixture]
8 | public class CircularStackTest
9 | {
10 | [Test]
11 | public void Push01()
12 | {
13 | var capacity = 4;
14 | var cq = new CircularStack(capacity);
15 | for (int i = 0; i < 6; i++)
16 | { cq.Push(i); }
17 | Assert.AreEqual(capacity, cq.Count());
18 | }
19 |
20 | [Test]
21 | public void Pop01()
22 | {
23 | var capacity = 4;
24 | var cq = new CircularStack(capacity);
25 | for (int i = 0; i < capacity; i++)
26 | { cq.Push(i); }
27 | for (int i = 0; i < capacity; i++)
28 | {
29 | Assert.AreEqual(capacity - (i + 1), cq.Pop());
30 | Assert.AreEqual(capacity - (i + 1), cq.Count());
31 | }
32 | Assert.Throws(() => cq.Pop());
33 | }
34 |
35 | [Test]
36 | public void PushPop01()
37 | {
38 | var capacity = 4;
39 | var cq = new CircularStack(capacity);
40 | cq.Push(0);
41 | cq.Push(1);
42 | cq.Push(2);
43 | cq.Push(3);
44 | cq.Pop();
45 | cq.Pop();
46 | Assert.AreEqual(1, cq.Peek());
47 | Assert.AreEqual(0, cq.PeekBottom());
48 | cq.Push(4);
49 | cq.Push(5);
50 | Assert.AreEqual(5, cq.Peek());
51 | Assert.AreEqual(0, cq.PeekBottom());
52 | cq.Push(6);
53 | cq.Push(7);
54 | Assert.AreEqual(7, cq.Peek());
55 | Assert.AreEqual(4, cq.PeekBottom());
56 | }
57 |
58 | [Test]
59 | public void PeekPeekBottom01()
60 | {
61 | var capacity = 4;
62 | var cq = new CircularStack(capacity);
63 | for (int i = 0; i < 6; i++)
64 | { cq.Push(i); }
65 | Assert.AreEqual(capacity, cq.Count());
66 | Assert.AreEqual(5, cq.Peek());
67 | Assert.AreEqual(2, cq.PeekBottom());
68 | }
69 |
70 | [Test]
71 | public void IsEmpty01()
72 | {
73 | var cq = new CircularStack(1);
74 | Assert.IsTrue(cq.IsEmpty());
75 | cq.Push(1);
76 | Assert.IsFalse(cq.IsEmpty());
77 | cq.Pop();
78 | Assert.IsTrue(cq.IsEmpty());
79 | }
80 |
81 | [Test]
82 | public void Count01()
83 | {
84 | var cq = new CircularStack(4);
85 | Assert.AreEqual(0, cq.Count());
86 | cq.Push(1);
87 | cq.Push(1);
88 | Assert.AreEqual(2, cq.Count());
89 | cq.Pop();
90 | Assert.AreEqual(1, cq.Count());
91 | cq.Pop();
92 | Assert.AreEqual(0, cq.Count());
93 | }
94 |
95 | [Test]
96 | public void Capacity01()
97 | {
98 | var capacity = 4;
99 | var cq = new CircularStack(capacity);
100 | Assert.AreEqual(capacity, cq.Capacity());
101 | for (int i = 0; i < 6; i++)
102 | { cq.Push(i); }
103 | Assert.AreEqual(capacity, cq.Capacity());
104 | }
105 |
106 | [Test]
107 | public void Clear01()
108 | {
109 | var capacity = 4;
110 | var cq = new CircularStack(capacity);
111 | Assert.AreEqual(capacity, cq.Capacity());
112 | for (int i = 0; i < 6; i++)
113 | { cq.Push(i); }
114 | Assert.AreEqual(capacity, cq.Count());
115 | cq.Clear();
116 | Assert.AreEqual(0, cq.Count());
117 | for (int i = 0; i < 6; i++)
118 | { cq.Push(i); }
119 | Assert.AreEqual(capacity, cq.Count());
120 | }
121 |
122 | [Test]
123 | public void ArgumentOutOfRange01()
124 | {
125 | Assert.Throws(() => new CircularStack(0));
126 | Assert.Throws(() => new CircularStack(-1));
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/DateTimeExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using NUnit.Framework;
4 | using rm.Extensions;
5 |
6 | namespace rm.ExtensionsTest;
7 |
8 | [TestFixture]
9 | public class DateTimeExtensionTest
10 | {
11 | [Test]
12 | public void ToUtcFormatString01()
13 | {
14 | var date = new DateTime(1994, 11, 05, 13, 15, 30, DateTimeKind.Utc);
15 | Assert.AreEqual("1994-11-05T13:15:30.000Z", date.ToUtcFormatString());
16 | }
17 |
18 | [Test]
19 | public void ToSqlDateTimeMinUtc01()
20 | {
21 | var date = new DateTime().ToSqlDateTimeMinUtc();
22 | var expectedDate = new DateTime(1753, 1, 1, 0, 0, 0, DateTimeKind.Utc);
23 | Assert.AreEqual(expectedDate, date);
24 | Assert.AreEqual(expectedDate.Kind, date.Kind);
25 | }
26 |
27 | [Test]
28 | public void AsUtcKind01()
29 | {
30 | var date = DateTime.ParseExact(
31 | "4/1/2014 12:00:00 AM", "M/d/yyyy hh:mm:ss tt", CultureInfo.CurrentCulture);
32 | Assert.AreEqual(DateTimeKind.Unspecified, date.Kind);
33 | date = date.AsUtcKind();
34 | Assert.AreEqual(DateTimeKind.Utc, date.Kind);
35 | var expectedDate = new DateTime(2014, 4, 1, 0, 0, 0, DateTimeKind.Utc);
36 | Assert.AreEqual(expectedDate, date);
37 | Assert.AreEqual(expectedDate.Kind, date.Kind);
38 | Assert.AreEqual(expectedDate, date.ToUniversalTime());
39 | Assert.AreEqual(expectedDate.Kind, date.ToUniversalTime().Kind);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/DecimalExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using NUnit.Framework;
2 | using rm.Extensions;
3 |
4 | namespace rm.ExtensionsTest;
5 |
6 | [TestFixture]
7 | public class DecimalExtensionTest
8 | {
9 | [Test]
10 | public void TruncateTo01()
11 | {
12 | Assert.AreEqual(12.34m, 12.349m.TruncateTo(2));
13 | Assert.AreEqual(12.33m, 12.339m.TruncateTo(2));
14 | Assert.AreEqual(12m, 12.999m.TruncateTo(0));
15 | Assert.AreEqual(11m, 11.999m.TruncateTo(0));
16 |
17 | Assert.AreEqual(-12.34m, (-12.349m).TruncateTo(2));
18 | Assert.AreEqual(-12.33m, (-12.339m).TruncateTo(2));
19 | Assert.AreEqual(-12m, (-12.999m).TruncateTo(0));
20 | Assert.AreEqual(-11m, (-11.999m).TruncateTo(0));
21 |
22 | Assert.AreEqual(12.1234567890123456789012345678m, 12.1234567890123456789012345678m.TruncateTo(28));
23 | Assert.AreEqual(-12.1234567890123456789012345678m, (-12.1234567890123456789012345678m).TruncateTo(28));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/DictionaryExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using NUnit.Framework;
4 | using rm.Extensions;
5 |
6 | namespace rm.ExtensionsTest;
7 |
8 | [TestFixture]
9 | public class DictionaryExtensionTest
10 | {
11 | [Test]
12 | [TestCase(new[] { 1, 2 }, 3, 0)]
13 | [TestCase(new[] { 1, 2 }, 1, 1)]
14 | [TestCase(new[] { 0, 1, 2 }, 0, 0)]
15 | public void GetValueOrDefault01(int[] a, int key, int expected)
16 | {
17 | var dictionary = a.ToDictionary(x => x);
18 | Assert.AreEqual(expected, dictionary.GetValueOrDefault(key));
19 | }
20 |
21 | [Test]
22 | [TestCase(new[] { 1, 2 }, 3, null)]
23 | [TestCase(new[] { 1, 2 }, 1, "1")]
24 | [TestCase(new[] { 0, 1, 2 }, 0, "0")]
25 | public void GetValueOrDefault02(int[] a, int key, string expected)
26 | {
27 | var dictionary = a.ToDictionary(x => x, y => y.ToString());
28 | Assert.AreEqual(expected, dictionary.GetValueOrDefault(key));
29 | }
30 |
31 | [Test]
32 | public void AsReadOnly01()
33 | {
34 | var dictionary = new[] { 0, 1, 2 }.ToDictionary(x => x, y => y.ToString()).AsReadOnly();
35 | Assert.Throws(() => dictionary[5] = "5");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/rm.ExtensionsTest/EnumExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using NUnit.Framework;
5 | using rm.Extensions;
6 | using Sc = System.ComponentModel;
7 |
8 | namespace rm.ExtensionsTest;
9 |
10 | public enum Temperature
11 | {
12 | C = 1,
13 | F
14 | }
15 |
16 | public enum Color
17 | {
18 | [Sc.Description("Red color")]
19 | Red = 1,
20 | Green,
21 | [Sc.Description("Blue color")]
22 | Blue,
23 | [Sc.Description("")]
24 | Black,
25 | [Sc.Description()]
26 | White,
27 | }
28 |
29 | public enum Grade
30 | {
31 | Toddler = 1,
32 | [Sc.Description("Pre-K")]
33 | PreK,
34 | Kindergarten,
35 | [Sc.Description("1")]
36 | One,
37 | [Sc.Description("2")]
38 | Two,
39 | [Sc.Description("3")]
40 | Three,
41 | [Sc.Description("4")]
42 | Four,
43 | [Sc.Description("5")]
44 | Five,
45 | [Sc.Description("6")]
46 | Six,
47 | [Sc.Description("7")]
48 | Seven,
49 | [Sc.Description("8")]
50 | Eight,
51 | [Sc.Description("9")]
52 | Nine,
53 | [Sc.Description("10")]
54 | Ten,
55 | [Sc.Description("11")]
56 | Eleven,
57 | [Sc.Description("12")]
58 | Twelve,
59 | College
60 | }
61 |
62 | public enum EmptyEnum { }
63 |
64 | [TestFixture]
65 | public class EnumExtensionTest
66 | {
67 | [Test]
68 | public void Parse01()
69 | {
70 | Assert.AreEqual(Color.Red, "Red".Parse());
71 | }
72 |
73 | [Test]
74 | public void Parse02()
75 | {
76 | Assert.Throws(() => { "Red".Parse(); });
77 | }
78 |
79 | [Test]
80 | public void TryParse01()
81 | {
82 | Color color;
83 | Assert.IsTrue("Red".TryParse(out color));
84 | Assert.AreEqual(Color.Red, color);
85 | }
86 |
87 | [Test]
88 | public void TryParse02()
89 | {
90 | Temperature t;
91 | Assert.IsFalse("Red".TryParse(out t));
92 | Assert.AreNotEqual(Color.Red, t);
93 | Assert.AreEqual(0, (int)t);
94 | }
95 |
96 | [Test]
97 | public void GetDescription01()
98 | {
99 | Assert.AreEqual("Red color", Color.Red.GetDescription());
100 | Assert.AreEqual(Color.Green.ToString(), Color.Green.GetDescription());
101 | Assert.AreEqual(Color.Black.ToString(), Color.Black.GetDescription());
102 | Assert.AreEqual(Color.White.ToString(), Color.White.GetDescription());
103 | }
104 |
105 | [Test]
106 | public void UnsupportedEnumValueException01()
107 | {
108 | var enumValue = (Color)0;
109 | var TEnum = enumValue.GetType();
110 | var ex = Assert.Throws>(() =>
111 | {
112 | enumValue.GetEnumName();
113 | });
114 | Assert.AreEqual($"Value {enumValue} of enum {TEnum.Name} is not supported.", ex.Message);
115 | }
116 |
117 | [Test]
118 | public void GetDescription02()
119 | {
120 | Assert.Throws>(() =>
121 | {
122 | ((Color)0).GetDescription();
123 | });
124 | }
125 |
126 | [Test]
127 | public void GetEnumValue01()
128 | {
129 | Assert.AreEqual(Color.Red, "Red".GetEnumValue());
130 | Assert.Throws>(() =>
131 | {
132 | "UnsupportedEnumValue".GetEnumValue();
133 | });
134 | }
135 |
136 | [Test]
137 | public void GetEnumValues01()
138 | {
139 | var colors = EnumExtension.GetEnumValues();
140 | Assert.AreEqual(5, colors.Count());
141 | Assert.IsTrue(colors.Contains(Color.Red));
142 | Assert.IsTrue(colors.Contains(Color.Green));
143 | Assert.IsTrue(colors.Contains(Color.Blue));
144 | }
145 |
146 | [Test(Description = "GetEnumValue() v/s Enum.ToString() speed test.")]
147 | [Category("slow")]
148 | public void GetEnumValue02()
149 | {
150 | Action, string> speedTest = (testPredicate, testName) =>
151 | {
152 | var iterations = 1000000;
153 | DateTime datetime;
154 | TimeSpan timespan;
155 | datetime = DateTime.Now;
156 | foreach (var item in Enumerable.Range(0, iterations))
157 | {
158 | if (testPredicate())
159 | { }
160 | }
161 | timespan = DateTime.Now - datetime;
162 | Console.WriteLine("{0}: \t{1}", testName, timespan.TotalSeconds);
163 | };
164 | // 15x slow
165 | speedTest(() => { return Color.Red.ToString() == "Red"; }, "Enum.ToString()");
166 | // 10x slow
167 | speedTest(() => { return "Red".Parse() == Color.Red; }, "Enum.Parse()");
168 | // 1.5x slow
169 | speedTest(() => { return "Red".GetEnumValue() == Color.Red; }, "GetEnumValue()");
170 | // fastest
171 | speedTest(() => { return Color.Red.GetEnumName() == "Red"; }, "GetEnumName()");
172 | }
173 |
174 | [Test]
175 | public void GetEnumNames01()
176 | {
177 | var colors = EnumExtension.GetEnumNames();
178 | Assert.AreEqual(5, colors.Count());
179 | Assert.IsTrue(colors.Contains(Color.Red.ToString()));
180 | Assert.IsTrue(colors.Contains(Color.Green.ToString()));
181 | Assert.IsTrue(colors.Contains(Color.Blue.ToString()));
182 | }
183 |
184 | [Test]
185 | public void GetEnumNameToDescriptionMap01()
186 | {
187 | var colorsMap = EnumExtension.GetEnumNameToDescriptionMap();
188 | Assert.AreEqual(5, colorsMap.Count());
189 | Assert.IsTrue(colorsMap.Contains(
190 | new KeyValuePair(Color.Red.ToString(), Color.Red.GetDescription())
191 | ));
192 | Assert.IsTrue(colorsMap.Contains(
193 | new KeyValuePair(Color.Green.ToString(), Color.Green.GetDescription())
194 | ));
195 | Assert.IsTrue(colorsMap.Contains(
196 | new KeyValuePair(Color.Blue.ToString(), Color.Blue.GetDescription())
197 | ));
198 | }
199 |
200 | [Test]
201 | public void GetEnumNameFromDescription01()
202 | {
203 | Assert.AreEqual(Color.Red.ToString(), "Red color".GetEnumNameFromDescription());
204 | Assert.Throws>(() =>
205 | {
206 | "UnsupportedEnumValue".GetEnumNameFromDescription();
207 | });
208 | }
209 |
210 | [Test]
211 | public void Sorting01()
212 | {
213 | var gradesUnsorted = new[] { "Pre-K", "1", "College", "2", "Toddler" };
214 | var grades = gradesUnsorted
215 | .Select(x => x.GetEnumValueFromDescription()).ToArray();
216 | Array.Sort(grades);
217 | Console.WriteLine("orig: {0}", string.Join(", ", gradesUnsorted));
218 | var gradesSorted = grades.Select(x => x.GetDescription());
219 | var gradesSortedFlat = string.Join(", ", gradesSorted);
220 | Assert.AreEqual("Toddler, Pre-K, 1, 2, College", gradesSortedFlat);
221 | Console.WriteLine("sort: {0}", gradesSortedFlat);
222 | }
223 |
224 | [Test]
225 | public void IsDefined01()
226 | {
227 | Assert.IsFalse(0.IsDefined