Skip to content

Instantly share code, notes, and snippets.

@neuecc
Last active November 13, 2017 13:14
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neuecc/9d6772153898633292614ca40537ea0d to your computer and use it in GitHub Desktop.
Save neuecc/9d6772153898633292614ca40537ea0d to your computer and use it in GitHub Desktop.
StringSpan(Split, GetBytes, Trim), StringKey(for Dictionary Key as string, char[], StringSpan)
using System;
using System.Text;
public static class StringSplitExtensions
{
public static SplitCharEnumerator SplitSlice(this string str, char separator)
{
return new SplitCharEnumerator(str, separator);
}
public static SplitStringEnumerator SplitSlice(this string str, string separator)
{
return new SplitStringEnumerator(str, separator);
}
public struct SplitCharEnumerator
{
readonly string str;
readonly char separator;
int index;
public SplitCharEnumerator(string str, char separator) : this()
{
this.str = str;
this.separator = separator;
}
public bool TryMoveNext(out StringSpan split)
{
if (index == -1)
{
split = default(StringSpan);
return false;
}
var offset = index;
index = str.IndexOf(separator, offset);
if (index == -1)
{
split = new StringSpan(str, offset, str.Length - offset);
return true;
}
else
{
split = new StringSpan(str, offset, index - offset);
index++;
return true;
}
}
}
public struct SplitStringEnumerator
{
readonly string str;
readonly string separator;
int index;
public SplitStringEnumerator(string str, string separator) : this()
{
this.str = str;
this.separator = separator;
}
public bool TryMoveNext(out StringSpan split)
{
if (index == -1)
{
split = default(StringSpan);
return false;
}
var offset = index;
index = str.IndexOf(separator, offset, StringComparison.Ordinal);
if (index == -1)
{
split = new StringSpan(str, offset, str.Length - offset);
return true;
}
else
{
split = new StringSpan(str, offset, index - offset);
index += separator.Length;
return true;
}
}
}
}
[Flags]
public enum TrimOptions
{
None = 0,
Whitespace = 1,
SingleQuotation = 2,
DoubleQuotation = 4
}
public struct StringSpan
{
readonly string str;
readonly int offset;
readonly int count;
public bool IsNull { get { return str == null; } }
public string String { get { return str; } }
public int Offset { get { return offset; } }
public int Count { get { return count; } }
public StringSpan(string str, int offset, int count)
{
this.str = str;
this.offset = offset;
this.count = count;
}
public StringSpan Trim(TrimOptions options = TrimOptions.Whitespace)
{
return TrimStart(options).TrimEnd(options);
}
public StringSpan TrimStart(TrimOptions options = TrimOptions.Whitespace)
{
if (options == TrimOptions.None) return this;
var end = count - offset;
if ((options & TrimOptions.Whitespace) != 0)
{
var i = offset;
for (; i < count; i++)
{
if (str[i] != ' ') break;
}
if (i != offset)
{
return new StringSpan(str, i, count - (i - offset)).TrimStart(options);
}
}
if ((options & TrimOptions.SingleQuotation) != 0)
{
var i = offset;
for (; i < count; i++)
{
if (str[i] != '\'') break;
}
if (i != offset)
{
return new StringSpan(str, i, count - (i - offset)).TrimStart(options);
}
}
if ((options & TrimOptions.DoubleQuotation) != 0)
{
var i = offset;
for (; i < count; i++)
{
if (str[i] != '\"') break;
}
if (i != offset)
{
return new StringSpan(str, i, count - (i - offset)).TrimStart(options);
}
}
return this;
}
public StringSpan TrimEnd(TrimOptions options = TrimOptions.Whitespace)
{
if (options == TrimOptions.None) return this;
var end = count - offset;
if ((options & TrimOptions.Whitespace) != 0)
{
var last = offset + count - 1;
var i = last;
for (; i >= offset; i--)
{
if (str[i] != ' ') break;
}
if (last != i)
{
return new StringSpan(str, offset, i - offset + 1).TrimEnd(options);
}
}
if ((options & TrimOptions.SingleQuotation) != 0)
{
var last = offset + count - 1;
var i = last;
for (; i >= offset; i--)
{
if (str[i] != '\'') break;
}
if (last != i)
{
return new StringSpan(str, offset, i - offset + 1).TrimEnd(options);
}
}
if ((options & TrimOptions.DoubleQuotation) != 0)
{
var last = offset + count - 1;
var i = last;
for (; i >= offset; i--)
{
if (str[i] != '\"') break;
}
if (last != i)
{
return new StringSpan(str, offset, i - offset + 1).TrimEnd(options);
}
}
return this;
}
public static implicit operator StringSpan(string stringKey)
{
return new StringSpan(stringKey, 0, stringKey.Length);
}
public int GetBytes(Encoding encoding, byte[] bytes, int byteOffset)
{
return encoding.GetBytes(str, offset, count, bytes, byteOffset);
}
public override string ToString()
{
if (str == null) return null;
if (offset == 0 && count == str.Length) return str;
return str.Substring(offset, count);
}
}
public struct StringKey : IEquatable<StringKey>
{
StringSpan stringKey;
ArraySegment<char> charKey;
bool isString;
public string Key
{
get
{
return isString
? stringKey.ToString()
: (charKey.Array == null) ? null
: new string(charKey.Array, charKey.Offset, charKey.Count);
}
}
public StringKey(StringSpan stringKey)
{
this.stringKey = stringKey;
this.charKey = default(ArraySegment<char>);
this.isString = true;
}
public StringKey(ArraySegment<char> charKey)
{
this.stringKey = null;
this.charKey = charKey;
this.isString = false;
}
public StringKey(char[] charKey, int offset, int count)
{
this.stringKey = null;
this.charKey = new ArraySegment<char>(charKey, offset, count);
this.isString = false;
}
public static implicit operator StringKey(string stringKey)
{
return new StringKey(stringKey);
}
public static implicit operator StringKey(StringSpan stringKey)
{
return new StringKey(stringKey);
}
public static implicit operator StringKey(ArraySegment<char> charKey)
{
return new StringKey(charKey.Array, charKey.Offset, charKey.Count);
}
// FNV1-1a hash https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
// Same as switch(string) http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/Compiler/MethodBodySynthesizer.Lowered.cs,26
public override int GetHashCode()
{
int hashCode = unchecked((int)2166136261);
if (isString)
{
if (stringKey.IsNull) return 0;
var text = stringKey.String;
var end = stringKey.Offset + stringKey.Count;
for (int i = stringKey.Offset; i < end; i++)
{
hashCode = unchecked((hashCode ^ text[i]) * 16777619);
}
return hashCode;
}
else
{
if (charKey.Array == null) return 0;
var text = charKey.Array;
var end = charKey.Offset + charKey.Count;
for (int i = charKey.Offset; i < end; i++)
{
hashCode = unchecked((hashCode ^ text[i]) * 16777619);
}
return hashCode;
}
}
public bool Equals(StringKey other)
{
if (this.isString && other.isString)
{
if (this.stringKey.IsNull && other.stringKey.IsNull) return true;
if (this.stringKey.IsNull) return false;
if (other.stringKey.IsNull) return false;
if (this.stringKey.Count != other.stringKey.Count) return false;
if (this.stringKey.Offset == 0 && this.stringKey.String.Length == this.stringKey.Count
&& other.stringKey.Offset == 0 && other.stringKey.String.Length == other.stringKey.Count)
{
return this.stringKey.String == other.stringKey.String;
}
var i1 = this.stringKey.Offset;
for (int i2 = other.stringKey.Offset; i2 < other.stringKey.Count; i2++, i1++)
{
if (this.stringKey.String[i1] != other.stringKey.String[i2]) return false;
}
return true;
}
if (this.isString && !other.isString)
{
if (this.stringKey.IsNull && other.charKey.Array == null) return true;
if (this.stringKey.IsNull) return false;
if (other.charKey.Array == null) return false;
if (this.stringKey.Count != other.charKey.Count) return false;
var i1 = this.stringKey.Offset;
for (int i2 = other.charKey.Offset; i2 < other.charKey.Count; i2++, i1++)
{
if (this.stringKey.String[i1] != other.charKey.Array[i2]) return false;
}
return true;
}
if (!this.isString && other.isString)
{
if (other.stringKey.IsNull && this.charKey.Array == null) return true;
if (other.stringKey.IsNull) return false;
if (this.charKey.Array == null) return false;
if (other.stringKey.Count != this.charKey.Count) return false;
var i1 = other.stringKey.Offset;
for (int i2 = this.charKey.Offset; i2 < this.charKey.Count; i2++, i1++)
{
if (other.stringKey.String[i1] != this.charKey.Array[i2]) return false;
}
return true;
}
{
if (this.charKey.Array == null && other.charKey.Array == null) return true;
if (this.charKey.Array == null) return false;
if (other.charKey.Array == null) return false;
if (this.charKey.Count != other.charKey.Count) return false;
var i1 = this.charKey.Offset;
for (int i2 = other.charKey.Offset; i2 < other.charKey.Count; i2++, i1++)
{
if (this.charKey.Array[i1] != other.charKey.Array[i2]) return false;
}
return true;
}
}
public override string ToString()
{
return Key;
}
}
@neuecc
Copy link
Author

