I got a lot of data from a database, which are results from a search function. Now I've a List<string[]> which has duplicated elements of type string[]. The string[] in the list are the search results.
I know that every new created array has a different instance so i can't use MyListOfArrays.Distinct().ToList().
Maybe it's a very basic question...
My question is, are there any functions built in to remove a duplicated string[] form the List<string[]>? Or do I have to write it by my selfe?
Thank you
You can use distinct method with custom equalityComparer
IEnumerable<string[]> distinct = inputStringArrayList.Distinct(new EqualityComparer());
EqualityComparer
class EqualityComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if (x.Length != y.Length)
{
return false;
}
if (x.Where((t, i) => t != y[i]).Any())
{
return false;
}
return true;
}
public int GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
}
Alternative Equals Method
public bool Equals(string[] x, string[] y)
{
return x.SequenceEqual(y);
}
Here I am assuming you are having exact same string arrays with same content at same index.
Correction from Matthew Watson
public int GetHashCode(string[] obj)
{
if (obj == null)
return 0;
int hash = 17;
unchecked
{
foreach (string s in obj)
hash = hash*23 + ((s == null) ? 0 : s.GetHashCode());
}
return hash;
}
I have corrected the answer from #Muctadir Dinar.
(He deserves credit for the answer - I am just correcting it and providing a complete test program):
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
sealed class EqualityComparer: IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] obj)
{
if (obj == null)
return 0;
int hash = 17;
unchecked
{
foreach (string s in obj)
hash = hash*23 + ((s == null) ? 0 : s.GetHashCode());
}
return hash;
}
}
class Program
{
private void run()
{
var list = new List<string[]>
{
strings(1, 10),
strings(2, 10),
strings(3, 10),
strings(2, 10),
strings(4, 10)
};
dump(list);
Console.WriteLine();
var result = list.Distinct(new EqualityComparer());
dump(result);
}
static void dump(IEnumerable<string[]> list)
{
foreach (var array in list)
Console.WriteLine(string.Join(",", array));
}
static string[] strings(int start, int count)
{
return Enumerable.Range(start, count)
.Select(element => element.ToString())
.ToArray();
}
static void Main(string[] args)
{
new Program().run();
}
}
}
A simple and not very efficient approach would be to use string.Join on the string[]:
list = list
.GroupBy(strArr => string.Join("|", strArr))
.Select(g => g.First())
.ToList();
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've got a List<Card>, and I want to sort these cards
So, I'm looking for a method to sort them with different criterias, like their ID, their Name ...
public class Card : IComparer
{
public string ID;
public string Name;
public int CompareId(object firstCard, object secondCard)
{
Card c1 = (Card)firstCard;
Card c2 = (Card)secondCard;
return c1.Id.CompareTo(c2.Id);
}
}
But then, visual studio sent me an error :
'Card' does not implement interface member 'IComparer<Card>.Compare(Card, Card)'
You, probably, want to have your class Comparable not a Comparator
public class Card : IComparable<Card>
{
public string ID;
public string Name;
public int CompareTo(Card other)
{
if (null == other)
return 1;
// string.Compare is safe when Id is null
return string.Compare(this.Id, other.Id);
}
}
then
List<Card> myList = ...
myList.Sort();
Edit: If you want to have several criteria to choose from, you have to implement several Comparers as separated classes, e.g.
public sealed class CardByIdComparer : IComparer<Card>
{
public int Compare(Card x, Card y)
{
if (object.ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
else
return string.Compare(x.Id, y.Id);
}
}
and when sorting provide the required:
List<Card> myList = ...
myList.Sort(new CardByIdComparer());
Edit 2: (inspired by spender's library). If you want to combine several comparers into one (i.e. use comparer1, on tie - comparer2 etc.)
public sealed class ComparerCombined<T> : IComparer<T> {
private IComparer<T>[] m_Comparers;
public ComparerCombined(params IComparer<T>[] comparers) {
if (null == comparers)
throw new ArgumentNullException(nameof(comparers));
m_Comparers = comparers
.Select(item => item == null ? Comparer<T>.Default : item)
.Where(item => item != null)
.Distinct()
.ToArray();
}
public int Compare(T x, T y) {
if (object.ReferenceEquals(x, y))
return 0;
else if (null == x)
return -1;
else if (null == y)
return 1;
foreach (var comparer in m_Comparers) {
int result = comparer.Compare(x, y);
if (result != 0)
return result;
}
return 0;
}
}
usage:
myList.Sort(new ComparerCombined(
new CardByIdComparer(), // Sort By Id
new CardByNameComparer() // On tie (equal Id's) sort by name
));
The easiest way You can use Linq:
List<Card> objSortedList = objListObject.OrderBy(o=>o.ID).ToList();
or
List<Card> objSortedList = objListObject.OrderByDescending(o=>o.ID).ToList();
Good examples for demonstrate the concept of
List<T>.Sort(IComparer <T>) method check the link please.
IComparer<T> in this example compare method used for strings IComparer<T>
but you can use this for ID(int) too.
using System;
using System.Collections.Generic;
class GFG : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null)
{
return 0;
}
// "CompareTo()" method
return x.CompareTo(y);
}
}
public class geek
{
public static void Main()
{
List<string> list1 = new List<string>();
// list elements
list1.Add("C++");
list1.Add("Java");
list1.Add("C");
list1.Add("Python");
list1.Add("HTML");
list1.Add("CSS");
list1.Add("Scala");
list1.Add("Ruby");
list1.Add("Perl");
int range = 4;
GFG gg = new GFG();
Console.WriteLine("\nSort a range with comparer:");
// sort the list within a
// range of index 1 to 4
// where range = 4
list1.Sort(1, range, gg);
Console.WriteLine("\nBinarySearch and Insert Dart");
// Binary Search and storing
// index value to "index"
int index = list1.BinarySearch(0, range,
"Dart", gg);
if (index < 0)
{
list1.Insert(~index, "Dart");
range++;
}
}
}
You need to implement IComparer
public int Compare(Card card1, Card card2)
{
if (card1.ID > card2.ID)
return 1; //move card1 up
if (card2.ID < card1.ID)
return -1; //move card2 up
return 0; //do nothing
}
I have two Lists that I'm trying to compare. What I need to accomplish is essentially to remove the items that are duplicated between the two lists and only keep the objects that are different. Right now, I'm inserting the non-duplicate data into a new list.
The data I'm using here...
LIST1
("b",2)
("c",3)
LIST2
("a",1)
("b",2)
("c",3)
("d",4)
NEWLIST
("a",1)
("d",4)
Here's what I have so far...
My object:
public class TestClass
{
protected string teststring;
protected int testint;
public string TestString
{
get { return teststring; }
set { teststring = value; }
}
public int TestInt
{
get { return testint; }
set { testint = value; }
}
public TestClass() { }
}
My compare logic:
private static List<TestClass> CompareCodes(List<TestClass> list1, List<TestClass> list2)
{
List<TestClass> newList = new List<TestClass>();
foreach (TestClass s in list2)
{
if (list1.Contains(s) == false)
{
newList.Add(s);
}
}
if (newList.Count != 0)
return newList;
else
return null;
}
The new list will be used to insert data into a database table. If it's null, no action will be taken. I'm using .NET 2.0 in this app (it's an enhancement to an older app), so I can't use LINQ. So is there any other way to make this work that I'm missing? Or is there a better way to do this? I haven't been able to find anything (maybe just not looking hard enough) to accomplish what I'm trying to do.
Thanks in advance!
So you're almost there, but you'll need to override the Equals method on your class to make it work:
public class TestClass
{
public override bool Equals(object y)
{
TestClass newY = y as TestClass;
if (newY == null) { return false; }
return newY.TestString == this.TestString &&
newY.TestInt == this.TestInt;
}
public override int GetHashCode()
{
unchecked // Overflow is fine, just wrap
{
int hash = 17;
// Suitable nullity checks etc, of course :)
hash = hash * 23 + this.TestInt.GetHashCode();
hash = hash * 23 + this.TestString == null ?
0 :
this.TestString.GetHashCode();
return hash;
}
}
}
Use Jon Skeet's answer here to implement your hash code.
In the code that you have provided you are only keeping the items of list2 that are not in list1. but how about the items that are in list1 but not in list2 ? and since you mention
What I need to accomplish is essentially to remove the items that are duplicated between the two lists and only keep the objects that are different
This code belows returns a new list with the items that are unique in both lists
private static List<TestClass> CompareCodes(List<TestClass> list1, List<TestClass> list2)
{
List<TestClass> newList ;
newList = new List<TestClass>();
//All the items in list1 that are not in list2 are added
foreach (TestClass s in list1)
{
if ( ! list2.Contains(s))
{
newList.Add(s);
}
}
//All the items in list2 that are not in list1 are added
foreach (TestClass s in list2)
{
if ( ! list1.Contains(s))
{
newList.Add(s);
}
}
return newList;
}
And in your class
public class TestClass implements IEquatable
{
protected string teststring;
protected int testint;
public string TestString
{
get { return teststring; }
set { teststring = value; }
}
public int TestInt
{
get { return testint; }
set { testint = value; }
}
public override bool Equals(object y)
{
TestClass newY = y as TestClass;
if (newY == null) { return false; }
return newY.TestString == this.TestString &&
newY.TestInt == this.TestInt;
}
public override int GetHashCode()
{
// use this example or implement some hash code logic
return this.TestInt.GetHashCode() ;
}
public TestClass() { }
}
private static List<TestClass> CompareCodes(List<TestClass> list1,
List<TestClass> list2)
{
List<TestClass> newList = new List<TestClass>();
bool found = false;
foreach (TestClass s in list2)
{
foreach (TestClass t in list1)
{
//let's say that teststring is your object id / key
if(s.TestString==t.TestString )
{
found = true;
break;
}
}
if(!found)
newList.Add(s);
found=false;
}
// do the something for the List1
foreach (TestClass s in list1)
{
foreach (TestClass t in list2)
{
//let's say that teststring is your object id / key
if(s.TestString==t.TestString )
{
found = true;
break;
}
}
if(!found)
newList.Add(s);
found=false;
}
if (newList != null)
return newList;
else
return null;
}
I've got various chapters with different depths.
so there are 14.1 and 14.4.2 and 14.7.8.8.2 and so on.
Alphanumerical sorted the 14.10 will appear before 14.2. That's bad. It should come after 14.9.
Is there an easy way to sort theese, without adding leading zeros? f.e. with linq?
public class NumberedSectionComparer : IComparer<string>
{
private int Compare(string[] x, string[]y)
{
if(x.Length > y.Length)
return -Compare(y, x);//saves needing separate logic.
for(int i = 0; i != x.Length; ++i)
{
int cmp = int.Parse(x[i]).CompareTo(int.Parse(y[i]));
if(cmp != 0)
return cmp;
}
return x.Length == y.Length ? 0 : -1;
}
public int Compare(string x, string y)
{
if(ReferenceEquals(x, y))//short-cut
return 0;
if(x == null)
return -1;
if(y == null)
return 1;
try
{
return Compare(x.Split('.'), y.Split('.'));
}
catch(FormatException)
{
throw new ArgumentException();
}
}
}
I did this right now, need some tests:
using System;
using System.Collections.Generic;
using System.Linq;
namespace TestesConsole
{
class Program
{
static void Main(string[] args)
{
string[] vers = new[]
{
"14.10",
"14.9",
"14.10.1",
};
var ordered = vers.OrderBy(x => x, new VersionComparer()).ToList();
}
}
public class VersionComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string[] xs = x.Split('.');
string[] ys = y.Split('.');
int maxLoop = Math.Min(xs.Length, ys.Length);
for (int i = 0; i < maxLoop; i++)
{
if(int.Parse(xs[i]) > int.Parse(ys[i]))
{
return 1;
}
else if(int.Parse(xs[i]) < int.Parse(ys[i]))
{
return -1;
}
}
if(xs.Length > ys.Length)
{
return 1;
}
else if(xs.Length < ys.Length)
{
return -1;
}
return 0;
}
}
}
var headers = new List<string> {"14.1.2.3", "14.1", "14.9", "14.2.1", "14.4.2", "14.10.1.2.3.4", "14.7.8.8.2"};
headers.Sort(new MySorter());
class MySorter : IComparer<string>
{
public int Compare(string x, string y)
{
IList<string> a = x.Split('.');
IList<string> b = y.Split('.');
int numToCompare = (a.Count < b.Count) ? a.Count : b.Count;
for (int i = 0; i < numToCompare; i++)
{
if (a[i].Equals(b[i]))
continue;
int numa = Convert.ToInt32(a[i]);
int numb = Convert.ToInt32(b[i]);
return numa.CompareTo(numb);
}
return a.Count.CompareTo(b.Count);
}
}
Using IComparer hast the big disadvantage of repeating the rather expensive calculation very often, so I thought precalculating an order criterium would be a good idea:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ChapterSort
{
class Program
{
static void Main(string[] args)
{
String[] chapters=new String[] {"14.1","14.4.2","14.7.8.8.2","14.10","14.2","14.9","14.10.1.2.3.4","14.1.2.3" };
IEnumerable<String> newchapters=chapters.OrderBy(x => new ChapterNumerizer(x,256,8).NumericValue);
foreach (String s in newchapters) Console.WriteLine(s);
}
}
public class ChapterNumerizer
{
private long numval;
public long NumericValue {get{return numval;}}
public ChapterNumerizer (string chapter,int n, int m)
{
string[] c = chapter.Split('.');
numval=0;
int j=0;
foreach (String cc in c)
{
numval=n*numval+int.Parse(cc);
j++;
}
while (j<m)
{
numval*=n;
j++;
}
}
}
}
This solution is more general.
public class SequenceComparer<T> : IComparer<IEnumerable<T>> where T : IComparable<T>
{
public int Compare(IEnumerable<T> x, IEnumerable<T> y)
{
IEnumerator<T> enx = x.GetEnumerator();
IEnumerator<T> eny = y.GetEnumerator();
do
{
bool endx = enx.MoveNext();
bool endy = eny.MoveNext();
if (!endx && !endy)
return 0;
if (!endx)
return -1;
if (!endy)
return 1;
var comp = enx.Current.CompareTo(eny.Current);
if(comp != 0)
return comp;
} while (true);
}
}
Then use:
var sv = vers.Select(v => new { Key = v, Split = v.Split('.').Select(Int32.Parse) });
var ordered = sv.OrderBy(x => x.Split, new SequenceComparer<int>()).Select(x => x.Key);
As a small LINQ one-liner:
List<string> chapters= new List<string>()
{
"14.1",
"14.4.2",
"14.7.8.8.2",
"14.10",
"14.2"
};
chapters.OrderBy(c => Regex.Replace(c, "[0-9]+", match => match.Value.PadLeft(10, '0')));
Independent of levels but surely not the best performance...
Credits are going to https://stackoverflow.com/a/5093939/226278
I'd like to create my own class extending array of ints. Is that possible? What I need is array of ints that can be added by "+" operator to another array (each element added to each), and compared by "==", so it could (hopefully) be used as a key in dictionary.
The thing is I don't want to implement whole IList interface to my new class, but only add those two operators to existing array class.
I'm trying to do something like this:
class MyArray : Array<int>
But it's not working that way obviously ;).
Sorry if I'm unclear but I'm searching solution for hours now...
UPDATE:
I tried something like this:
class Zmienne : IEquatable<Zmienne>
{
public int[] x;
public Zmienne(int ilosc)
{
x = new int[ilosc];
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
{
return false;
}
return base.Equals((Zmienne)obj);
}
public bool Equals(Zmienne drugie)
{
if (x.Length != drugie.x.Length)
return false;
else
{
for (int i = 0; i < x.Length; i++)
{
if (x[i] != drugie.x[i])
return false;
}
}
return true;
}
public override int GetHashCode()
{
int hash = x[0].GetHashCode();
for (int i = 1; i < x.Length; i++)
hash = hash ^ x[i].GetHashCode();
return hash;
}
}
Then use it like this:
Zmienne tab1 = new Zmienne(2);
Zmienne tab2 = new Zmienne(2);
tab1.x[0] = 1;
tab1.x[1] = 1;
tab2.x[0] = 1;
tab2.x[1] = 1;
if (tab1 == tab2)
Console.WriteLine("Works!");
And no effect. I'm not good with interfaces and overriding methods unfortunately :(. As for reason I'm trying to do it. I have some equations like:
x1 + x2 = 0.45
x1 + x4 = 0.2
x2 + x4 = 0.11
There are a lot more of them, and I need to for example add first equation to second and search all others to find out if there is any that matches the combination of x'es resulting in that adding.
Maybe I'm going in totally wrong direction?
For a single type, it is pretty easy to encapsulate, as below. Note that as a key you want to make it immutable too. If you want to use generics, it gets harder (ask for more info):
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
static class Program {
static void Main() {
MyVector x = new MyVector(1, 2, 3), y = new MyVector(1, 2, 3),
z = new MyVector(4,5,6);
Console.WriteLine(x == y); // true
Console.WriteLine(x == z); // false
Console.WriteLine(object.Equals(x, y)); // true
Console.WriteLine(object.Equals(x, z)); // false
var comparer = EqualityComparer<MyVector>.Default;
Console.WriteLine(comparer.GetHashCode(x)); // should match y
Console.WriteLine(comparer.GetHashCode(y)); // should match x
Console.WriteLine(comparer.GetHashCode(z)); // *probably* different
Console.WriteLine(comparer.Equals(x,y)); // true
Console.WriteLine(comparer.Equals(x,z)); // false
MyVector sum = x + z;
Console.WriteLine(sum);
}
}
public sealed class MyVector : IEquatable<MyVector>, IEnumerable<int> {
private readonly int[] data;
public int this[int index] {
get { return data[index]; }
}
public MyVector(params int[] data) {
if (data == null) throw new ArgumentNullException("data");
this.data = (int[])data.Clone();
}
private int? hash;
public override int GetHashCode() {
if (hash == null) {
int result = 13;
for (int i = 0; i < data.Length; i++) {
result = (result * 7) + data[i];
}
hash = result;
}
return hash.GetValueOrDefault();
}
public int Length { get { return data.Length; } }
public IEnumerator<int> GetEnumerator() {
for (int i = 0; i < data.Length; i++) {
yield return data[i];
}
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
public override bool Equals(object obj)
{
return this == (obj as MyVector);
}
public bool Equals(MyVector obj) {
return this == obj;
}
public override string ToString() {
StringBuilder sb = new StringBuilder("[");
if (data.Length > 0) sb.Append(data[0]);
for (int i = 1; i < data.Length; i++) {
sb.Append(',').Append(data[i]);
}
sb.Append(']');
return sb.ToString();
}
public static bool operator ==(MyVector x, MyVector y) {
if(ReferenceEquals(x,y)) return true;
if(ReferenceEquals(x,null) || ReferenceEquals(y,null)) return false;
if (x.hash.HasValue && y.hash.HasValue && // exploit known different hash
x.hash.GetValueOrDefault() != y.hash.GetValueOrDefault()) return false;
int[] xdata = x.data, ydata = y.data;
if(xdata.Length != ydata.Length) return false;
for(int i = 0 ; i < xdata.Length ; i++) {
if(xdata[i] != ydata[i]) return false;
}
return true;
}
public static bool operator != (MyVector x, MyVector y) {
return !(x==y);
}
public static MyVector operator +(MyVector x, MyVector y) {
if(x==null || y == null) throw new ArgumentNullException();
int[] xdata = x.data, ydata = y.data;
if(xdata.Length != ydata.Length) throw new InvalidOperationException("Length mismatch");
int[] result = new int[xdata.Length];
for(int i = 0 ; i < xdata.Length ; i++) {
result[i] = xdata[i] + ydata[i];
}
return new MyVector(result);
}
}
Its not permitted to extend the array class, see the reference: http://msdn.microsoft.com/en-us/library/system.array.aspx
You could either implement IList (which has the basic methods), or encapsulate an Array in your class and provide conversion operators.
Please let me know if you need more detail.
Can you not just use the List class? This already does what you want via the AddRange method.
implement the ienumerable interface