c# - Obtener el recuento de caracteres únicos entre la primera y la última letra

CorePress2024-01-24  12

Estoy intentando obtener el recuento de caracteres únicos que se encuentran entre la primera y la última letra de una palabra. Por ejemplo: si escribo Amarillo, el resultado esperado es Y3w, si escribo Personas, el resultado debería ser P4e y si escribo Dinero, el resultado debería ser M3y. Esto es lo que probé:

//var strArr = wordToConvert.Split(' ');
string[] strArr = new[] { "Money","Yellow", "People" };
List<string> newsentence = new List<string>();

foreach (string word in strArr)
{
    if (word.Length > 2)
    {
        //ignore 2-letter words
        string newword = null;

        int distinctCount = 0;
        int k = word.Length;
        int samecharcount = 0;
        int count = 0;

        for (int i = 1; i < k - 2; i++)
        {
            if (word.ElementAt(i) != word.ElementAt(i + 1))
            {
                count++;
            }
            else
            {
                samecharcount++;
            }
        }
        distinctCount = count + samecharcount;

        char frst = word[0];
        char last = word[word.Length - 1];
        newword = String.Concat(frst, distinctCount.ToString(), last);
        newsentence.Add(newword);
    }
    else
    {
        newsentence.Add(word);
    }
}

var result = String.Join(" ", newsentence.ToArray());

Console.WriteLine("Output: " + result);
Console.WriteLine("

------------------------------------

----------------");

Con este código obtengo el resultado esperado para Amarillo, pero parece que no funciona con Personas y Dinero. ¿Qué puedo hacer para solucionar este problema? También me pregunto si tal vez haya una mejor manera de hacerlo, por ejemplo, usando LINQ/Regex.

¿Por qué Yel¿Bajo no rinde Y4w?

-Maxqueue

26/03/2021 a las 20:28

@Maxqueue Debido a que el programa debe contar solo caracteres únicos entre el primer y el último carácter, en este caso *ello, contiene doble L, por lo que cuenta solo una vez.

Usuario1899289003

26/03/2021 a las 20:30

Linq simplemente puedes hacer una línea 1 como var result = word.First().ToString() + word.Substring(1, palabra.Longitud - 2).ToLower().Distinct().Count().ToString() + palabra.Last().ToString();

- Franck

26/03/2021 a las 20:31

puedes incrementar el conteo o el mismo conteo de caracteres, para finalmente sumarlo en un conteo distinto. Sin embargo, por la forma en que lo implementes, siempre obtendrás un Count distinto igual a k-2. Probablemente quieras resumir solo uno de los que supongo

-Mirronelli

26/03/2021 a las 20:32



------------------------------------

Aquí hay una implementación que usa Linq:

string[] strArr = new[]{"Money", "Yellow", "People"};
List<string> newsentence = new List<string>();
foreach (string word in strArr)
{
    if (word.Length > 2)
    {
        // we want the first letter, the last letter, and the distinct count of everything in between
        var first = word.First();
        var last = word.Last();
        var others = word.Skip(1).Take(word.Length - 2);

        // Case sensitive
        var distinct = others.Distinct();

        // Case insensitive
        // var distinct = others.Select(c => char.ToLowerInvariant(c)).Distinct();

        string newword = first + distinct.Count().ToString() + last;
        newsentence.Add(newword);
    }
    else
    {
        newsentence.Add(word);
    }
}

var result = String.Join(" ", newsentence.ToArray());
Console.WriteLine(result);

Salida:

M3y Y3w P4e

Tenga en cuenta que esto no tiene en cuenta las mayúsculas y minúsculas, por lo que el resultado para FiIsSh es 4.

1

¡Esto funciona de maravilla! Sólo por haberlo completado todo, ¿cómo puedo convertir las otras var a minúsculas para evitar la salida 4 para YeLlow?

Usuario1899289003

26 de marzo de 2021 a las 20:52



------------------------------------

Quizás no sea el de mayor rendimiento, pero aquí hay otro ejemplo usando linq:

var words = new[] { "Money","Yellow", "People" };
var transformedWords = words.Select(Transform);
var sentence = String.Join(' ', transformedWords);

public string Transform(string input)
{
    if (input.Length < 3)
    {
        return input;
    }
    var count = input.Skip(1).SkipLast(1).Distinct().Count();
    return $"{input[0]}{count}{input[^1]}";
}

3

Me olvidé de SkipLast :)

- Stuartd

26/03/2021 a las 20:39

@stuartd Sí, no lo uso mucho. ICreo que es una de las últimas incorporaciones.

Mente plateada

26/03/2021 a las 20:40

1

Tienes razón, es nuevo en .Net Core. Lo usé el otro día por primera vez.

- Stuartd

26/03/2021 a las 20:40



------------------------------------

Puedes implementarlo con la ayuda de Linq. p.ej. (C#8+)

private static string EncodeWord(string value) => value.Length <= 2
  ? value
  : $"{value[0]}{value.Substring(1, value.Length - 2).Distinct().Count()}{value[^1]}";

Demostración:

  string[] tests = new string[] {
    "Money","Yellow", "People"
  };

  var report = string.Join(Environment.NewLine, tests
    .Select(test => $"{test} :: {EncodeWord(test)}"));

  Console.Write(report);

Resultado:

Money :: M3y
Yellow :: Y3w
People :: P4e

1

Buen uso del operador de rango, ^1. Sin embargo, vale la pena mencionar que requiere C#8.

- Stuartd

26/03/2021 a las 20:50



------------------------------------

Mucha gente ha propuesto buenas soluciones. Tengo dos soluciones para ti: una usa LINQ y la otra no.

LINQ, probablemente no muy diferente de los demás

if (str.Length < 3) return str;

var midStr = str.Substring(1, str.Length - 2);
var midCount = midStr.Distinct().Count();
return string.Concat(str[0], midCount, str[str.Length - 1]);

Sin LINQ

if (str.Length < 3) return str;
    
var uniqueLetters = new Dictionary<char, int>();
var midStr = str.Substring(1, str.Length - 2);
foreach (var c in midStr)
{
   if (!uniqueLetters.ContainsKey(c))
   {
      uniqueLetters.Add(c, 0);
   }
}
        
var midCount = uniqueLetters.Keys.Count();
return string.Concat(str[0], midCount, str[str.Length - 1]);

Probé esto con las siguientes 6 cadenas:

Amarillo Dinero Púrpura A mí Tú Holaiiiiii

Salida:

LINQ: Y3w, Non-LINQ: Y3w  
LINQ: M3y, Non-LINQ: M3y  
LINQ: P4e, Non-LINQ: P4e  
LINQ: Me, Non-LINQ: Me  
LINQ: Y1u, Non-LINQ: Y1u  
LINQ: H1i, Non-LINQ: H1i

violín

En cuanto al rendimiento, supongo que son más o menos iguales, si no idénticos, pero no he realizado ninguna prueba de rendimiento real en los dos enfoques. No puedo imaginar que serían muy diferentes, en todo caso. La única diferencia real es que la segunda ruta expande Distinct() a lo que probablemente hace bajo las sábanas de todos modos (no he mirado la fuente para ver si eso es cierto, pero esa es una forma bastante común de obtener un recuento de . Y la primera ruta es ciertamente menos código.

8

1

También puedes usar un hashet. Incluso con una variable de contador que se incrementa cuando if (hashset.Add(c)). Además, deberías usar Count en lugar de Count(). ;)

Mente plateada

26/03/2021 a las 22:00

@Silvermind Así que busqué ambos, sólo para estar seguro. Según esto, la diferencia entre hashset.Add() y Dictionary.Add() es bastante mínima. (Aunque ese artículo tiene ahora 10 años, así que tómelo por lo que vale). En cuanto a Count vs. Count(), en secreto, este último llama al primero si esa propiedad existe; Antiguo TestamentoDe esta manera enumera a través de la lista. Entonces no debería hacer ninguna diferencia. :) (¡Aunque la propiedad probablemente sea preferible solo para evitar problemas de rendimiento no deseados relacionados con el casting!)

- Ari Roth

29/03/2021 a las 18:56

sobre el diccionario; Sé que es bastante mínimo, pero no es por eso que lo sugerí. Dado que no necesita el valor, ¿por qué debería crear la ambigüedad de que un diccionario parece necesario? Si no es necesario y no añade valor, crea queambigüedad.

Mente plateada

29/03/2021 a las 20:53

1

Así que ahora estoy asombrado, gracias, ;). Voy a sumergirme en eso. ¿Está de acuerdo con mi punto de lanzar una excepción cuando se accede a la propiedad de conteo? Puede parecer una tontería, pero cuando paso mi colección oculta a un método público que acepta IEnumerable, no espero que se rompa. Por cierto, estoy en mi móvil y tengo problemas para saber si esta es la fuente de dotnet core o framework. Puede que ellos hagan lo mismo, pero en realidad estoy atónito.

Mente plateada

29/03/2021 a las 21:19

1

¡En cualquier momento! Y estoy aproximadamente un 90% seguro de que es .NET Core, aunque creo que es poco probable que el código para ese método en particular difiera entre los dos. Es un código bastante genérico. :) Sin embargo, también tengo problemas para verificar eso. En cuanto a la excepción, puedes hacer eso. No se me ocurre ningún escenario en el que quisieras hacerlo de improviso, pero supongo que no hay nada que te detenga. Creo que al final tienes razón: Count() es bueno, pero es a lo que me referí antes: usar Count significa que sabes exactamente quéLa colección lo es y, en última instancia, probablemente sea más segura.

- Ari Roth

29/03/2021 a las 21:58



------------------------------------

Usaría Linq para ese propósito:

            string[] words = new string[] { "Yellow" , "People", "Money", "Sh" }; // Sh for 2 letter words (or u can insert 0 and then remove the trinary operator)
            foreach (string word in words)
            {
                int uniqeCharsInBetween = word.Substring(1, word.Length - 2).ToCharArray().Distinct().Count();
                string result = word[0] + (uniqeCharsInBetween == 0 ? string.Empty : uniqeCharsInBetween.ToString()) + word[word.Length - 1];
                Console.WriteLine(result);
            }

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare