Each item/string in my array starts with two letters followed by two or three numbers and then sometimes followed by another letter.
Examples, RS01 RS10 RS32A RS102 RS80 RS05A RS105A RS105B
I tried to sort this using the default Array.Sort but it came back with this...
RS01
RS05A
RS10
RS102
RS105A
RS105B
RS32A
RS80
But I need it like this..
RS01
RS05A
RS10
RS32A
RS80
RS102
RS105A
RS105B
Any Ideas?
Here is sorting with custom comparison delegate and regular expressions:
string[] array = { "RS01", "RS10", "RS32A", "RS102",
"RS80", "RS05A", "RS105A", "RS105B" };
Array.Sort(array, (s1, s2) =>
{
Regex regex = new Regex(#"([a-zA-Z]+)(\d+)([a-zA-Z]*)");
var match1 = regex.Match(s1);
var match2 = regex.Match(s2);
// prefix
int result = match1.Groups[1].Value.CompareTo(match2.Groups[1].Value);
if (result != 0)
return result;
// number
result = Int32.Parse(match1.Groups[2].Value)
.CompareTo(Int32.Parse(match2.Groups[2].Value));
if (result != 0)
return result;
// suffix
return match1.Groups[3].Value.CompareTo(match2.Groups[3].Value);
});
UPDATE (little refactoring, and moving all stuff to separate comparer class). Usage:
Array.Sort(array, new RSComparer());
Comparer itself:
public class RSComparer : IComparer<string>
{
private Dictionary<string, RS> entries = new Dictionary<string, RS>();
public int Compare(string x, string y)
{
if (!entries.ContainsKey(x))
entries.Add(x, new RS(x));
if (!entries.ContainsKey(y))
entries.Add(y, new RS(y));
return entries[x].CompareTo(entries[y]);
}
private class RS : IComparable
{
public RS(string value)
{
Regex regex = new Regex(#"([A-Z]+)(\d+)([A-Z]*)");
var match = regex.Match(value);
Prefix = match.Groups[1].Value;
Number = Int32.Parse(match.Groups[2].Value);
Suffix = match.Groups[3].Value;
}
public string Prefix { get; private set; }
public int Number { get; private set; }
public string Suffix { get; private set; }
public int CompareTo(object obj)
{
RS rs = (RS)obj;
int result = Prefix.CompareTo(rs.Prefix);
if (result != 0)
return result;
result = Number.CompareTo(rs.Number);
if (result != null)
return result;
return Suffix.CompareTo(rs.Suffix);
}
}
}
You can use this linq query:
var strings = new[] {
"RS01","RS05A","RS10","RS102","RS105A","RS105B","RS32A","RS80"
};
strings = strings.Select(str => new
{
str,
num = int.Parse(String.Concat(str.Skip(2).TakeWhile(Char.IsDigit))),
version = String.Concat(str.Skip(2).SkipWhile(Char.IsDigit))
})
.OrderBy(x => x.num).ThenBy(x => x.version)
.Select(x => x.str)
.ToArray();
DEMO
Result:
RS01
RS05A
RS10
RS32A
RS80
RS102
RS105A
RS105B
You'll want to write a custom comparer class implementing IComparer<string>; it's pretty straightforward to break your strings into components. When you call Array.Sort, give it an instance of your comparer and you'll get the results you want.
Related
I would like to refactor my method. I also need to get which value was first? So which anyOf? Is it possible to get it from here?
Example:
List<string> anyOf = new List<string>(){"at", "near", "by", "above"};
string source = "South Branch Raritan River near High Bridge at NJ"
public static int IndexOfAny(this string source, IEnumerable<string> anyOf, StringComparison stringComparisonType = StringComparison.CurrentCultureIgnoreCase)
{
var founds = anyOf
.Select(sub => source.IndexOf(sub, stringComparisonType))
.Where(i => i >= 0);
return founds.Any() ? founds.Min() : -1;
}
I would like to get back what is first in string. "near" or "at".
You could use:
public static (int index, string? firstMatch) IndexOfAny(this string source, IEnumerable<string> anyOf, StringComparison stringComparisonType = StringComparison.CurrentCultureIgnoreCase)
{
return anyOf
.Select(s => (Index: source.IndexOf(s, stringComparisonType), String: s))
.Where(x => x.Index >= 0)
.DefaultIfEmpty((-1, null))
.First();
}
I couldn't resist creating a more efficient implementation.
Working here.
Whilst this looks more complicated, its better because,
It allocates only,
an array for the valid search terms,
a array of indices for each search term and,
an array of lengths for each search term.
The source text is enumerated only once and, if a match is found,
that loop will exit early.
Additionally, the code incorporates parameter checking which you'll want as extension methods should be resusable.
public static class Extensions
{
public static int IndexOfAny<T>(
this IEnumerable<T> source,
IEnumerable<IEnumerable<T>> targets,
IEqualityComparer<T> comparer = null)
{
// Parameter Handling
comparer = comparer ?? EqualityComparer<T>.Default;
ArgumentNullException.ThrowIfNull(targets);
var clean = targets
.Where(t => t != null)
.Select(t => t.ToArray())
.Where(t => t.Length > 0)
.ToArray();
if (clean.Length == 0)
{
throw new ArgumentException(
$"'{nameof(targets)}' does not contain a valid search sequence");
}
// Prep
var lengths = clean.Select(t => t.Length).ToArray();
var indices = clean.Select(_ => 0).ToArray();
int i = 0;
// Process
foreach(var t in source)
{
i++;
for(var j = 0; j < clean.Length; j++)
{
var index = indices[j];
if (comparer.Equals(clean[j][index], t))
{
index += 1;
if (index == lengths[j])
{
return i - lengths[j];
}
indices[j] = index;
}
else
{
if (index != 0)
{
indices[j] = 0;
}
}
}
}
return -1;
}
public static int IndexOfAny(
this string source,
IEnumerable<string> targets,
StringComparer comparer = null)
{
comparer = comparer ?? StringComparer.Ordinal;
ArgumentNullException.ThrowIfNull(targets);
return source.ToCharArray().IndexOfAny(
targets.Select(t => t.ToCharArray()),
new CharComparerAdapter(comparer));
}
}
public class CharComparerAdapter : IEqualityComparer<char>
{
private StringComparer Comparer { get; }
public CharComparerAdapter(StringComparer comparer)
{
ArgumentNullException.ThrowIfNull(comparer);
Comparer = comparer;
}
public bool Equals(char left, char right)
{
return Comparer.Equals(left.ToString(), right.ToString());
}
public int GetHashCode(char v)
{
return v;
}
}
I have two json objects and I compare them, But it is compared by key and value,
And I want to compare two json objects by only key,
How do I it?
This my code:
var jdp = new JsonDiffPatch();
var areEqual2 = jdp.Diff(json1, json2);
you can create and use a class like this:
class CustomComparer : IEqualityComparer<YourObjectType>
{
public bool Equals(YourObjectType first, YourObjectType second)
{
if (first == null | second == null) { return false; }
else if (first.Hash == second.Hash)
return true;
else return false;
}
public int GetHashCode(YourObjectType obj)
{
throw new NotImplementedException();
}
}
If you want to get a different between 2 json:
private List<string> GetDiff(List<string> path1, List<string> path2)
{
List<string> equal=new List<string>();
foreach (var j1 in path1)
{
foreach (var j2 in path2)
{
if (j1 == j2)
{
equal.Add(j1);
}
}
}
return equal;
}
One way you could achive this is by retrieving the names/path of all keys in json and comparing the List. For example,
var path1 = GetAllPaths(json1).OrderBy(x=>x).ToList();
var path2 = GetAllPaths(json2).OrderBy(x=>x).ToList();
var result = path1.SequenceEqual(path2);
Where GetAllPaths is defined as
private IEnumerable<string> GetAllPaths(string json)
{
var regex = new Regex(#"\[\d*\].",RegexOptions.Compiled);
return JObject.Parse(json).DescendantsAndSelf()
.OfType<JProperty>()
.Where(jp => jp.Value is JValue)
.Select(jp => regex.Replace(jp.Path,".")).Distinct();
}
Sample Demo
I made a pattern which i dont really like.
It is as the following:
List<Element> listOfPossibleResults = getAllPossibleResults();
Element result = findResult(getFirstPriorityElements(listOfPossibleResults));
if (result!= null)
{
return result;
}
result = findResult(getSecondPriorityElements(listOfPossibleResults));
if (result!= null)
{
return result;
}
private Element findResult(List<Element> elements) {...};
private List<Element> getFirstPriorityElements(List<Element> elements) {...};
private List<Element> getSecondPriorityElements(List<Element> elements) {...};
etc..
Basically i'am creating sublists based on a couple of rules. After creating the sublist, i try and find a specific element in it. If i dont find, i move on to the next priority, and so on.
I would like a solution where i can iterate over these criterias, until i find a solution. But i dont know how to get them to a format which i can iterate over.
Can you guys give me a C# specific solution of the issue?
As #Lepijohnny mentioned, you can use Chain of responsibility design pattern. For example:
abstract class Handler<TRequest, TResult>
{
protected Handler<TRequest, TResult> successor;
public void SetSuccessor(Handler<TRequest, TResult> successor)
{
this.successor = successor;
}
public abstract TResult HandleRequest(TRequest request);
}
class FirstHandler : Handler<List<Element>, Element>
{
public override void HandleRequest(TRequest request)
{
Element result = findResult(getFirstPriorityElements(request));
if (result == null)
{
result = sucessor?.HandleRequest(request);
}
return result;
}
private Element findResult(List<Element> elements) {...};
private List<Element> getFirstPriorityElements(List<Element> elements) {...};
}
class SecondHandler : Handler<List<Element>, Element>
{
public override void HandleRequest(TRequest request)
{
Element result = findResult(getSecondPriorityElements(request));
if (result == null)
{
result = sucessor?.HandleRequest(request);
}
return result;
}
private Element findResult(List<Element> elements) {...};
private List<Element> getSecondPriorityElements(List<Element> elements) {...};
}
Usage:
void Example()
{
// Setup Chain of Responsibility
var h1 = new FirstHandler();
var h2 = new SecondHandler();
h1.SetSuccessor(h2);
var result = h1.Handle(new List<Element>());
}
It's a just quick example. I think it describe how this pattern works and you will be able to adjust it for your needs.
In the "result" class put a property called "Priority (int)" then:
result = listOfPossibleResults.GroupBy(x => x.Priority).OrderBy(x => x.Key);
then:
return result.FirstOrDefault(x => x.Count() > 0);
You will need to fill in the priority of the result items when you first retrieve them.
P.S. I typed the code right here, forgive me if there is a spelling mistake somewhere.
If you could refactor the methods getFirstPriorityElements(List<> list) to a single getPriorityElements(List<> list, int nr) you could do the following
method IteratePredicates(List<> list, int nr = 0)
{
if (nr>maxpriority) return null;
return findresult(getPriorityElements(list,nr)) ?? IteratePredicates(list,nr++);
}
In a for loop:
method IteratePredicates(List<> list, int nr = 0)
{
for (int i = 0; i < maxpriority; i++)
{
var result = findresult(getPriorityElements(list, nr));
if (result != null)
return result;
}
return null;
}
Am I right that your get__PriorityElements is literally a filter? In that case, it's more declarative and hopefully more readable to treat those like this:
Func<Element, bool> isFirstPriority = ...;
var firstPriorityElements = elements.Where(isFirstPriority);
And now your overall goal is to extract a single element (or none) from the highest-possible priority subsequence, using a predicate contained in findResult? So replace this with an actual predicate
Func<Element, bool> isResult = ...;
like so. Now you want to look through all the first priority elements for an isResult match, then if not found all the second priority elements, etc. This sounds just like a sequence concatenation! So we end up with
var prioritisedSequence = elements
.Where(isFirstPriority)
.Concat(elements
.Where(isSecondPriority))
.Concat....;
And finally the result
var result = prioritisedSequence
.FirstOrDefault(isResult);
Since Where and Concat are lazily enumerated this has the benefit that it is declarative while avoiding more work than necessary, and it's lightweight and 'LINQy' as well.
If you want abstract it even more, and anticipate changes in how priorities will be arranged, you could actually make a higher order list for those like this:
IEnumerable<Func<Element, bool>> priorityFilters = new[]
{
isFirstPriority,
isSecondPriority,
...
};
and then the concatenation can be performed as an aggregation over that sequence:
var prioritisedSequence = priorityFilters
.Aggregate(
Enumerable.Empty<Element>(),
(current, filter) => current.Concat(elements.Where(filter)));
This change may make it easier to add new priorities in future, or you may think it clutters and hides the intention of your code.
You can treat methods as objects using Func<T, T>, and then you can also put them in e.g. an array. Then you can iterate over the array, calling the methods one by one until a result is found.
The solution then becomes:
var methods = new Func<List<Element>, List<Element>>[]
{ getFirstPriorityElements, getSecondPriorityElements };
return methods
.Select(method => findResult(method(listOfPossibleResults)))
.Where(result => result != null)
.FirstOrDefault();
This is short and readable, works without changing your methods or types, and no need to add classes just for the sake of applying a pattern.
You can use the specs pattern Here is a sample code:
Create a interface with a criteria:
public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria { get; }
}
Then create a class that holds your query specs:
public class GlobalSongSpecification : ISpecification<Song>
{
public List<int> GenreIdsToInclude { get; set; } = new List<int>();
public List<int> AlbumIdsToInclude { get; set; } = new List<int>();
public List<string> ArtistsToInclude { get; set; } = new List<string>();
public string TitleFilter { get; set; }
public int MinRating { get; set; }
[JsonIgnore]
public Expression<Func<Song, bool>> Criteria
{
get
{
return s =>
(!GenreIdsToInclude.Any() || s.Genres.Any(g => GenreIdsToInclude.Any(gId => gId == g.Id))) &&
(!AlbumIdsToInclude.Any() || AlbumIdsToInclude.Contains(s.AlbumId)) &&
(!ArtistsToInclude.Any() ||ArtistsToInclude.Contains(s.Artist)) &&
(String.IsNullOrEmpty(this.TitleFilter) || s.Title.Contains(TitleFilter)) &&
s.Rating >= MinRating;
}
}
}
Create a repository with a method that exposes one that receives ISpecification:
public interface ISongRepository
{
IEnumerable<Song> List(ISpecification<Song> specification);
//IQueryable<Song> List();
Song GetById(int id);
void Add(Song song);
IEnumerable<string> AllArtists();
IEnumerable<Genre> AllGenres();
}
And your client code call the GlobalSongSpecification, populates it and pass it to the repository in order to filter by the criteria:
public ActionResult Index(List<int> selectedGenres = null,
List<string> selectedArtists = null,
string titleSearch = null,
int minRating = 0,
string filter = null,
string save = null,
string playlistName = null)
{
if (selectedArtists == null) { selectedArtists = new List<string>(); }
if (selectedGenres == null) { selectedGenres = new List<int>(); }
var spec = new GlobalSongSpecification();
spec.ArtistsToInclude.AddRange(selectedArtists);
spec.GenreIdsToInclude.AddRange(selectedGenres);
spec.MinRating = minRating;
spec.TitleFilter = titleSearch;
var songs = _songRepository.List(spec);
//You can work with the filtered data at this point
}
And you populate a razor view or expose it as web api.
The sample code is from pluralsight desing patterns library course Here(Specification Pattern module)
Firstly I have seen IEqualityComparer for anonymous type and the answers there do not answer my question, for the obvious reason that I need an IEqualityComparer not and IComparer for use with Linq's Distinct() method. I have checked the other answers too and these fall short of a solution...
The Problem
I have some code to manipulate and pull records in from a DataTable
var glext = m_dtGLExt.AsEnumerable();
var cflist =
(from c in glext
orderby c.Field<string>(m_strpcCCType),
c.Field<string>(m_strpcCC),
c.Field<string>(m_strpcCCDesc),
c.Field<string>(m_strpcCostItem)
select new
{
CCType = c.Field<string>(m_strpcCCType),
CC = c.Field<string>(m_strpcCC),
CCDesc = c.Field<string>(m_strpcCCDesc),
CostItem = c.Field<string>(m_strpcCostItem)
}).Distinct();
but I need the distinct method to be case insensitive. What is throwing me here is the use of anonymous types.
Attempted Solution 1
If I had SomeClass which had concrete objects I could obviously do
public class SumObject
{
public string CCType { get; set; }
public string CC { get; set; }
public string CCDesc { get; set; }
public string CostItem { get; set; }
}
I could obviously do this
List<SumObject> lso = new List<SumObject>()
{
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Rooney", CostItem = "I477" },
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Zidane", CostItem = "I677" },
new SumObject() { CCType = "1-OCC", CC = "300401", CCDesc = "Falcao", CostItem = "I470" },
};
var e = lso.Distinct(new SumObjectComparer()); // Great :]
where
class SumObjectComparer : IEqualityComparer<SumObject>
{
public bool Equals(SumObject x, SumObject y)
{
if (Object.ReferenceEquals(x, y))
return true;
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.CCType.CompareNoCase(y.CCType) == 0 &&
x.CC.CompareNoCase(y.CC) == 0 &&
x.CCDesc.CompareNoCase(y.CCDesc) == 0 &&
x.CostItem.CompareNoCase(y.CostItem) == 0;
}
public int GetHashCode(SumObject o)
{
if (Object.ReferenceEquals(o, null))
return 0;
int hashCCType = String.IsNullOrEmpty(o.CCType) ?
0 : o.CCType.ToLower().GetHashCode();
int hashCC = String.IsNullOrEmpty(o.CC) ?
0 : o.CC.ToLower().GetHashCode();
int hashCCDesc = String.IsNullOrEmpty(o.CCDesc) ?
0 : o.CCDesc.ToLower().GetHashCode();
int hashCostItem = String.IsNullOrEmpty(o.CostItem) ?
0 : o.CostItem.ToLower().GetHashCode();
return hashCCType ^ hashCC ^ hashCCDesc ^ hashCostItem;
}
}
However, the use of anonymous types in the above Linq query are throwing me.
Attempted Solution 2
To attempt another solution to this (and because I have the same issue elsewhere) I generated the following generic comparer class
public class GenericEqualityComparer<T> : IEqualityComparer<T>
{
Func<T, T, bool> compareFunction;
Func<T, int> hashFunction;
public GenericEqualityComparer(Func<T, T, bool> compareFunction, Func<T, int> hashFunction)
{
this.compareFunction = compareFunction;
this.hashFunction = hashFunction;
}
public bool Equals(T x, T y) { return compareFunction(x, y); }
public int GetHashCode(T obj) { return hashFunction(obj); }
}
so that I could attempt to do
var comparer = new GenericEqualityComparer<dynamic>(
(x, y) => { /* My equality stuff */ },
o => { /* My hash stuff */ });
but this casts the returned value as IEnumerable<dynamic> which in turn effects my forthcoming use of cflist, so that in a following query the join fails.
var cf =
(from o in cflist
join od in glext
on new { o.CCType, o.CC, o.CCDesc, o.CostItem } equals new
{
CCType = od.Field<string>(m_strpcCCType),
CC = od.Field<string>(m_strpcCC),
CCDesc = od.Field<string>(m_strpcCCDesc),
CostItem = od.Field<string>(m_strpcCostItem)
}
into c
select new { ... }
I don't want to get into ugly casting to and from IEnumerable<T>s due to the heavy use of this code...
Question
Is there a way I can create my an IEquailityComparer for my anonymous types?
Thanks for your time.
Is there a way I can create my an IEquailityComparer for my anonymous types?
Sure. You just need to use type inference. For example, you could have something like:
public static class InferredEqualityComparer
{
public static IEqualityComparer<T> Create<T>(
IEnumerable<T> example,
Func<T, T, bool> equalityCheck,
Func<T, int> hashCodeProvider)
{
return new EqualityComparerImpl<T>(equalityCheck, hashCodeProvider);
}
private sealed class EqualityComparerImpl<T> : IEqualityComparer<T>
{
// Implement in the obvious way, remembering the delegates and
// calling them appropriately.
}
}
Then:
var glext = m_dtGLExt.AsEnumerable();
var query = from c in glext
orderby ...
select new { ... };
var comparer = InferredEqualityComparer.Create(query,
(x, y) => { ... },
o => { ... }
);
var distinct = query.Distinct(comparer);
Basically the first parameter to the method is just used for type inference, so that the compiler can work out what type to use for the lambda expression parameters.
You could create the comparer ahead of time by creating a sample of the anonymous type:
var sample = new[] { new { ... } };
var comparer = InferredExqualityComparer.Create(sample, ...);
var distinct = (... query here ... ).Distinct(comparer);
but then any time you change the query you've got to change the sample too.
This post may get what you want. Although for .NET 2.0 it also works for newer versions (see the bottom of this post for how to achieve this). In contrast to Jon Skeets solution we won´t use a factory-method like create. But this is only syntactic sugar I think.
I'm working with a service that provides data as a Lisp-like S-Expression string. This data is arriving thick and fast, and I want to churn through it as quickly as possible, ideally directly on the byte stream (it's only single-byte characters) without any backtracking. These strings can be quite lengthy and I don't want the GC churn of allocating a string for the whole message.
My current implementation uses CoCo/R with a grammar, but it has a few problems. Due to the backtracking, it assigns the whole stream to a string. It's also a bit fiddly for users of my code to change if they have to. I'd rather have a pure C# solution. CoCo/R also does not allow for the reuse of parser/scanner objects, so I have to recreate them for each message.
Conceptually the data stream can be thought of as a sequence of S-Expressions:
(item 1 apple)(item 2 banana)(item 3 chainsaw)
Parsing this sequence would create three objects. The type of each object can be determined by the first value in the list, in the above case "item". The schema/grammar of the incoming stream is well known.
Before I start coding I'd like to know if there are libraries out there that do this already. I'm sure I'm not the first person to have this problem.
EDIT
Here's a little more detail on what I want as I think the original question may have been a little vague.
Given some SExpressions, such as:
(Hear 12.3 HelloWorld)
(HJ LAJ1 -0.42)
(FRP lf (pos 2.3 1.7 0.4))
I want a list of objects equivalent to this:
{
new HearPerceptorState(12.3, "HelloWorld"),
new HingeJointState("LAJ1", -0.42),
new ForceResistancePerceptorState("lf", new Polar(2.3, 1.7, 0.4))
}
The actual data set I'm working on is a list of perceptors from a robot model in the RoboCup 3D simulated soccer league. I may potentially also need to deserialise another set of related data with a more complex structure.
In my opinion a parse generator is unneccessary to parse simple S-expressions consisting only of lists, numbers and symbols. A hand-written recursive descent parser is probably simpler and at least as fast. The general pattern would look like this (in java, c# should be very similar):
Object readDatum(PushbackReader in) {
int ch = in.read();
return readDatum(in, ch);
}
Object readDatum(PushbackReader in, int ch) {
if (ch == '(')) {
return readList(in, ch);
} else if (isNumber(ch)) {
return readNumber(in, ch);
} else if (isSymbolStart(ch)) {
return readSymbol(in, ch);
} else {
error(ch);
}
}
List readList(PushbackReader in, int lookAhead) {
if (ch != '(') {
error(ch);
}
List result = new List();
while (true) {
int ch = in.read();
if (ch == ')') {
break;
} else if (isWhiteSpace(ch)) {
skipWhiteSpace(in);
} else {
result.append(readDatum(in, ch);
}
}
return result;
}
String readSymbol(PushbackReader in, int ch) {
StringBuilder result = new StringBuilder();
result.append((char)ch);
while (true) {
int ch2 = in.read();
if (isSymbol(ch2)) {
result.append((char)ch2);
} else if (isWhiteSpace(ch2) || ch2 == ')') {
in.unread(ch2);
break;
} else if (ch2 == -1) {
break;
} else {
error(ch2);
}
}
return result.toString();
}
I wrote an S-Expression parser in C# using OMeta#. It can parse the kind of S-Expressions that you are giving in your examples, you just need to add decimal numbers to the parser.
The code is available as SExpression.NET on github and a related article is available here. As an alternative I suggest to take a look at the YaYAML YAML parser for .NET also written using OMeta#.
Consider using Ragel. It's a state machine compiler and produces reasonably fast code.
It may not be apparent from the home page, but Ragel does have C# support.
Here's a trivial example of how to use it in C#
Look at gplex and gppg.
Alternatively, you can trivially translate the S-expressions to XML and let .NET do the rest.
Drew, perhaps you should add some context to the question, otherwise this answer will make no sense to other users, but try this:
CHARACTERS
letter = 'A'..'Z' + 'a'..'z' .
digit = "0123456789" .
messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')' .
TOKENS
double = ['-'] digit { digit } [ '.' digit { digit } ] .
ident = letter { letter | digit | '_' } .
message = messageChar { messageChar } CONTEXT (")") .
Oh, I have to point out that '\u0020' is the unicode SPACE, which you are subsequently removing with "- ' '". Oh, and you can use CONTEXT (')') if you don't need more than one character lookahead.
FWIW: CONTEXT does not consume the enclosed sequence, you must still consume it in your production.
EDIT:
Ok, this seems to work. Really, I mean it this time :)
CHARACTERS
letter = 'A'..'Z' + 'a'..'z' .
digit = "0123456789" .
// messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')' .
TOKENS
double = ['-'] digit { digit } [ '.' digit { digit } ] .
ident = letter { letter | digit | '_' } .
// message = letter { messageChar } CONTEXT (')') .
// MessageText<out string m> = message (. m = t.val; .)
// .
HearExpr<out HeardMessage message> = (. TimeSpan time; Angle direction = Angle.NaN; string messageText; .)
"(hear"
TimeSpan<out time>
( "self" | AngleInDegrees<out direction> )
// MessageText<out messageText> // REMOVED
{ ANY } (. messageText = t.val; .) // MOD
')' (. message = new HeardMessage(time, direction, new Message(messageText)); .)
.
Here's a relatively simple (and hopefully, easy to extend) solution:
public delegate object Acceptor(Token token, string match);
public class Symbol
{
public Symbol(string id) { Id = id ?? Guid.NewGuid().ToString("P"); }
public override string ToString() => Id;
public string Id { get; private set; }
}
public class Token : Symbol
{
internal Token(string id) : base(id) { }
public Token(string pattern, Acceptor acceptor) : base(pattern) { Regex = new Regex(string.Format("^({0})", !string.IsNullOrEmpty(Pattern = pattern) ? Pattern : ".*"), RegexOptions.Compiled); ValueOf = acceptor; }
public string Pattern { get; private set; }
public Regex Regex { get; private set; }
public Acceptor ValueOf { get; private set; }
}
public class SExpressionSyntax
{
private readonly Token Space = Token("\\s+", Echo);
private readonly Token Open = Token("\\(", Echo);
private readonly Token Close = Token("\\)", Echo);
private readonly Token Quote = Token("\\'", Echo);
private Token comment;
private static Exception Error(string message, params object[] arguments) => new Exception(string.Format(message, arguments));
private static object Echo(Token token, string match) => new Token(token.Id);
private static object Quoting(Token token, string match) => NewSymbol(token, match);
private Tuple<Token, string, object> Read(ref string input)
{
if (!string.IsNullOrEmpty(input))
{
var found = null as Match;
var sofar = input;
var tuple = Lexicon.FirstOrDefault(current => (found = current.Item2.Regex.Match(sofar)).Success && (found.Length > 0));
var token = tuple != null ? tuple.Item2 : null;
var match = token != null ? found.Value : null;
input = match != null ? input.Substring(match.Length) : input;
return token != null ? Tuple.Create(token, match, token.ValueOf(token, match)) : null;
}
return null;
}
private Tuple<Token, string, object> Next(ref string input)
{
Tuple<Token, string, object> read;
while (((read = Read(ref input)) != null) && ((read.Item1 == Comment) || (read.Item1 == Space))) ;
return read;
}
public object Parse(ref string input, Tuple<Token, string, object> next)
{
var value = null as object;
if (next != null)
{
var token = next.Item1;
if (token == Open)
{
var list = new List<object>();
while (((next = Next(ref input)) != null) && (next.Item1 != Close))
{
list.Add(Parse(ref input, next));
}
if (next == null)
{
throw Error("unexpected EOF");
}
value = list.ToArray();
}
else if (token == Quote)
{
var quote = next.Item3;
next = Next(ref input);
value = new[] { quote, Parse(ref input, next) };
}
else
{
value = next.Item3;
}
}
else
{
throw Error("unexpected EOF");
}
return value;
}
protected Token TokenOf(Acceptor acceptor)
{
var found = Lexicon.FirstOrDefault(pair => pair.Item2.ValueOf == acceptor);
var token = found != null ? found.Item2 : null;
if ((token == null) && (acceptor != Commenting))
{
throw Error("missing required token definition: {0}", acceptor.Method.Name);
}
return token;
}
protected IList<Tuple<string, Token>> Lexicon { get; private set; }
protected Token Comment { get { return comment = comment ?? TokenOf(Commenting); } }
public static Token Token(string pattern, Acceptor acceptor) => new Token(pattern, acceptor);
public static object Commenting(Token token, string match) => Echo(token, match);
public static object NewSymbol(Token token, string match) => new Symbol(match);
public static Symbol Symbol(object value) => value as Symbol;
public static string Moniker(object value) => Symbol(value) != null ? Symbol(value).Id : null;
public static string ToString(object value)
{
return
value is object[] ?
(
((object[])value).Length > 0 ?
((object[])value).Aggregate(new StringBuilder("("), (result, obj) => result.AppendFormat(" {0}", ToString(obj))).Append(" )").ToString()
:
"( )"
)
:
(value != null ? (value is string ? string.Concat('"', (string)value, '"') : (value is bool ? value.ToString().ToLower() : value.ToString())).Replace("\\\r\n", "\r\n").Replace("\\\n", "\n").Replace("\\t", "\t").Replace("\\n", "\n").Replace("\\r", "\r").Replace("\\\"", "\"") : null) ?? "(null)";
}
public SExpressionSyntax()
{
Lexicon = new List<Tuple<string, Token>>();
Include(Space, Open, Close, Quote);
}
public SExpressionSyntax Include(params Token[] tokens)
{
foreach (var token in tokens)
{
Lexicon.Add(new Tuple<string, Token>(token.Id, token));
}
return this;
}
public object Parse(string input)
{
var next = Next(ref input);
var value = Parse(ref input, next);
if ((next = Next(ref input)) != null)
{
throw Error("unexpected ", next.Item1);
}
return value;
}
}
public class CustomSExpressionSyntax : SExpressionSyntax
{
public CustomSExpressionSyntax()
: base()
{
Include
(
// "//" comments
Token("\\/\\/.*", SExpressionSyntax.Commenting),
// Obvious
Token("false", (token, match) => false),
Token("true", (token, match) => true),
Token("null", (token, match) => null),
Token("\\-?[0-9]+\\.[0-9]+", (token, match) => double.Parse(match)),
Token("\\-?[0-9]+", (token, match) => int.Parse(match)),
// String literals
Token("\\\"(\\\\\\n|\\\\t|\\\\n|\\\\r|\\\\\\\"|[^\\\"])*\\\"", (token, match) => match.Substring(1, match.Length - 2)),
// Identifiers
Token("[_A-Za-z][_0-9A-Za-z]*", NewSymbol)
);
}
}
public class Node { }
public class HearPerceptorState : Node
{
public string Ident { get; set; }
public double Value { get; set; }
}
public class HingeJointState : Node
{
public string Ident { get; set; }
public double Value { get; set; }
}
public class Polar : Tuple<double, double, double>
{
public Polar(double a, double b, double c) : base(a, b, c) { }
}
public class ForceResistancePerceptorState : Node
{
public string Ident { get; set; }
public Polar Polar { get; set; }
}
public class Test
{
public static void Main()
{
var input = #"
(
(Hear 12.3 HelloWorld)
(HJ LAJ1 -0.42)
(FRP lf (pos 2.3 1.7 0.4))
)
";
// visit DRY helpers
Func<object, object[]> asRecord = value => (object[])value;
Func<object, Symbol> symbol = value => SExpressionSyntax.Symbol(value);
Func<object, string> identifier = value => symbol(value).Id;
// the SExpr visit, proper
Func<object[], Node[]> visitAll = null;
Func<object[], Node> visitHear = null;
Func<object[], Node> visitHJ = null;
Func<object[], Node> visitFRP = null;
visitAll =
all =>
all.
Select
(
item =>
symbol(asRecord(item)[0]).Id != "Hear" ?
(
symbol(asRecord(item)[0]).Id != "HJ" ?
visitFRP(asRecord(item))
:
visitHJ(asRecord(item))
)
:
visitHear(asRecord(item))
).
ToArray();
visitHear =
item =>
new HearPerceptorState { Value = (double)asRecord(item)[1], Ident = identifier(asRecord(item)[2]) };
visitHJ =
item =>
new HingeJointState { Ident = identifier(asRecord(item)[1]), Value = (double)asRecord(item)[2] };
visitFRP =
item =>
new ForceResistancePerceptorState
{
Ident = identifier(asRecord(item)[1]),
Polar =
new Polar
(
(double)asRecord(asRecord(item)[2])[1],
(double)asRecord(asRecord(item)[2])[2],
(double)asRecord(asRecord(item)[2])[3]
)
};
var syntax = new CustomSExpressionSyntax();
var sexpr = syntax.Parse(input);
var nodes = visitAll(asRecord(sexpr));
Console.WriteLine("SO_3051254");
Console.WriteLine();
Console.WriteLine(nodes.Length == 3);
Console.WriteLine(nodes[0] is HearPerceptorState);
Console.WriteLine(nodes[1] is HingeJointState);
Console.WriteLine(nodes[2] is ForceResistancePerceptorState);
}
}
Testable here:
https://repl.it/CnLC/1
'HTH,