neuecc commented Aug 10, 2017

Usage

static void Main()
{
    // sample dictionary for lookup
    var exists = new Dictionary<StringKey, int> { { "hoge", 100 }, { "huga", 300 } };

    // from outer env(stream, file, arguments or others)
    var args = "hoge,huga,tako";

    var enumerator = args.SplitSlice(','); // struct SplitCharEnumerator, no allocation
    while (enumerator.TryMoveNext(out var item)) // item == struct StringSpan, no allocation
    {
        // can check with String - StringSpan(or ArraySegment<char>)
        if (exists.TryGetValue(item, out var i))
        {
            Console.WriteLine(i);
        }
    }
}

@neuecc
Copy link
Author

neuecc commented Aug 12, 2017

// @ufcpp suggested more efficient StringKey
[StructLayout(LayoutKind.Explicit)]
public struct StringKey : IEquatable<StringKey>
{
    [FieldOffset(0)]
    StringSpan stringKey;

    [FieldOffset(0)]
    ArraySegment<char> charKey;

    [FieldOffset(0)]
    object firstField; // StringSpan.String or ArraySegment<char>.Array

    bool IsString { get { return firstField is string; } }

    public string Key
    {
        get
        {
            return IsString
                ? stringKey.ToString()
                : (charKey.Array == null) ? null
                                          : new string(charKey.Array, charKey.Offset, charKey.Count);
        }
    }

