In continuation to my previous question C# Confluent.Kafka SetValueDeserializer object deserialization, I have tried creating my custom deserializer to deserialize protobuf message but getting this error:
System.InvalidOperationException: 'Type is not expected, and no contract can be inferred: Ileco.Chimp.Proto.FinalValue'
on line:
return Serializer.Deserialize<T>(stream);
Here my consumer and deserializer:
class Worker
{
public static void Consumer(string brokerList, string connStr, string consumergroup, string topic, string cacertlocation)
{
var config = new ConsumerConfig
{
BootstrapServers = brokerList,
SecurityProtocol = SecurityProtocol.SaslSsl,
SocketTimeoutMs = 60000, //this corresponds to the Consumer config `request.timeout.ms`
SessionTimeoutMs = 30000,
SaslMechanism = SaslMechanism.Plain,
SaslUsername = "$ConnectionString",
SaslPassword = connStr,
SslCaLocation = cacertlocation,
GroupId = consumergroup,
AutoOffsetReset = AutoOffsetReset.Earliest,
BrokerVersionFallback = "1.0.0", //Event Hubs for Kafka Ecosystems supports Kafka v1.0+, a fallback to an older API will fail
//Debug = "security,broker,protocol" //Uncomment for librdkafka debugging information
};
using (var consumer = new ConsumerBuilder<string, FinalValue>(config)
.SetKeyDeserializer(Deserializers.Utf8)
.SetValueDeserializer(new MyCustomDeserializer<FinalValue>())
.SetErrorHandler((_, e) => Console.WriteLine($"Error: {e.Reason}"))
.Build())
{
CancellationTokenSource cts = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
consumer.Subscribe(topic);
Console.WriteLine("Consuming messages from topic: " + topic + ", broker(s): " + brokerList);
while (true)
{
try
{
var msg = consumer.Consume(cts.Token);
Console.WriteLine($"Received: '{msg.Message.Value}'");
//var bytes = Encoding.ASCII.GetBytes(msg.Message.Value);
//var fv = FromByteArray<ProtobufMsg>(bytes);
//var proto = ProtoDeserialize<ProtobufMsg>(bytes);
}
catch (ConsumeException e)
{
Console.WriteLine($"Consume error: {e.Error.Reason}");
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
public class MyCustomDeserializer<T> : IDeserializer<T>
{
public T Deserialize(ReadOnlySpan<byte> data, bool isNull, Confluent.Kafka.SerializationContext context)
{
using (var stream = new MemoryStream(data.ToArray()))
{
return Serializer.Deserialize<T>(stream);
}
}
}
FinalValue.proto
syntax = "proto3";
package ileco.chimp.proto;
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
option java_package = "ileco.chimp.proto";
option java_outer_classname = "FinalValueProtos";
message FinalValue {
google.protobuf.Timestamp timestamp = 1;
uint32 inputId = 2;
google.protobuf.DoubleValue value = 3;
uint32 sourceId = 4;
string inputGuid = 5;
}
FinalValue.cs
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: proto/FinalValue.proto
#pragma warning disable 1591, 0612, 3021
#region Designer generated code
using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
namespace Ileco.Chimp.Proto {
/// <summary>Holder for reflection information generated from proto/FinalValue.proto</summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public static partial class FinalValueReflection {
#region Descriptor
/// <summary>File descriptor for proto/FinalValue.proto</summary>
public static pbr::FileDescriptor Descriptor {
get { return descriptor; }
}
private static pbr::FileDescriptor descriptor;
static FinalValueReflection() {
byte[] descriptorData = global::System.Convert.FromBase64String(
string.Concat(
"ChZwcm90by9GaW5hbFZhbHVlLnByb3RvEhFpbGVjby5jaGltcC5wcm90bxof",
"Z29vZ2xlL3Byb3RvYnVmL3RpbWVzdGFtcC5wcm90bxoeZ29vZ2xlL3Byb3Rv",
"YnVmL3dyYXBwZXJzLnByb3RvIp4BCgpGaW5hbFZhbHVlEi0KCXRpbWVzdGFt",
"cBgBIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDwoHaW5wdXRJ",
"ZBgCIAEoDRIrCgV2YWx1ZRgDIAEoCzIcLmdvb2dsZS5wcm90b2J1Zi5Eb3Vi",
"bGVWYWx1ZRIQCghzb3VyY2VJZBgEIAEoDRIRCglpbnB1dEd1aWQYBSABKAlC",
"JQoRaWxlY28uY2hpbXAucHJvdG9CEEZpbmFsVmFsdWVQcm90b3NiBnByb3Rv",
"Mw=="));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, },
new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
new pbr::GeneratedClrTypeInfo(typeof(global::Ileco.Chimp.Proto.FinalValue), global::Ileco.Chimp.Proto.FinalValue.Parser, new[]{ "Timestamp", "InputId", "Value", "SourceId", "InputGuid" }, null, null, null)
}));
}
#endregion
}
#region Messages
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public sealed partial class FinalValue : pb::IMessage<FinalValue> {
private static readonly pb::MessageParser<FinalValue> _parser = new pb::MessageParser<FinalValue>(() => new FinalValue());
public static pb::MessageParser<FinalValue> Parser { get { return _parser; } }
public static pbr::MessageDescriptor Descriptor {
get { return global::Ileco.Chimp.Proto.FinalValueReflection.Descriptor.MessageTypes[0]; }
}
pbr::MessageDescriptor pb::IMessage.Descriptor {
get { return Descriptor; }
}
public FinalValue() {
OnConstruction();
}
partial void OnConstruction();
public FinalValue(FinalValue other) : this() {
Timestamp = other.timestamp_ != null ? other.Timestamp.Clone() : null;
inputId_ = other.inputId_;
Value = other.Value;
sourceId_ = other.sourceId_;
inputGuid_ = other.inputGuid_;
}
public FinalValue Clone() {
return new FinalValue(this);
}
/// <summary>Field number for the "timestamp" field.</summary>
public const int TimestampFieldNumber = 1;
private global::Google.Protobuf.WellKnownTypes.Timestamp timestamp_;
public global::Google.Protobuf.WellKnownTypes.Timestamp Timestamp {
get { return timestamp_; }
set {
timestamp_ = value;
}
}
/// <summary>Field number for the "inputId" field.</summary>
public const int InputIdFieldNumber = 2;
private uint inputId_;
public uint InputId {
get { return inputId_; }
set {
inputId_ = value;
}
}
/// <summary>Field number for the "value" field.</summary>
public const int ValueFieldNumber = 3;
private static readonly pb::FieldCodec<double?> _single_value_codec = pb::FieldCodec.ForStructWrapper<double>(26);
private double? value_;
public double? Value {
get { return value_; }
set {
value_ = value;
}
}
/// <summary>Field number for the "sourceId" field.</summary>
public const int SourceIdFieldNumber = 4;
private uint sourceId_;
public uint SourceId {
get { return sourceId_; }
set {
sourceId_ = value;
}
}
/// <summary>Field number for the "inputGuid" field.</summary>
public const int InputGuidFieldNumber = 5;
private string inputGuid_ = "";
public string InputGuid {
get { return inputGuid_; }
set {
inputGuid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
}
}
public override bool Equals(object other) {
return Equals(other as FinalValue);
}
public bool Equals(FinalValue other) {
if (ReferenceEquals(other, null)) {
return false;
}
if (ReferenceEquals(other, this)) {
return true;
}
if (!object.Equals(Timestamp, other.Timestamp)) return false;
if (InputId != other.InputId) return false;
if (Value != other.Value) return false;
if (SourceId != other.SourceId) return false;
if (InputGuid != other.InputGuid) return false;
return true;
}
public override int GetHashCode() {
int hash = 1;
if (timestamp_ != null) hash ^= Timestamp.GetHashCode();
if (InputId != 0) hash ^= InputId.GetHashCode();
if (value_ != null) hash ^= Value.GetHashCode();
if (SourceId != 0) hash ^= SourceId.GetHashCode();
if (InputGuid.Length != 0) hash ^= InputGuid.GetHashCode();
return hash;
}
public override string ToString() {
return pb::JsonFormatter.ToDiagnosticString(this);
}
public void WriteTo(pb::CodedOutputStream output) {
if (timestamp_ != null) {
output.WriteRawTag(10);
output.WriteMessage(Timestamp);
}
if (InputId != 0) {
output.WriteRawTag(16);
output.WriteUInt32(InputId);
}
if (value_ != null) {
_single_value_codec.WriteTagAndValue(output, Value);
}
if (SourceId != 0) {
output.WriteRawTag(32);
output.WriteUInt32(SourceId);
}
if (InputGuid.Length != 0) {
output.WriteRawTag(42);
output.WriteString(InputGuid);
}
}
public int CalculateSize() {
int size = 0;
if (timestamp_ != null) {
size += 1 + pb::CodedOutputStream.ComputeMessageSize(Timestamp);
}
if (InputId != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(InputId);
}
if (value_ != null) {
size += _single_value_codec.CalculateSizeWithTag(Value);
}
if (SourceId != 0) {
size += 1 + pb::CodedOutputStream.ComputeUInt32Size(SourceId);
}
if (InputGuid.Length != 0) {
size += 1 + pb::CodedOutputStream.ComputeStringSize(InputGuid);
}
return size;
}
public void MergeFrom(FinalValue other) {
if (other == null) {
return;
}
if (other.timestamp_ != null) {
if (timestamp_ == null) {
timestamp_ = new global::Google.Protobuf.WellKnownTypes.Timestamp();
}
Timestamp.MergeFrom(other.Timestamp);
}
if (other.InputId != 0) {
InputId = other.InputId;
}
if (other.value_ != null) {
if (value_ == null || other.Value != 0D) {
Value = other.Value;
}
}
if (other.SourceId != 0) {
SourceId = other.SourceId;
}
if (other.InputGuid.Length != 0) {
InputGuid = other.InputGuid;
}
}
public void MergeFrom(pb::CodedInputStream input) {
uint tag;
while ((tag = input.ReadTag()) != 0) {
switch(tag) {
default:
input.SkipLastField();
break;
case 10: {
if (timestamp_ == null) {
timestamp_ = new global::Google.Protobuf.WellKnownTypes.Timestamp();
}
input.ReadMessage(timestamp_);
break;
}
case 16: {
InputId = input.ReadUInt32();
break;
}
case 26: {
double? value = _single_value_codec.Read(input);
if (value_ == null || value != 0D) {
Value = value;
}
break;
}
case 32: {
SourceId = input.ReadUInt32();
break;
}
case 42: {
InputGuid = input.ReadString();
break;
}
}
}
}
}
#endregion
}
#endregion Designer generated code
As I noted yesterday, you appear to have used the Google .proto processing tools (protoc), but are using protobuf-net; if you want to use protobuf-net, similar command-line/IDE/build/etc tools exist that are compatible with the protobuf-net library, or you can use https://protogen.marcgravell.com/ for ad-hoc usage (to avoid having to install anything). Alternatively: continue using the Google schema tools, but use the Google library. Basically: they need to match.
The only minor gotcha here is that protobuf-net does not currently have explicit inbuilt support for DoubleValue; for reference: this can be considered as simply:
namespace Google.Protobuf.WellKnownTypes
{
[ProtoContract]
public sealed class DoubleValue
{
[ProtoMember(1)]
public double Value {get;set;}
}
}
I should probably find time to take all the types from wrappers.proto and allow them as double?, float?, long? etc - but it will need an additional marker, as Nullable<T> is already handled but with a different meaning (i.e. optional in .proto terms)
I'm a java developer and new to C#, I'm stuck with InvalidCastException on the following code below.
I have implemented a Queue, based on a custom Doubly Linked List implementation. Both implementations are generic, and thus, on the test code, I want to use a generic print method.
The test code below is just working fine;
using System;
using System.Collections.Generic;
namespace Queue01
{
public class TestQueue
{
public static void Main(string[] args)
{
Queue<int> queue = new Queue<int>();
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(6);
Console.WriteLine("*****");
PrintQueue(queue);
Console.WriteLine("*****");
int first = queue.Dequeue();
Console.WriteLine("Enqueued value : " + first);
Console.WriteLine("*****");
PrintQueue(queue);
}
public static void PrintQueue(object queue)
{
Queue<int> q = (Queue<int>)queue;
foreach (object i in q)
{
Console.WriteLine(i);
}
}
}
}
However, I want to make the PrintQueue static method working for all types, thus I've changed the method code as below;
public static void PrintQueue(object queue)
{
Queue<object> q = (Queue<object>)queue;
foreach (object i in q)
{
Console.WriteLine(i);
}
}
The whole test code which is not working is as below;
using System;
using System.Collections.Generic;
namespace Queue01
{
public class TestQueue
{
public static void Main(string[] args)
{
Queue<int> queue = new Queue<int>();
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(6);
Console.WriteLine("*****");
PrintQueue(queue);
Console.WriteLine("*****");
int first = queue.Dequeue();
Console.WriteLine("Enqueued value : " + first);
Console.WriteLine("*****");
PrintQueue(queue);
}
public static void PrintQueue(object queue)
{
Queue<object> q = (Queue<object>)queue;
foreach (object i in q)
{
Console.WriteLine(i);
}
}
}
}
And then I'm stuck with the InvalidCastException. Where is the problem and how can I fix this exception and what is the best practice to make this code generic?
On java, Object is the base, root class of every class instance. Thus, I've passed the stack instance as an object to the method, assuming that it won't be a problem because int, the alias for Int32 also extended from the Object.
Here down below, I am adding the whole rest files that I've used for compiling the test code above;
1) Here is the DoublyLinkedListNode class that I used in Doubly Linked List implemetation;
using System;
using System.Collections.Generic;
namespace Queue01
{
public class DoublyLinkedListNode<T>
{
// Fields
public T Value { get; set; }
public DoublyLinkedListNode<T> Previous { get; set; }
public DoublyLinkedListNode<T> Next { get; set; }
public DoublyLinkedListNode(T value)
{
Value = value;
}
}
}
2) Here is my DoublyLinkedList class which implements a doubly linked list for the queue implementation I am going to use;
using System;
using System.Collections.Generic;
namespace Queue01
{
public class DoublyLinkedList<T> : ICollection<T>
{
#region Fields
public DoublyLinkedListNode<T> Head { get; private set; }
public DoublyLinkedListNode<T> Tail { get; private set; }
#endregion
#region Constructor
#endregion
#region Add
public void AddFirst(T value)
{
AddFirst(new DoublyLinkedListNode<T>(value));
}
public void AddFirst(DoublyLinkedListNode<T> node)
{
DoublyLinkedListNode<T> temp = Head;
Head = node;
Head.Next = temp;
//if(Count == 0)
if (Empty)
{
Tail = Head;
}
else
{
temp.Previous = Head;
}
Count++;
}
public void AddLast(T value)
{
AddLast(new DoublyLinkedListNode<T>(value));
}
public void AddLast(DoublyLinkedListNode<T> node)
{
//if (Count == 0)
if (Empty)
{
Head = node;
}
else
{
Tail.Next = node;
node.Previous = Tail;
}
Tail = node;
Count++;
}
#endregion
#region Remove
public void RemoveFirst()
{
//if (Count != 0)
if (!Empty)
{
Head = Head.Next;
Count--;
if (Count == 0)
{
Tail = null;
}
else
{
Head.Previous = null;
}
}
}
public void RemoveLast()
{
//if (Count != 0)
if (!Empty)
{
if (Count == 1)
{
Head = null;
Tail = null;
}
else
{
Tail.Previous.Next = null;
Tail = Tail.Previous;
}
Count--;
}
}
#endregion
#region ICollection
public int Count
{
get;
private set;
}
public void Add(T item)
{
AddFirst(item);
}
public bool Contains(T item)
{
DoublyLinkedListNode<T> current = Head;
while (current != null)
{
if (current.Value.Equals(item))
{
return true;
}
current = current.Next;
}
return false;
}
public void CopyTo(T[] array, int arrayIndex)
{
DoublyLinkedListNode<T> current = Head;
while (current != null)
{
array[arrayIndex++] = current.Value;
current = current.Next;
}
}
public bool IsReadOnly
{
get
{
return false;
}
}
public bool Remove(T item)
{
DoublyLinkedListNode<T> previous = null;
DoublyLinkedListNode<T> current = Head;
while (current != null)
{
if (current.Value.Equals(item))
{
if (previous != null)
{
previous.Next = current.Next;
if (current.Next == null)
{
Tail = previous;
}
else
{
current.Next.Previous = previous;
}
Count--;
}
else
{
RemoveFirst();
}
return true;
}
previous = current;
current = current.Next;
}
return false;
}
public void Clear()
{
Head = null;
Tail = null;
Count = 0;
}
//System.Collections.Generic.IEnumerator<T> System.Collections.Generic.IEnumerable<T>.GetEnumerator()
public IEnumerator<T> GetEnumerator()
{
DoublyLinkedListNode<T> current = Head;
while (current != null)
{
yield return current.Value;
current = current.Next;
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
//return ((System.Collections.Generic.IEnumerable<T>)this).GetEnumerator();
return this.GetEnumerator();
}
#endregion
#region helper
public void PrintEnumerable()
{
IEnumerator<T> e = this.GetEnumerator();
while (e.MoveNext())
{
T val = e.Current;
Console.WriteLine("Value: " + val);
}
}
public void PrintReverse()
{
for (DoublyLinkedListNode<T> node = Tail; node != null; node = node.Previous)
{
Console.WriteLine(node.Value);
}
}
public bool isEmpty()
{
if (Count == 0)
{
return true;
}
return false;
}
public bool Empty { get { return isEmpty(); } }
public DoublyLinkedListNode<T> First { get { return this.Head; } }
public DoublyLinkedListNode<T> Last { get { return this.Tail; } }
#endregion
}
}
3) And this is my Queue implementation based on doubly linked list implemetation that I've used above;
using System;
using System.Collections.Generic;
namespace Queue01
{
public class Queue<T> : System.Collections.Generic.IEnumerable<T>
{
DoublyLinkedList<T> _items = new DoublyLinkedList<T>();
LinkedList<T> hede = new LinkedList<T>();
public void Enqueue(T item)
{
_items.AddLast(item);
}
public T Dequeue()
{
if(_items.Count == 0)
{
throw new InvalidOperationException("The queue is empty.");
}
T value = _items.First.Value;
_items.RemoveFirst();
return value;
}
public IEnumerator<T> GetEnumerator()
{
return this._items.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}
Fundamentally, you shouldn't try to make PrintQueue work for every object - you should try to make it work for any Queue<T>... and the simplest way of doing that is to make it generic:
public static void PrintQueue<T>(Queue<T> queue)
{
foreach (T item in queue)
{
Console.WriteLine(item);
}
}
Or you could be more general and accept an IEnumerable<T>:
public static void PrintSequence<T>(IEnumerable<T> queue)
{
foreach (T item in queue)
{
Console.WriteLine(item);
}
}
Your original code is failing because a Queue<int> isn't a Queue<object>... in fact, because Queue<T> isn't covariant in T, you can't convert Queue<string> to Queue<object>... but IEnumerable<T> is covariant, so:
Queue<string> stringQueue = new Queue<string>();
...
PrintSequence<object>(stringQueue);
... would be okay.
What about change PrintQueue method. Just like this:
public static void PrintQueue<T>(object queue)
{
var q = (Queue<T>)queue;
foreach (var i in q)
Console.WriteLine(i);
}
and use it like this:
PrintQueue<int>(queue);
change your code like this:
public static void PrintQueue(dynamic queue)
{
foreach (var i in queue)
{
Console.WriteLine(i);
}
}
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
Does anyone know where I can find an example of how to construct a trie in C#? I'm trying to take a dictionary/list of words and create a trie with it.
This is my own code, pulled from my answer to How to find a word from arrays of characters? :
public class Trie
{
public struct Letter
{
public const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static implicit operator Letter(char c)
{
return new Letter() { Index = Chars.IndexOf(c) };
}
public int Index;
public char ToChar()
{
return Chars[Index];
}
public override string ToString()
{
return Chars[Index].ToString();
}
}
public class Node
{
public string Word;
public bool IsTerminal { get { return Word != null; } }
public Dictionary<Letter, Node> Edges = new Dictionary<Letter, Node>();
}
public Node Root = new Node();
public Trie(string[] words)
{
for (int w = 0; w < words.Length; w++)
{
var word = words[w];
var node = Root;
for (int len = 1; len <= word.Length; len++)
{
var letter = word[len - 1];
Node next;
if (!node.Edges.TryGetValue(letter, out next))
{
next = new Node();
if (len == word.Length)
{
next.Word = word;
}
node.Edges.Add(letter, next);
}
node = next;
}
}
}
Take a look at this codeplex project:
https://github.com/gmamaladze/trienet
It is a library containing several different variants of well tested generic c# trie classes including patricia trie and parallel trie.
Trie – the simple trie, allows only prefix search, like .Where(s => s.StartsWith(searchString))
SuffixTrie - allows also infix search, like .Where(s => s.Contains(searchString))
PatriciaTrie – compressed trie, more compact, a bit more efficient during look-up, but a quite slower durig build-up.
SuffixPatriciaTrie – the same as PatriciaTrie, also enabling infix search.
ParallelTrie – very primitively implemented parallel data structure which allows adding data and retriving results from different threads simultaneusly.
A simple Trie implementation.
http://github.com/bharathkumarms/AlgorithmsMadeEasy/blob/master/AlgorithmsMadeEasy/Tries.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace AlgorithmsMadeEasy
{
class Tries
{
TrieNode root;
public void CreateRoot()
{
root = new TrieNode();
}
public void Add(char[] chars)
{
TrieNode tempRoot = root;
int total = chars.Count() - 1;
for (int i = 0; i < chars.Count(); i++)
{
TrieNode newTrie;
if (tempRoot.children.Keys.Contains(chars[i]))
{
tempRoot = tempRoot.children[chars[i]];
}
else
{
newTrie = new TrieNode();
if (total == i)
{
newTrie.endOfWord = true;
}
tempRoot.children.Add(chars[i], newTrie);
tempRoot = newTrie;
}
}
}
public bool FindPrefix(char[] chars)
{
TrieNode tempRoot = root;
for (int i = 0; i < chars.Count(); i++)
{
if (tempRoot.children.Keys.Contains(chars[i]))
{
tempRoot = tempRoot.children[chars[i]];
}
else
{
return false;
}
}
return true;
}
public bool FindWord(char[] chars)
{
TrieNode tempRoot = root;
int total = chars.Count() - 1;
for (int i = 0; i < chars.Count(); i++)
{
if (tempRoot.children.Keys.Contains(chars[i]))
{
tempRoot = tempRoot.children[chars[i]];
if (total == i)
{
if (tempRoot.endOfWord == true)
{
return true;
}
}
}
else
{
return false;
}
}
return false;
}
}
public class TrieNode
{
public Dictionary<char, TrieNode> children = new Dictionary<char, TrieNode>();
public bool endOfWord;
}
}
/*
Calling Code:
Tries t = new Tries();
t.CreateRoot();
t.Add("abc".ToCharArray());
t.Add("abgl".ToCharArray());
t.Add("cdf".ToCharArray());
t.Add("abcd".ToCharArray());
t.Add("lmn".ToCharArray());
bool findPrefix1 = t.FindPrefix("ab".ToCharArray());
bool findPrefix2 = t.FindPrefix("lo".ToCharArray());
bool findWord1 = t.FindWord("lmn".ToCharArray());
bool findWord2 = t.FindWord("ab".ToCharArray());
bool findWord3 = t.FindWord("cdf".ToCharArray());
bool findWord4 = t.FindWord("ghi".ToCharArray());
*/
Quick google results:
Taken from: Trie Generic
By Glenn Slayden
attributed to Kerry D. Wong
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
public class Trie<TValue> : System.Collections.IEnumerable, IEnumerable<Trie<TValue>.TrieNodeBase>
{
public abstract class TrieNodeBase
{
protected TValue m_value = default(TValue);
public TValue Value
{
get { return m_value; }
set { m_value = value; }
}
public bool HasValue { get { return !Object.Equals(m_value, default(TValue)); } }
public abstract bool IsLeaf { get; }
public abstract TrieNodeBase this[char c] { get; }
public abstract TrieNodeBase[] Nodes { get; }
public abstract void SetLeaf();
public abstract int ChildCount { get; }
public abstract bool ShouldOptimize { get; }
public abstract KeyValuePair<Char, TrieNodeBase>[] CharNodePairs();
public abstract TrieNodeBase AddChild(char c, ref int node_count);
/// <summary>
/// Includes current node value
/// </summary>
/// <returns></returns>
public IEnumerable<TValue> SubsumedValues()
{
if (Value != null)
yield return Value;
if (Nodes != null)
foreach (TrieNodeBase child in Nodes)
if (child != null)
foreach (TValue t in child.SubsumedValues())
yield return t;
}
/// <summary>
/// Includes current node
/// </summary>
/// <returns></returns>
public IEnumerable<TrieNodeBase> SubsumedNodes()
{
yield return this;
if (Nodes != null)
foreach (TrieNodeBase child in Nodes)
if (child != null)
foreach (TrieNodeBase n in child.SubsumedNodes())
yield return n;
}
/// <summary>
/// Doesn't include current node
/// </summary>
/// <returns></returns>
public IEnumerable<TrieNodeBase> SubsumedNodesExceptThis()
{
if (Nodes != null)
foreach (TrieNodeBase child in Nodes)
if (child != null)
foreach (TrieNodeBase n in child.SubsumedNodes())
yield return n;
}
/// <summary>
/// Note: doesn't de-optimize optimized nodes if re-run later
/// </summary>
public void OptimizeChildNodes()
{
if (Nodes != null)
foreach (var q in CharNodePairs())
{
TrieNodeBase n_old = q.Value;
if (n_old.ShouldOptimize)
{
TrieNodeBase n_new = new SparseTrieNode(n_old.CharNodePairs());
n_new.m_value = n_old.m_value;
Trie<TValue>.c_sparse_nodes++;
ReplaceChild(q.Key, n_new);
}
n_old.OptimizeChildNodes();
}
}
public abstract void ReplaceChild(Char c, TrieNodeBase n);
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Sparse Trie Node
///
/// currently, this one's "nodes" value is never null, because we leave leaf nodes as the non-sparse type,
/// (with nodes==null) and they currently never get converted back. Consequently, IsLeaf should always be 'false'.
/// However, we're gonna do the check anyway.
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public class SparseTrieNode : TrieNodeBase
{
Dictionary<Char, TrieNodeBase> d;
public SparseTrieNode(IEnumerable<KeyValuePair<Char, TrieNodeBase>> ie)
{
d = new Dictionary<char, TrieNodeBase>();
foreach (var kvp in ie)
d.Add(kvp.Key, kvp.Value);
}
public override TrieNodeBase this[Char c]
{
get
{
TrieNodeBase node;
return d.TryGetValue(c, out node) ? node : null;
}
}
public override TrieNodeBase[] Nodes { get { return d.Values.ToArray(); } }
/// <summary>
/// do not use in current form. This means, run OptimizeSparseNodes *after* any pruning
/// </summary>
public override void SetLeaf() { d = null; }
public override int ChildCount { get { return d.Count; } }
public override KeyValuePair<Char, TrieNodeBase>[] CharNodePairs()
{
return d.ToArray();
}
public override TrieNodeBase AddChild(char c, ref int node_count)
{
TrieNodeBase node;
if (!d.TryGetValue(c, out node))
{
node = new TrieNode();
node_count++;
d.Add(c, node);
}
return node;
}
public override void ReplaceChild(Char c, TrieNodeBase n)
{
d[c] = n;
}
public override bool ShouldOptimize { get { return false; } }
public override bool IsLeaf { get { return d == null; } }
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Non-sparse Trie Node
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public class TrieNode : TrieNodeBase
{
private TrieNodeBase[] nodes = null;
private Char m_base;
public override int ChildCount { get { return (nodes != null) ? nodes.Count(e => e != null) : 0; } }
public int AllocatedChildCount { get { return (nodes != null) ? nodes.Length : 0; } }
public override TrieNodeBase[] Nodes { get { return nodes; } }
public override void SetLeaf() { nodes = null; }
public override KeyValuePair<Char, TrieNodeBase>[] CharNodePairs()
{
KeyValuePair<Char, TrieNodeBase>[] rg = new KeyValuePair<char, TrieNodeBase>[ChildCount];
Char ch = m_base;
int i = 0;
foreach (TrieNodeBase child in nodes)
{
if (child != null)
rg[i++] = new KeyValuePair<char, TrieNodeBase>(ch, child);
ch++;
}
return rg;
}
public override TrieNodeBase this[char c]
{
get
{
if (nodes != null && m_base <= c && c < m_base + nodes.Length)
return nodes[c - m_base];
return null;
}
}
public override TrieNodeBase AddChild(char c, ref int node_count)
{
if (nodes == null)
{
m_base = c;
nodes = new TrieNodeBase[1];
}
else if (c >= m_base + nodes.Length)
{
Array.Resize(ref nodes, c - m_base + 1);
}
else if (c < m_base)
{
Char c_new = (Char)(m_base - c);
TrieNodeBase[] tmp = new TrieNodeBase[nodes.Length + c_new];
nodes.CopyTo(tmp, c_new);
m_base = c;
nodes = tmp;
}
TrieNodeBase node = nodes[c - m_base];
if (node == null)
{
node = new TrieNode();
node_count++;
nodes[c - m_base] = node;
}
return node;
}
public override void ReplaceChild(Char c, TrieNodeBase n)
{
if (nodes == null || c >= m_base + nodes.Length || c < m_base)
throw new Exception();
nodes[c - m_base] = n;
}
public override bool ShouldOptimize
{
get
{
if (nodes == null)
return false;
return (ChildCount * 9 < nodes.Length); // empirically determined optimal value (space & time)
}
}
public override bool IsLeaf { get { return nodes == null; } }
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Trie proper begins here
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private TrieNodeBase _root = new TrieNode();
public int c_nodes = 0;
public static int c_sparse_nodes = 0;
// in combination with Add(...), enables C# 3.0 initialization syntax, even though it never seems to call it
public System.Collections.IEnumerator GetEnumerator()
{
return _root.SubsumedNodes().GetEnumerator();
}
IEnumerator<TrieNodeBase> IEnumerable<TrieNodeBase>.GetEnumerator()
{
return _root.SubsumedNodes().GetEnumerator();
}
public IEnumerable<TValue> Values { get { return _root.SubsumedValues(); } }
public void OptimizeSparseNodes()
{
if (_root.ShouldOptimize)
{
_root = new SparseTrieNode(_root.CharNodePairs());
c_sparse_nodes++;
}
_root.OptimizeChildNodes();
}
public TrieNodeBase Root { get { return _root; } }
public TrieNodeBase Add(String s, TValue v)
{
TrieNodeBase node = _root;
foreach (Char c in s)
node = node.AddChild(c,ref c_nodes);
node.Value = v;
return node;
}
public bool Contains(String s)
{
TrieNodeBase node = _root;
foreach (Char c in s)
{
node = node[c];
if (node == null)
return false;
}
return node.HasValue;
}
/// <summary>
/// Debug only; this is hideously inefficient
/// </summary>
public String GetKey(TrieNodeBase seek)
{
String sofar = String.Empty;
GetKeyHelper fn = null;
fn = (TrieNodeBase cur) =>
{
sofar += " "; // placeholder
foreach (var kvp in cur.CharNodePairs())
{
Util.SetStringChar(ref sofar, sofar.Length - 1, kvp.Key);
if (kvp.Value == seek)
return true;
if (kvp.Value.Nodes != null && fn(kvp.Value))
return true;
}
sofar = sofar.Substring(0, sofar.Length - 1);
return false;
};
if (fn(_root))
return sofar;
return null;
}
/// <summary>
/// Debug only; this is hideously inefficient
/// </summary>
delegate bool GetKeyHelper(TrieNodeBase cur);
public String GetKey(TValue seek)
{
String sofar = String.Empty;
GetKeyHelper fn = null;
fn = (TrieNodeBase cur) =>
{
sofar += " "; // placeholder
foreach (var kvp in cur.CharNodePairs())
{
Util.SetStringChar(ref sofar, sofar.Length - 1, kvp.Key);
if (kvp.Value.Value != null && kvp.Value.Value.Equals(seek))
return true;
if (kvp.Value.Nodes != null && fn(kvp.Value))
return true;
}
sofar = sofar.Substring(0, sofar.Length - 1);
return false;
};
if (fn(_root))
return sofar;
return null;
}
public TrieNodeBase FindNode(String s_in)
{
TrieNodeBase node = _root;
foreach (Char c in s_in)
if ((node = node[c]) == null)
return null;
return node;
}
/// <summary>
/// If continuation from the terminal node is possible with a different input string, then that node is not
/// returned as a 'last' node for the given input. In other words, 'last' nodes must be leaf nodes, where
/// continuation possibility is truly unknown. The presense of a nodes array that we couldn't match to
/// means the search fails; it is not the design of the 'OrLast' feature to provide 'closest' or 'best'
/// matching but rather to enable truncated tails still in the context of exact prefix matching.
/// </summary>
public TrieNodeBase FindNodeOrLast(String s_in, out bool f_exact)
{
TrieNodeBase node = _root;
foreach (Char c in s_in)
{
if (node.IsLeaf)
{
f_exact = false;
return node;
}
if ((node = node[c]) == null)
{
f_exact = false;
return null;
}
}
f_exact = true;
return node;
}
// even though I found some articles that attest that using a foreach enumerator with arrays (and Lists)
// returns a value type, thus avoiding spurious garbage, I had already changed the code to not use enumerator.
public unsafe TValue Find(String s_in)
{
TrieNodeBase node = _root;
fixed (Char* pin_s = s_in)
{
Char* p = pin_s;
Char* p_end = p + s_in.Length;
while (p < p_end)
{
if ((node = node[*p]) == null)
return default(TValue);
p++;
}
return node.Value;
}
}
public unsafe TValue Find(Char* p_tag, int cb_ctag)
{
TrieNodeBase node = _root;
Char* p_end = p_tag + cb_ctag;
while (p_tag < p_end)
{
if ((node = node[*p_tag]) == null)
return default(TValue);
p_tag++;
}
return node.Value;
}
public IEnumerable<TValue> FindAll(String s_in)
{
TrieNodeBase node = _root;
foreach (Char c in s_in)
{
if ((node = node[c]) == null)
break;
if (node.Value != null)
yield return node.Value;
}
}
public IEnumerable<TValue> SubsumedValues(String s)
{
TrieNodeBase node = FindNode(s);
if (node == null)
return Enumerable.Empty<TValue>();
return node.SubsumedValues();
}
public IEnumerable<TrieNodeBase> SubsumedNodes(String s)
{
TrieNodeBase node = FindNode(s);
if (node == null)
return Enumerable.Empty<TrieNodeBase>();
return node.SubsumedNodes();
}
public IEnumerable<TValue> AllSubstringValues(String s)
{
int i_cur = 0;
while (i_cur < s.Length)
{
TrieNodeBase node = _root;
int i = i_cur;
while (i < s.Length)
{
node = node[s[i]];
if (node == null)
break;
if (node.Value != null)
yield return node.Value;
i++;
}
i_cur++;
}
}
/// <summary>
/// note: only returns nodes with non-null values
/// </summary>
public void DepthFirstTraverse(Action<String,TrieNodeBase> callback)
{
Char[] rgch = new Char[100];
int depth = 0;
Action<TrieNodeBase> fn = null;
fn = (TrieNodeBase cur) =>
{
if (depth >= rgch.Length)
{
Char[] tmp = new Char[rgch.Length * 2];
Buffer.BlockCopy(rgch, 0, tmp, 0, rgch.Length * sizeof(Char));
rgch = tmp;
}
foreach (var kvp in cur.CharNodePairs())
{
rgch[depth] = kvp.Key;
TrieNodeBase n = kvp.Value;
if (n.Nodes != null)
{
depth++;
fn(n);
depth--;
}
else if (n.Value == null) // leaf nodes should always have a value
throw new Exception();
if (n.Value != null)
callback(new String(rgch, 0, depth+1), n);
}
};
fn(_root);
}
/// <summary>
/// note: only returns nodes with non-null values
/// </summary>
public void EnumerateLeafPaths(Action<String,IEnumerable<TrieNodeBase>> callback)
{
Stack<TrieNodeBase> stk = new Stack<TrieNodeBase>();
Char[] rgch = new Char[100];
Action<TrieNodeBase> fn = null;
fn = (TrieNodeBase cur) =>
{
if (stk.Count >= rgch.Length)
{
Char[] tmp = new Char[rgch.Length * 2];
Buffer.BlockCopy(rgch, 0, tmp, 0, rgch.Length * sizeof(Char));
rgch = tmp;
}
foreach (var kvp in cur.CharNodePairs())
{
rgch[stk.Count] = kvp.Key;
TrieNodeBase n = kvp.Value;
stk.Push(n);
if (n.Nodes != null)
fn(n);
else
{
if (n.Value == null) // leaf nodes should always have a value
throw new Exception();
callback(new String(rgch, 0, stk.Count), stk);
}
stk.Pop();
}
};
fn(_root);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
/// Convert a trie with one value type to another
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public Trie<TNew> ToTrie<TNew>(Func<TValue, TNew> value_converter)
{
Trie<TNew> t = new Trie<TNew>();
DepthFirstTraverse((s,n)=>{
t.Add(s,value_converter(n.Value));
});
return t;
}
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
///
///
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static class TrieExtension
{
public static Trie<TValue> ToTrie<TValue>(this IEnumerable<String> src, Func<String, int, TValue> selector)
{
Trie<TValue> t = new Trie<TValue>();
int idx = 0;
foreach (String s in src)
t.Add(s,selector(s,idx++));
return t;
}
public static Trie<TValue> ToTrie<TValue>(this Dictionary<String, TValue> src)
{
Trie<TValue> t = new Trie<TValue>();
foreach (var kvp in src)
t.Add(kvp.Key, kvp.Value);
return t;
}
public static IEnumerable<TValue> AllSubstringValues<TValue>(this String s, Trie<TValue> trie)
{
return trie.AllSubstringValues(s);
}
public static void AddToValueHashset<TKey, TValue>(this Dictionary<TKey, HashSet<TValue>> d, TKey k, TValue v)
{
HashSet<TValue> hs;
if (d.TryGetValue(k, out hs))
hs.Add(v);
else
d.Add(k, new HashSet<TValue> { v });
}
};
I've just created a Trie implementation in C#:
https://github.com/TomGullen/C-Sharp-Trie/tree/master
Code:
/*
Copyright (c) 2016 Scirra Ltd
www.scirra.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
public class Trie
{
private class Node
{
public bool Terminal { get; set; }
public Dictionary<char, Node> Nodes { get; private set; }
public Node ParentNode { get; private set; }
public char C { get; private set; }
/// <summary>
/// String word represented by this node
/// </summary>
public string Word
{
get
{
var b = new StringBuilder();
b.Insert(0, C.ToString(CultureInfo.InvariantCulture));
var selectedNode = ParentNode;
while (selectedNode != null)
{
b.Insert(0, selectedNode.C.ToString(CultureInfo.InvariantCulture));
selectedNode = selectedNode.ParentNode;
}
return b.ToString();
}
}
public Node(Node parent, char c)
{
C = c;
ParentNode = parent;
Terminal = false;
Nodes = new Dictionary<char, Node>();
}
/// <summary>
/// Return list of terminal nodes under this node
/// </summary>
public IEnumerable<Node> TerminalNodes(char? ignoreChar = null)
{
var r = new List<Node>();
if (Terminal) r.Add(this);
foreach (var node in Nodes.Values)
{
if (ignoreChar != null && node.C == ignoreChar) continue;
r = r.Concat(node.TerminalNodes()).ToList();
}
return r;
}
}
private Node TopNode_ { get; set; }
private Node TopNode
{
get
{
if (TopNode_ == null) TopNode_ = new Node(null, ' ');
return TopNode_;
}
}
private bool CaseSensitive { get; set; }
/// <summary>
/// Get list of all words in trie that start with
/// </summary>
public HashSet<string> GetAutocompleteSuggestions(string wordStart, int fetchMax = 10)
{
if(fetchMax <= 0) throw new Exception("Fetch max must be positive integer.");
wordStart = NormaliseWord(wordStart);
var r = new HashSet<string>();
var selectedNode = TopNode;
foreach (var c in wordStart)
{
// Nothing starting with this word
if (!selectedNode.Nodes.ContainsKey(c)) return r;
selectedNode = selectedNode.Nodes[c];
}
// Get terminal nodes for this node
{
var terminalNodes = selectedNode.TerminalNodes().Take(fetchMax);
foreach (var node in terminalNodes)
{
r.Add(node.Word);
}
}
// Go up a node if not found enough suggestions
if (r.Count < fetchMax)
{
var parentNode = selectedNode.ParentNode;
if (parentNode != null)
{
var remainingToFetch = fetchMax - r.Count;
var terminalNodes = parentNode.TerminalNodes(selectedNode.C).Take(remainingToFetch);
foreach (var node in terminalNodes)
{
r.Add(node.Word);
}
}
}
return r;
}
/// <summary>
/// Initialise instance of trie with set of words
/// </summary>
public Trie(IEnumerable<string> words, bool caseSensitive = false)
{
CaseSensitive = caseSensitive;
foreach (var word in words)
{
AddWord(word);
}
}
/// <summary>
/// Add a single word to the trie
/// </summary>
public void AddWord(string word)
{
word = NormaliseWord(word);
var selectedNode = TopNode;
for (var i = 0; i < word.Length; i++)
{
var c = word[i];
if (!selectedNode.Nodes.ContainsKey(c))
{
selectedNode.Nodes.Add(c, new Node(selectedNode, c));
}
selectedNode = selectedNode.Nodes[c];
}
selectedNode.Terminal = true;
}
/// <summary>
/// Normalise word for trie
/// </summary>
private string NormaliseWord(string word)
{
if (String.IsNullOrWhiteSpace(word)) word = String.Empty;
word = word.Trim();
if (!CaseSensitive)
{
word = word.Trim();
}
return word;
}
/// <summary>
/// Does this word exist in this trie?
/// </summary>
public bool IsWordInTrie(string word)
{
word = NormaliseWord(word);
if (String.IsNullOrWhiteSpace(word)) return false;
var selectedNode = TopNode;
foreach (var c in word)
{
if (!selectedNode.Nodes.ContainsKey(c)) return false;
selectedNode = selectedNode.Nodes[c];
}
return selectedNode.Terminal;
}
}
Example usage:
var trie = new Trie(new String[] {"hello", "help", "he-man", "happy", "hoppy", "tom"});
var autoCompleteSuggestions = trie.GetAutocompleteSuggestions("ha");
foreach (var s in autoCompleteSuggestions)
{
Response.Write(s + "\n");
}
Here is a Trie and a scanner in one (taken from the Resin codebase):
using System;
using System.Collections.Generic;
using System.IO;
namespace Resin
{
public class UseTrie
{
public void Main()
{
var words = new[]{"pre", "prefix"};
var trie = new Trie(words);
// Print "pre" and "prefix"
foreach(var word in trie.GetTokens("pr"))
{
Console.WriteLine(word);
}
}
}
public class Trie
{
public char Value { get; set; }
public bool Eow { get; set; }
public IDictionary<char, Trie> Children { get; set; }
public bool Root { get; set; }
public Trie(bool isRoot)
{
Root = isRoot;
Children = new Dictionary<char, Trie>();
}
public Trie(IList<string> words)
{
if (words == null) throw new ArgumentNullException("words");
Root = true;
Children = new Dictionary<char, Trie>();
foreach (var word in words)
{
AppendToDescendants(word);
}
}
public Trie(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new ArgumentException("text");
}
Value = text[0];
Children = new Dictionary<char, Trie>();
if (text.Length > 1)
{
var overflow = text.Substring(1);
if (overflow.Length > 0)
{
AppendToDescendants(overflow);
}
}
else
{
Eow = true;
}
}
public IEnumerable<string> GetTokens(string prefix)
{
var words = new List<string>();
Trie child;
if (Children.TryGetValue(prefix[0], out child))
{
child.Scan(prefix, prefix, ref words);
}
return words;
}
private void Scan(string originalPrefix, string prefix, ref List<string> words)
{
if (string.IsNullOrWhiteSpace(prefix)) throw new ArgumentException("prefix");
if (prefix.Length == 1 && prefix[0] == Value)
{
// The scan has reached its destination. Find words derived from this node.
if (Eow) words.Add(originalPrefix);
foreach (var node in Children.Values)
{
node.Scan(originalPrefix+node.Value, new string(new []{node.Value}), ref words);
}
}
else if (prefix[0] == Value)
{
Trie child;
if (Children.TryGetValue(prefix[1], out child))
{
child.Scan(originalPrefix, prefix.Substring(1), ref words);
}
}
}
public void AppendToDescendants(string text)
{
if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("text");
Trie child;
if (!Children.TryGetValue(text[0], out child))
{
child = new Trie(text);
Children.Add(text[0], child);
}
else
{
child.Append(text);
}
}
public void Append(string text)
{
if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("text");
if (text[0] != Value) throw new ArgumentOutOfRangeException("text");
if (Root) throw new InvalidOperationException("When appending from the root, use AppendToDescendants.");
var overflow = text.Substring(1);
if (overflow.Length > 0)
{
AppendToDescendants(overflow);
}
}
}
}
Article at below URI has very good implementation and comparison with other .NET Collection. It also specify scenario where we should use Trie instead of .NET build in collections like (HashSet<>, SortedList<>) etc.
https://visualstudiomagazine.com/articles/2015/10/20/text-pattern-search-trie-class-net.aspx
To get instant suggestions from trie data structure, after loading from strings use the below. (faster retrieval)
public class Trie
{
public struct Letter
{
public const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static implicit operator Letter(char c)
{
c = c.ToString().ToUpper().ToCharArray().First();
return new Letter() { Index = Chars.IndexOf(c) };
}
public int Index;
public char ToChar()
{
return Chars[Index];
}
public override string ToString()
{
return Chars[Index].ToString();
}
}
public class Node
{
public string Word;
public bool IsTerminal { get { return Word != null; } }
public Dictionary<Letter, Node> Edges = new Dictionary<Letter, Node>();
}
public Node Root = new Node();
public Trie(string[] words)
{
for (int w = 0; w < words.Length; w++)
{
var word = words[w];
var node = Root;
for (int len = 1; len <= word.Length; len++)
{
var letter = word[len - 1];
Node next;
if (!node.Edges.TryGetValue(letter, out next))
{
next = new Node();
if (len == word.Length)
{
next.Word = word;
}
node.Edges.Add(letter, next);
}
node = next;
}
}
}
public List<string> GetSuggestions(string word, int max)
{
List<string> outPut = new List<string>();
var node = Root;
int i = 0;
foreach (var l in word)
{
Node cNode;
if (node.Edges.TryGetValue(l, out cNode))
{
node = cNode;
}
else
{
if (i == word.Length - 1)
return outPut;
}
i++;
}
GetChildWords(node, ref outPut, max);
return outPut;
}
public void GetChildWords(Node n, ref List<string> outWords, int Max)
{
if (n.IsTerminal && outWords.Count < Max)
outWords.Add(n.Word);
foreach (var item in n.Edges)
{
GetChildWords(item.Value, ref outWords, Max);
}
}
}
Simple and beautiful:
class Trie
{
private readonly string _key;
private string _value;
private List<Trie> _path;
private List<Trie> _children;
public Trie(string key = "root", string value = "root_val")
{
this._key = key;
this._value = value;
this._path = this._children = new List<Trie>();
}
public void Initialize(Dictionary<string, string> nodes, int keyLength = 1)
{
foreach (var node in nodes)
{
this.Add(node, keyLength);
}
}
public void Add(KeyValuePair<string, string> node, int keyLength = 1)
{
if (this._children.Count == 0 || !this._children.Any(ch => (node.Key.StartsWith(ch._key)) || (ch._key == node.Key)))
{
//For any item that could be a child of newly added item
Predicate<Trie> possibleChildren = (Trie ch) => { return ch._key.StartsWith(node.Key); };
var newChild = new Trie(node.Key, node.Value);
newChild._children.AddRange(this._children.FindAll(possibleChildren));
this._children.RemoveAll(possibleChildren);
this._children.Add(newChild);
}
else
{
this._children.First(ch => (ch._key == node.Key) || (node.Key.Substring(0, keyLength) == ch._key)).Add(node, keyLength + 1);
}
}
public void Delete(string key, bool recursively = true)
{
var newChildren = new List<Trie>(this._children);
foreach (var child in this._children)
{
if (child._key == key)
{
if (!recursively)
{
newChildren.AddRange(child._children);
}
newChildren.Remove(child);
}
else
{
child.Delete(key, recursively);
}
}
this._children = newChildren;
}
public List<Trie> Find(string key, int keyLength = 1)
{
this._path = new List<Trie>();
if (key.Length >= keyLength - 1 && this._key == key.Substring(0, keyLength - 1))
{
this._path.Add(this);
}
foreach (var child in this._children)
{
var childPath = child.Find(key, keyLength + 1);
this._path.AddRange(childPath);
}
return this._path;
}
}
var items = new Dictionary<string, string>
{
{ "a", "First level item" },
{ "b", "First level item"},
{ "ad", "Second level item"},
{ "bv", "Second level item"},
{ "adf", "Third level item"},
{ "adg", "Third level item"},
{ "bvc", "Third level item"},
{ "bvr", "Third level item"}
};
var myTree = new Trie();
myTree.Initialize(items);