Найти количество символов, взаимных между двумя строками в С#

Я ищу метод, который будет принимать две строки и возвращать количество символов, общих для обоих, например:

«G010» и «G1820A» должны возвращать 3, поскольку символы G, 0 и 1 существуют в обоих.

Если char встречается дважды в обоих, они должны учитываться отдельно следующим образом:

«G12AA» и «GAA2» должны возвращать 4, так как символы G, A, A и 2 существуют в обоих.

Любая помощь с этим? Поиски в Google до сих пор не были слишком полезными.


person Tommy    schedule 13.02.2014    source источник
comment
Это может быть классика, но пробовали ли вы что-нибудь?   -  person Soner Gönül    schedule 13.02.2014
comment
У меня есть метод, который сравнивает две строки на предмет сходства, но с символами в одних и тех же индексах, однако это совершенно другое.   -  person Tommy    schedule 13.02.2014
comment
G12AA и GA2 должны возвращать 4? а GA2 и G12AA должны возвращать 4?   -  person Francesco Milani    schedule 13.02.2014


Ответы (8)


Хорошо, как насчет этого, у него есть преимущество в том, что он максимизирует ленивые вычисления и минимизирует манипуляции со строками.

public int CommonChars(string left, string right)
{
    return left.GroupBy(c => c)
        .Join(
            right.GroupBy(c => c),
            g => g.Key,
            g => g.Key,
            (lg, rg) => lg.Zip(rg, (l, r) => l).Count())
        .Sum(); 
}

по сути, он группирует каждую сторону по char, а затем находит символы, у которых есть группа с обеих сторон. Совпавшие группы учитываются последовательно, пока не закончится одна из них. Эти подсчеты суммируются для получения результата.


Было бы тривиально выполнить это в общем случае для любых двух последовательностей. Смотри ниже,

public static int CommomCount<T>(
        this IEnumerable<T> source,
        IEnumerable<T> sequence,
        IEqualityComparer<T> comparer = null)
{
    if (sequence == null)
    {
        return 0;
    }

    if (comparer == null)
    {
        comparer = EqualityComparer<T>.Default;
    }

    return source.GroupBy(t => t, comparer)
        .Join(
            sequence.GroupBy(t => t, comparer),
            g => g.Key,
            g => g.Key,
            (lg, rg) => lg.Zip(rg, (l, r) => l).Count(),
            comparer)
        .Sum();
}

Который вы бы использовали так.

"G12AA".CommonCount("GAA2")

Необязательный параметр comparer может оказаться полезным, если вам требуется нечувствительность к регистру или другая специальная обработка.


В интересах повторного использования у меня возникнет соблазн удалить Sum() и вернуть IEnumerable<T>, а затем добавить сумму к вызову, например так:

public static IEnumerable<T> Commom<T>(
        this IEnumerable<T> source,
        IEnumerable<T> sequence,
        IEqualityComparer<T> comparer = null)
{
    if (sequence == null)
    {
        return Enumerable.Empty<T>();
    }

    if (comparer == null)
    {
        comparer = EqualityComparer<T>.Default;
    }

    return source.GroupBy(t => t, comparer)
        .Join(
            sequence.GroupBy(t => t, comparer),
            g => g.Key,
            g => g.Key,
            (lg, rg) => lg.Zip(rg, (l, r) => l),
            comparer)
        .SelectMany(g => g);
}

так что вы могли бы легко сделать

Console.WriteLine(new string("G12AA".Common("GAA2").ToArray()));

или только оригинал

"G12AA".Common("GAA2").Count();
person Jodrell    schedule 13.02.2014

Попробуй это

    public int CommonCharacters(string s1, string s2)
    {
        bool[] matchedFlag = new bool[s2.Length];

        for (int i1 = 0; i1 < s1.Length; i1++)
        {
            for (int i2 = 0; i2 < s2.Length; i2++)
            {
                if (!matchedFlag[i2] && s1.ToCharArray()[i1] == s2.ToCharArray()[i2])
                {
                    matchedFlag[i2] = true;
                    break;
                }
            }
        }

        return matchedFlag.Count(u => u);
    }
person Matt Webber    schedule 13.02.2014
comment
измените порядок операторов в вашем условии, чтобы повысить эффективность: нет смысла выполнять всю эту дополнительную работу по замене строк на массивы, когда флаг означает, что он все равно не будет использоваться! - person Nathan; 13.02.2014
comment
Я имел в виду изменить порядок условий: if (s1[i1] == s2[i2] && !matchedFlag[i2]): эффективнее сначала проверить логическое значение: изменить на if (!matchedFlag[i2] && s1[i1 ] == s2[i2]) - см. короткое замыкание :) (сомнительно, насколько это будет иметь значение теперь, когда вы правильно используете индексатор строк при первом условии - должен быть очень большой набор данных - но я всегда стараюсь помещать простейшее условие в начало оператора if, в котором используется &&:, может иметь существенное значение, особенно внутри нескольких циклов) - person Nathan; 13.02.2014
comment
Ага, мой плохой, такое чувство, что я не полностью встал с постели этим утром. - person Matt Webber; 13.02.2014
comment
хе-хе, не беспокойтесь - теперь потеряйте вызовы tochararray - они тоже лишние;) - person Nathan; 13.02.2014
comment
Если вы говорите о G12A и GAA2, он возвращает 3. G, A и 2. - person Matt Webber; 13.02.2014
comment
хорошо, извините - должно быть, я перепутал входные данные в моем тестовом коде - думаю, я тоже сплю :) - person Nathan; 13.02.2014
comment
Еще лучше - не перепутал ввод, перепутал вывод - ошибка копирования и вставки, лол, строка a = G12A; строка b = GAA2; результат var = a.CommonCount(b); Console.WriteLine(мое: + результат); var result2 = StringExtensions.CommonCharacters(a, b); Console.WriteLine(другое: + результат); - person Nathan; 13.02.2014

Вы можете использовать Linq для решения этой проблемы, используя что-то вроде этого:

static void Main(string[] args)
{
    IEnumerable<char> a = "G010".ToCharArray();
    IEnumerable<char> b = "G1820A".ToCharArray();

    int commonChars = FindCommonElements(a, b).Count();
    Console.WriteLine(commonChars);

    Console.ReadLine();
}

private static T[] FindCommonElements<T>(IEnumerable<T> source, IEnumerable<T> target)
{
    ILookup<T, T> lookup2 = target.ToLookup(i => i);

    return (
      from group1 in source.GroupBy(i => i)
      let group2 = lookup2[group1.Key]
      from i in (group1.Count() < group2.Count() ? group1 : group2)
      select i
    ).ToArray();
}

commonChars будет иметь значение 3. Метод FindCommonElements был вдохновлен этим вопросом: Как сделать пересечение целочисленного списка, сохраняя при этом дубликаты?

person Vincent    schedule 13.02.2014
comment
Вы можете использовать Zip, как в моем ответе, чтобы избежать полного подсчета обеих групп. stackoverflow.com/a/21751976/659190 - person Jodrell; 14.02.2014

Выполнение этого с помощью Linq:

    int MyCount(string s1, string s2)
    {
        return s1.Count(c =>
                            {
                                var i = s2.IndexOf(c);
                                if (i >= 0)
                                {
                                    s2 = s2.Remove(i, 1);
                                    return true;
                                }
                                return false;
                            });
    }
person nima    schedule 13.02.2014

Этот будет работать быстрее с большими входными данными, поскольку он не выполняет вложенные циклы, а скорее зависит от хешированного поиска с использованием словаря. С другой стороны, он использует больше памяти.

 public int CommonCharacterCount(string s1, string s2)
            { 
                var r=0;
                Dictionary<char,int> s2Dict = new Dictionary<char,int>();
                foreach (var ch in s2)
                {
                    if (s2Dict.ContainsKey(ch))
                        s2Dict[ch] = s2Dict[ch]+1;
                    else s2Dict.Add(ch,1);
                }

                foreach (var c in s1)
                {
                    if (s2Dict.ContainsKey(c) && s2Dict[c]>0)
                    {
                        r++;
                        s2Dict[c] = s2Dict[c] - 1;
                    }
                }
                return r;
            }
person cellik    schedule 13.02.2014

string myname = "1234";
        string yourname = "12";
        char[] sam = new char[] { };
        sam = myname.ToCharArray();
        char[] sam1 = new char[] { };
        sam1 = yourname.ToCharArray();
        int id = 0;
        int id1 = 0;
        List<string> found = new List<string>();
        List<string> found1 = new List<string>();
        foreach (char item in sam)
        {
            if (found.Contains(item.ToString()))
            {
                found.Add(item.ToString() + id);
                id++;
            }
            else
                found.Add(item.ToString());
        }
        foreach (var item in sam1)
        {
            if (found1.Contains(item.ToString()))
            {
                found1.Add(item.ToString() + id);
                id1++;
            }
            else
                found1.Add(item.ToString());
        }
        var final = found.Except(found1);
        var final2 = found1.Except(found);
        var checkingCount = final.Count() + final2.Count();
        Console.Write(checkingCount);
        Console.ReadLine();

проверьте это, кстати, не эффективно. Но правильно понял.

person Riyas    schedule 22.11.2017

Пожалуйста, проверьте следующий код --> src - это первая строка, а chk - вторая строка.

var count = 0;var i=0; src.ToList().ForEach((x)=> {
while(chk.Substring(i).IndexOf(x) >= 0) {
count++; i++; if( i > chk.Length) перерыв;
});

person rt2800    schedule 13.02.2014
comment
А как насчет AA и A? - person L.B; 13.02.2014
comment
Все еще не знаете, как отформатировать ответ после 194 ответов? - person L.B; 13.02.2014
comment
целое число i установлено равным 0 (ноль) - person rt2800; 13.02.2014

person    schedule
comment
Что вы получаете с string s1 = "G12A";? - person L.B; 13.02.2014
comment
Как 4 неверно, когда s2 = GAA2, все 4 символа присутствуют в s1? - person Amit; 13.02.2014
comment
@Amit, но у них обоих есть одна общая буква «А», а не две. См. первый пример Томми - person L.B; 13.02.2014
comment
Спасибо @L.B за рассказ о повторяющихся персонажах. - person Amit; 13.02.2014