    public StringKey(StringSpan stringKey) : this()
    {
        this.stringKey = stringKey;
    }

    public StringKey(ArraySegment<char> charKey) : this()
    {
        this.charKey = charKey;
    }

    public StringKey(char[] charKey, int offset, int count) : this()
    {
        this.charKey = new ArraySegment<char>(charKey, offset, count);
    }

    public static implicit operator StringKey(string stringKey)
    {
        return new StringKey(stringKey);
    }

    public static implicit operator StringKey(StringSpan stringKey)
    {
        return new StringKey(stringKey);
    }

    public static implicit operator StringKey(ArraySegment<char> charKey)
    {
        return new StringKey(charKey.Array, charKey.Offset, charKey.Count);
    }

    // FNV1-1a hash https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
    // Same as switch(string) http://source.roslyn.io/#Microsoft.CodeAnalysis.CSharp/Compiler/MethodBodySynthesizer.Lowered.cs,26
    public override int GetHashCode()
    {
        int hashCode = unchecked((int)2166136261);

        if (IsString)
        {
            if (stringKey.IsNull) return 0;

            var text = stringKey.String;
            var end = stringKey.Offset + stringKey.Count;

            for (int i = stringKey.Offset; i < end; i++)
            {
                hashCode = unchecked((hashCode ^ text[i]) * 16777619);
            }

            return hashCode;
        }
        else
        {
            if (charKey.Array == null) return 0;

            var text = charKey.Array;
            var end = charKey.Offset + charKey.Count;

            for (int i = charKey.Offset; i < end; i++)
            {
                hashCode = unchecked((hashCode ^ text[i]) * 16777619);
            }

            return hashCode;
        }
    }

    public bool Equals(StringKey other)
    {
        if (this.IsString && other.IsString)
        {
            if (this.stringKey.IsNull && other.stringKey.IsNull) return true;
            if (this.stringKey.IsNull) return false;
            if (other.stringKey.IsNull) return false;
            if (this.stringKey.Count != other.stringKey.Count) return false;
            if (this.stringKey.Offset == 0 && this.stringKey.String.Length == this.stringKey.Count
             && other.stringKey.Offset == 0 && other.stringKey.String.Length == other.stringKey.Count)
            {
                return this.stringKey.String == other.stringKey.String;
            }

            var i1 = this.stringKey.Offset;
            for (int i2 = other.stringKey.Offset; i2 < other.stringKey.Count; i2++, i1++)
            {
                if (this.stringKey.String[i1] != other.stringKey.String[i2]) return false;
            }
            return true;
        }

        if (this.IsString && !other.IsString)
        {
            if (this.stringKey.IsNull && other.charKey.Array == null) return true;
            if (this.stringKey.IsNull) return false;
            if (other.charKey.Array == null) return false;
            if (this.stringKey.Count != other.charKey.Count) return false;

            var i1 = this.stringKey.Offset;
            for (int i2 = other.charKey.Offset; i2 < other.charKey.Count; i2++, i1++)
            {
                if (this.stringKey.String[i1] != other.charKey.Array[i2]) return false;
            }
            return true;
        }

        if (!this.IsString && other.IsString)
        {
            if (other.stringKey.IsNull && this.charKey.Array == null) return true;
            if (other.stringKey.IsNull) return false;
            if (this.charKey.Array == null) return false;
            if (other.stringKey.Count != this.charKey.Count) return false;

            var i1 = other.stringKey.Offset;
            for (int i2 = this.charKey.Offset; i2 < this.charKey.Count; i2++, i1++)
            {
                if (other.stringKey.String[i1] != this.charKey.Array[i2]) return false;
            }
            return true;
        }

        {
            if (this.charKey.Array == null && other.charKey.Array == null) return true;
            if (this.charKey.Array == null) return false;
            if (other.charKey.Array == null) return false;
            if (this.charKey.Count != other.charKey.Count) return false;

            var i1 = this.charKey.Offset;
            for (int i2 = other.charKey.Offset; i2 < other.charKey.Count; i2++, i1++)
            {
                if (this.charKey.Array[i1] != other.charKey.Array[i2]) return false;
            }
            return true;
        }
    }

    public override string ToString()
    {
        return Key;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment