TMP_TextUtilities fails to find entire link string - c#

I'm constructing links in certain texts in my game, using the <link> tag with TMP. These links contains a GUID in order to identify what it is pointing to.
<link=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>Some text</link>
This is working for my most part but when there a e in the GUID TMP cuts off at the first occurrence, so for example if there was an e at position 10 TMP only manages to return the 10 first characters in the GUID.
This is what happens:
xxxxxxxx-xxex-xxxx-xxxx-xxxxxxxxxxxx
xxxxxxxx-xx
The method generating the text
public string CharacterLink(CharacterInfo info)
{
return $"<color={characterColor.Format()}><link={info.Id}>{info.Fullname}</link></color>";
}
The methods for fetching the link
public Entity GetLinkCharacter(TextMeshProUGUI text)
{
TMP_LinkInfo info = GetLink(text);
if (info.Equals(default(TMP_LinkInfo)))
{
return null;
}
return entities.GetEntity(Guid.Parse(info.GetLinkID()));
}
private TMP_LinkInfo GetLink(TextMeshProUGUI text)
{
int index = TMP_TextUtilities.FindIntersectingLink(text, Input.mousePosition, null);
if (index == -1)
{
return default;
}
return text.textInfo.linkInfo[index];
}
Whenever there's an e in the GUID it throws an exception every time it tries to parse it since the GUID gets cut off on the first occurrence of an e.

The lack of quotation marks was the issue. Silly me forgot to add them.
public string CharacterLink(CharacterInfo info)
{
return $"<color={characterColor.Format()}><link={info.Id}>{info.Fullname}</link></color>";
}
had to be converted into this
public string CharacterLink(CharacterInfo info)
{
return $"<color=\"{characterColor.Format()}\"><link=\"{info.Id}\">{info.Fullname}</link></color>";
}

Related

How to use .OrderBy for multiple conditions, one only sometimes used?

Sorry if the title, is confusing, I had some trouble putting my problem into words.
I have a List, where every string is composed of 2 words, delimited by space.
For example:
{ "word1 word2", "wordA wordB", "dog cat", "mouse cat" }
I want to use OrderBy to sort the list by the 2nd word, if any words are equal, I then want to sort those by the 1st word. I'm having trouble figuring out how to handle the 2nd condition for this (sorting by 1st word only if 2nd words are equal).
I originally tried:
public List<string> SpecialSort(List<string> text)
{
return text.OrderBy(x => x.Split(' ')[1]).ThenBy(x => x.Split(' ')[0]);
}
but this seems to just sort first by the 2nd word, and then re-sort everything by the 1st word. Is there a way for me to do this where I only sort by 1st word if the 2nd words are equal?
Thanks!
My advice would be to split the text into words, while keeping the original text in a Select. Then sort the sequence and finally remove the split words.
Requirement
Input: a sequence of strings, every string has exactly one space.
This space is neither the first nor the last character.
The characters before this one and only space are defined as the first word.
The characters after the space are defined as the second word.
Output: Sort the sequence by 2nd word, then by 1st word.
IEnumerable<string> inputTexts = ...
const string splitChar = ' ';
// first add the split words
var sortedSequence = inputTexts.Select(txt => new
{
Original = txt,
Split = txt.Split(splitChar, StringSplitOptions.None),
})
// then sort by the split words
.OrderBy(splitTxt => splitTxt.Split[1])
.ThenBy(splitTxt => splitTxt.Split[0])
// finally remove the split words
.Select(splitTxt => splitTxt.Original);
Create intermediate results within an .OrderBy() statement can be painful, cause the comparer needs to possibly call them multiple times on each object. Also to make it better maintainable I would write a class that gets the original value, creates the desired elements and feeding these intermediate objects into a specific comparer that can sort them. At the end just get the original value out of the intermediate class and you're done.
A rough sketch for your example would look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Program
{
private static void Main(string[] args)
{
var words = new List<string>{"word1 word2", "wordA wordB", "dog cat", "mouse cat"};
var ordered = words
.Select(SpecialComparerInstance.Create)
.OrderBy(special => special, SpecialComparer.Default)
.Select(special => special.Value);
foreach (var item in ordered)
{
Console.WriteLine(item);
}
}
}
public class SpecialComparerInstance
{
public static SpecialComparerInstance Create(string value) => new SpecialComparerInstance(value);
public SpecialComparerInstance(string value)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException(nameof(value));
var elements = value.Split(' ');
if (elements.Length != 2)
throw new ArgumentException("Must contain exactly one space character", nameof(value));
Value = value;
FirstOrderValue = elements[1];
SecondOrderValue = elements[0];
}
public string Value { get; }
public string FirstOrderValue { get; }
public string SecondOrderValue { get; }
}
public class SpecialComparer : IComparer<SpecialComparerInstance>
{
public static readonly IComparer<SpecialComparerInstance> Default = new SpecialComparer(StringComparer.Ordinal);
private readonly StringComparer _comparer;
public SpecialComparer(StringComparer comparer)
{
_comparer = comparer;
}
public int Compare(SpecialComparerInstance x, SpecialComparerInstance y)
{
if (ReferenceEquals(x, y))
return 0;
if (ReferenceEquals(x, null))
return 1;
if (ReferenceEquals(y, null))
return -1;
var result = _comparer.Compare(x.FirstOrderValue, y.FirstOrderValue);
if (result == 0)
result = _comparer.Compare(x.SecondOrderValue, y.SecondOrderValue);
return result;
}
}

If I'm using non-nullable reference types, how do I show that I didn't find anything?

I've enabled the C# 8.0 non-nullable reference types feature in one of my projects, but now I'm unclear about how to represent missing data.
For example, I'm reading a file whose lines are colon-separated key/value pairs. Sometimes there's more than one colon on a line. In that case, the text before the first colon is the key, and the rest is the value. My code to parse each line looks like this:
public (string key, string value) GetKeyValue(string line)
{
var split = line.Split(':');
if (split.Length == 2)
return (split[0].Trim(), split[1].Trim());
else if (split.Length > 2)
{
var joined = string.Join(":", split.ToList().Skip(1));
return (split[0].Trim(), joined.Trim());
}
else
{
Debug.Print($"Couldn't parse this into key/value: {line}");
return (null, null);
}
}
What this does: If we have just one colon, return the key and value. If we have more than one, join the rest of the text after the first colon, then return the key and value. Otherwise we have no colons and can't parse it, so return a null tuple. (Let's assume this last case can reasonably happen; I can't just throw and call it a bad file.)
Obviously that last line gets a nullability warning unless I change the declaration to
public (string? key, string? value) GetKeyValue(string line)
Now in F# I would just use an Option type and in the no-colon case, I'd return None.
But C# doesn't have an Option type. I could return ("", ""), but to me that doesn't seem better than nulls.
In a case like this, what's a good way to say "I didn't find anything" without using nulls?
You could include if the result was successful in parsing by just returning a flag:
public class Result
{
private Result(){}
public bool Successful {get;private set;} = false;
public string Key {get; private set;} = string.Empty;
public string Value {get; private set;} = string.Empty;
public static Successful(string key, string value)
{
return new Result
{
Successful = true,
Key = key,
Value = value
};
}
public static Failed()
{
return new Result();
}
}
public Result GetKeyValue(string line){
return Result.Failed();
}
Then you could use it like
var result = GetKeyValue("yoda");
if(result.Successful)
{
// do something...
}
Alternatiely you could return 2 diffrent types and use pattern matching 👍
Actually, I realize now that part of the problem is that my method is doing two separate things:
Determine whether the line has a key.
Return the key and value.
Thus the return value has to indicate both whether there's a key and value, and what the key and value are.
I can simplify by doing the first item separately:
bool HasKey(string line)
{
var split = line.Split(':');
return split.Length >= 2;
}
Then in the method I posted, if there's no key, I can throw and say that the lines need to be filtered by HasKey first.
Putting on my functional thinking cap, an idiomatic return type would be IEnumerable<(string?,string?)>. The only change to your code would be to change return to yield return, and to remove the return statement if nothing is found.
public IEnumerable<(string? key, string? value)> GetKeyValue(string line)
{
var split = line.Split(':');
if (split.Length == 2)
return (split[0].Trim(), split[1].Trim());
else if (split.Length > 2)
{
var joined = string.Join(":", split.ToList().Skip(1));
yield return (split[0].Trim(), joined.Trim());
}
else
{
Debug.Print($"Couldn't parse this into key/value: {line}");
}
}
The caller then has several options on how to handle the response.
If they want to check if the key was found the old-fashioned eway, do this:
var result = GetKeyValue(line).SingleOrDefault();
if (!result.HasValue) HandleKeyNotFound();
If they prefer to throw an exception if the key is not found, they'd do this:
var result = GetKeyValue(line).Single();
If they just want to be quiet about it they can use ForEach, which will use the key and value if they are found and simply do nothing if they are not:
foreach (var result in GetKeyValue(line)) DoSomething(result.Item1, result.Item2);
Also, for what it's worth, I'd suggest using KeyValuePair instead of a tuple, since it clearly communicates the purpose of the fields.

substring out of range

I am trying to extract the number out of the last part of the string, I have wrote a function to do this but am having problems with out of range index.
Here is the string
type="value" cat=".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.1" descCat=".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.3"
and here is my function
private static string ExtractDescOID(string property)
{
string result = "";
int startPos = property.LastIndexOf("descOid=\"") + "descOid=\"".Length;
int endPos = property.Length - 1;
if (endPos - startPos != 1)
{
//This now gets rid of the first . within the string.
startPos++;
result = property.Substring(startPos, endPos);
}
else
{
result = "";
}
if (startPos == endPos)
{
Console.WriteLine("Something has gone wrong");
}
return result;
}
I want to be able to get 1.3.6.1.4.1.26928.1.1.1.2.1.2.1.3 this part of the string. I have stepped through the code, the string length is 99 however when AND MY startPos becomes 64 and endPos becomes 98 which is actually within the range.
The second argument to Substring(int, int) isn't the "end position", but the length of the substring to return.
result = property.Substring(startPos, endPos - startPos);
Read the documentation again, the second value is the length, not the index.
As found on MSDN:
public string Substring(
int startIndex,
int length
)
A different approach to this problem could be through using string.Split() to take care of the parsing for you. The only reason why I would propose this (other than that I like to present additional options to what's already there, plus this is the lazy man's way out) is that from a code perspective, the code is easier IMHO to decompose, and when decomposed, is easier to comprehend by others.
Here's the sample program with some comments to illustrate my point (tested, btw).
class Program
{
static void Main(string[] args)
{
var someAttributesFromAnXmlNodeIGuess =
"type=\"value\" cat=\".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.1\" descCat=\".1.3.6.1.4.1.26928.1.1.1.2.1.2.1.3\"";
var descCat = GetMeTheAttrib(someAttributesFromAnXmlNodeIGuess, "descCat");
Console.WriteLine(descCat);
Console.ReadLine();
}
// making the slightly huge assumption that you may want to
// access other attribs in the string...
private static string GetMeTheAttrib(string attribLine, string attribName)
{
var parsedDictionary = ParseAttributes(attribLine);
if (parsedDictionary.ContainsKey(attribName))
{
return parsedDictionary[attribName];
}
return string.Empty;
}
// keeping the contracts simple -
// i could have used IDictionary, which might make sense
// if this code became LINQ'd one day
private static Dictionary<string, string> ParseAttributes(string attribLine)
{
var dictionaryToReturn = new Dictionary<string, string>();
var listOfPairs = attribLine.Split(' '); // items look like type=value, etc
foreach (var pair in listOfPairs)
{
var attribList = pair.Split('=');
// we were expecting a type=value pattern... if this doesn't match then let's ignore it
if (attribList.Count() != 2) continue;
dictionaryToReturn.Add(attribList[0], attribList[1]);
}
return dictionaryToReturn;
}
}

Trying to get the address of a particular node of linkedlist in c#

I am trying to print the address of a particular node in c#.
Here is the function which finds the minimum two elements(suppose min1 and min2) in the existing linked list.
For some reasons i want to know the address of the first minimum occurred in the list(that may be min1 or min2, irrespective of which one is greater or which one is smaller, I just need to know the address of the first minimum found on traversal from head of the Linked List to NULL).
I tried to do so and my code is able to find the minimum two and also i tried to find the address of the first minimum element arrived on traversing from head in a while loop which breaks and store the address in address variable and come out of the loop.
But the problem is instead of printing the value of address it prints a long string as given below:
shekhar_final_version_Csharp.Huffman+Node
where shekhar_final_version_Csharp.Huffman is the name of namespace.
Below is code :
public Node find_two_smallest(ref Node pmin1, ref Node pmin2)
{
Node temp = tree;
Node address = tree;
Node min1 ;
min1 = new Node();
min1.freq = int.MaxValue;
Node min2;
min2 = new Node();
min2.freq = int.MaxValue;
while (temp != null)
{
if (temp.is_processed == 0)
{
if (temp.freq < min2.freq)
{
min1 = min2;
min2 = temp;
}
else if (temp.freq < min1.freq && temp.freq!=min2.freq)
{
min1 = temp;
}
temp = temp.next;
}
}
pmin1 = min1;
pmin2 = min2;
// Below is the code to find the address of first minimum arriving on traversal
// of List.
while (temp != null)
{
if (temp.freq == min2.freq || temp.freq == min1.freq )
{
address=temp;
break;
}
}
// See below the output corresponding to it.
Console.WriteLine("Address check: {0} Its value is : {1}",
address, address.freq);
return address;
}
It's corresponding output is:
Address check: shekhar_final_version_Csharp.Huffman+Node Its value is : 88
where it is showing correctly the value at that location (min1/min2=88) but not showing address, I want to see the integer value of the address.
Could some one please help me in achieving my target ?hanks.
When i try to see the address using & operaor it gives following error.
hp#ubuntu:~/Desktop/Internship_Xav/c#$ gmcs check1.cs /unsafe
check1.cs(119,76): error CS0214: Pointers and fixed size buffers may only be used in an unsafe context
check1.cs(119,76): error CS0208: Cannot take the address of, get the size of, or declare a pointer to a managed type `shekhar_final_version_Csharp.Huffman.Node'
check1.cs(12,22): (Location of the symbol related to previous error)
Compilation failed: 2 error(s), 0 warnings
You cannot get the memory address of a managed object like this. It is simply not a supported feature of the language.
Console.WriteLine calls ToString() on the objects passed to it and then prints this string. The default behavior of ToString() is to print the type name. If you want something else, override it in your classes:
public class Node
{
...
public override string ToString()
{
return Value; // Or whatever properties you want to print.
}
}
Note: In C# you cannot get the memory address of objects. This is because the garbage collector (GC) can relocate objects when collecting. If you need to identify an object, add your own identifier as a field or property. For instance with a Guid field:
public class Node
{
private Guid _guid = Guid.NewGuid();
public override string ToString()
{
return _guid;
}
}
Or by using a static counter:
public class Node
{
private static int _counter = 0;
private int _id = _counter++;
public override string ToString()
{
return _id;
}
}
In c# to refer to the address of a refererence type use the '&' operator
Console.WriteLine("Address check: {0} Its value is : {1}", &address, address.freq);

c# Using ArrayLists inside properties

I want to be able to have a class Named Musician, with a rule/Property called Hits( which is an Array-list, with two methods called ListHits() and AddHits(string)
ListHits returns a string containing all the hits separa
ted by a comma
AddHit – adds a hit to the Hits arrayList. Each hit is
a string between 1 and 50 characters long with no l
eading
or trailing white space.
I have no idea how to go about doing this im familiar with collections and adding values to Lists and i know how to set basic properties
-- i have tried for hours on end please HELP!
public class Musician : Celebrity
{
private string _hits;
public string Hits
{
get { return _hits; }
set
{
if (value.Length < 1)
{
throw new Exception("need more then 2 characters");
}
if (value.Length > 50)
{
throw new Exception("needs to be less then 50 characters");
}
else
{
_hits = value.Trim();
}
}
}
public Musician()
{
//
// TODO: Add constructor logic here
//
}
}
First off, you should try using a List<string> rather than an ArrayList. ArrayList was what you used before C# added generics in version 2.0. List<T> allows you to retain typing information about the items in the list, which enables you to more easily write correct code.
The code you posted didn't seem to really match the details you were asking for, but something like this should do what you specified:
public class Musician
{
private List<string> _hits;
public string ListHits()
{
return string.Join(", ", _hits);
}
public void AddHit(string hit)
{
/*
* validate the hit
*/
_hits.Add(hit);
}
}
The key is using string.Join to convert the _hits list into a comma-delimited string. From there, the rest is just basic C# concepts.

Categories

Resources