3

Hello I am trying to find unique strings in two dataGridView tables populated from XML files. The code I have made runs without issue however it fails to detect when I change a string (making it unique) in one of the tables. Is there anything wrong with my logic?

    private void button5_Click(object sender, EventArgs e)
    {
        string[] column1 = new string[dataGridView1.Rows.Count];
        string[] column2 = new string[dataGridView2.Rows.Count];
        int unique = 0;
        bool found = false;
        for (int i = 0; i < dataGridView1.Rows.Count; i++)
        {
            column1[i] = Convert.ToString(dataGridView1.Rows[i].Cells[2].Value);
        }
        for (int i = 0; i < dataGridView2.Rows.Count; i++)
        {
            column2[i] = Convert.ToString(dataGridView2.Rows[i].Cells[2].Value);
        }
        for (int i = 0; i < column1.Length; i++)
        {
            for (int j = 0; j < column2.Length; j++)
            {
                if (column1[i] == column2[j])
                {
                    found = true;
                }
            }
            if (found == false)
            {
                unique++;
                found = false;
            }
        }
        MessageBox.Show(unique + " unique strings found!"); 
    }

The final solution needs to be able to return the cells that contain unique strings so that I can highlight them to the user. Thanks a lot for your help!

bag-man
  • 1,423
  • 2
  • 19
  • 23

5 Answers5

3

Easy with linq:

array1.Except(array2).Concat(array2.Except(array1))

in response to your comment, you can simulate a full outer join with two left joins, and look for nulls in the output. Any side of the join that is not matched on the other side can be considered unique. Using the following extensions for the join:

public static class LinqEx
{
    public static IEnumerable<TResult> 
        LeftOuterJoin<TOuter, TInner, TKey, TResult>(
            this IEnumerable<TOuter> outer, 
            IEnumerable<TInner> inner, 
            Func<TOuter, TKey> outerKeySelector, 
            Func<TInner, TKey> innerKeySelector, 
            Func<TOuter, TInner, TResult> resultSelector)
    {
        return outer
            .GroupJoin(
                inner, 
                outerKeySelector, 
                innerKeySelector, 
                (a, b) => new
                {
                    a,
                    b
                })
            .SelectMany(
                x => x.b.DefaultIfEmpty(), 
                (x, b) => resultSelector(x.a, b));
    }

    public static IEnumerable<TResult> 
        FullOuterJoin<TSet1, TSet2, TKey, TResult>(
            this IEnumerable<TSet1> set1, 
            IEnumerable<TSet2> set2, 
            Func<TSet1, TKey> set1Selector, 
            Func<TSet2, TKey> set2Selector, 
            Func<TSet1, TSet2, TResult> resultSelector)
    {
        var leftJoin = set1.
            LeftOuterJoin(
                set2, 
                set1Selector, 
                set2Selector, 
                (s1, s2) => new {s1, s2});
        var rightJoin = set2
            .LeftOuterJoin(
                set1, 
                set2Selector, 
                set1Selector, 
                (s2, s1) => new {s1, s2});
        return leftJoin.Union(rightJoin)
            .Select(x => resultSelector(x.s1, x.s2));

    }
}

you can then create anonymous objects that capture the additional data such as the index of the item and where it comes from, and outer join them. Results with items on both sides of the join are filtered out (because they exist in both sets), so the result set now only contains items that are unique to one of the sets.

void Main()
{
    var set1 = new[] {"a", "b", "c"};
    var set2 = new[] {"b", "c", "d", "d"};
    var annotatedSet1 = set1
        .Select((item,index) => new {src = "set1", index, item});
    var annotatedSet2 = set2
        .Select((item,index) => new {src = "set2", index, item});

    var uniques = annotatedSet1
        .FullOuterJoin(
            annotatedSet2, 
            x => x.item, 
            x => x.item,
            (s1, s2) => new {s1, s2})
        .Where(x => x.s1 == null || x.s2 == null)
        .Select(x => x.s1 ?? x.s2);
}

which would yield the result:

{src="set1", index=0, item="a"}
{src="set2", index=2, item="d"}
{src="set2", index=3, item="d"}
spender
  • 117,338
  • 33
  • 229
  • 351
  • 2
    But will I be able to retrieve the positions that the unique strings are in? – bag-man Aug 22 '12 at 13:19
  • @OwenGarland You can use the `select` overload with an index to generate an anonymous class with both the index and the piece of data, and then continue on with the method shown here. – Servy Aug 22 '12 at 13:44
0

you can use link operator, Union and Distinct operators

var temp =  column1.Union(column2);
var result   = temp.Distinct();
Aghilas Yakoub
  • 28,516
  • 5
  • 46
  • 51
0

Here's a Linq's way with Enumerable.Except:

var dg1Cell2 = dataGridView1
    .Rows.Cast<DataGridViewRow>()
    .Select(r => r.Cells[2].Value.ToString());
var dg2Cell2 = dataGridView1
    .Rows.Cast<DataGridViewRow>()
    .Select(r => r.Cells[2].Value.ToString());
var uniqueInDG1 = dg1Cell2.Except(dg2Cell2);
var result = from r in dataGridView1.Rows.Cast<DataGridViewRow>()
             join u in uniqueInDG1 on r.Cells[2].Value.ToString() equals u
             select r;

Except produces a set difference, so only the unique strings in the first sequence remain. Then i join the result to the DataGridViewRows.

It is more efficient than it looks like because it is all executed lazily, except uses a set internally and join in linq-to-objects is also very efficient.

Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
0

Thanks for all your replies but the simple answer was that I wasn't resetting the value of found to false at the correct point. I appreciate the effort that went into your solutions but they are all far too complicated for my level of knowledge.

Here is the adjusted code:

    private void button5_Click(object sender, EventArgs e)
    {
        string[] column1 = new string[dataGridView1.Rows.Count];
        string[] column2 = new string[dataGridView2.Rows.Count];
        int unique = 0;
        bool found = false;
        for (int i = 0; i < dataGridView1.Rows.Count; i++)
        {
            column1[i] = Convert.ToString(dataGridView1.Rows[i].Cells[2].Value);
        }
        for (int i = 0; i < dataGridView2.Rows.Count; i++)
        {
            column2[i] = Convert.ToString(dataGridView2.Rows[i].Cells[2].Value);
        }
        for (int i = 0; i < column1.Length; i++)
        {
            for (int j = 0; j < column2.Length; j++)
            {
                if (column1[i] == column2[j])
                {
                    found = true;
                }
            }
            if (found == false)
            {
                unique++;
            }
            found = false;
        }
        MessageBox.Show(unique + " unique strings found!");         
    }
bag-man
  • 1,423
  • 2
  • 19
  • 23
0

The following code will return the distinct string array from 2 string arrays

public string[] UniqueNames(string[] names1, string[] names2) {
    newName = names1.ToList();
    foreach (var dr in names2) {
        if (!newName.Contains(dr)) {
            newName.Add(dr);
        }
    }
    string[] newNameArray = newName.ToArray();
    return newNameArray;
}

or you can also try,

public string[] UniqueNames(string[] names1, string[] names2) {            
    return names1.Union(names2).ToArray(); ;            
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
Javid Mir
  • 11
  • 2