before - string methods c#
Dividir una cuerda en trozos de un cierto tamaño (28)
Supongamos que tengo una cuerda:
string str = "1111222233334444";
¿Cómo puedo dividir esta cadena en trozos de algún tamaño?
por ejemplo, dividir esto en tamaños de 4 devolvería cadenas:
"1111"
"2222"
"3333"
"4444"
¿Cómo es esto para un one-liner?
List<string> result = new List<string>(Regex.Split(target, @"(?<=/G.{4})", RegexOptions.Singleline));
Con esta expresión regular no importa si el último fragmento tiene menos de cuatro caracteres, porque solo mira a los personajes que están detrás de él. Estoy seguro de que esta no es la solución más eficiente, pero solo tuve que tirarla. :RE
¿Por qué no bucles? Aquí hay algo que lo haría bastante bien:
string str = "111122223333444455";
int chunkSize = 4;
int stringLength = str.Length;
for (int i = 0; i < stringLength ; i += chunkSize)
{
if (i + chunkSize > stringLength) chunkSize = stringLength - i;
Console.WriteLine(str.Substring(i, chunkSize));
}
Console.ReadLine();
No sé cómo lidiarías con el caso donde la cadena no es un factor de 4, pero no diciendo que tu idea no es posible, solo preguntando la motivación para ello si un ciclo simple lo hace muy bien. Obviamente, lo anterior podría limpiarse e incluso incluirse como un método de extensión.
O como se menciona en los comentarios, sabes que es / 4 entonces
str = "1111222233334444";
for (int i = 0; i < stringLength; i += chunkSize)
{Console.WriteLine(str.Substring(i, chunkSize));}
Basado en otras respuestas de carteles, junto con algunas muestras de uso:
public static string FormatSortCode(string sortCode)
{
return ChunkString(sortCode, 2, "-");
}
public static string FormatIBAN(string iban)
{
return ChunkString(iban, 4, " ");
}
private static string ChunkString(string str, int chunkSize, string separator)
{
var b = new StringBuilder();
var stringLength = str.Length;
for (var i = 0; i < stringLength; i += chunkSize)
{
if (i + chunkSize > stringLength) chunkSize = stringLength - i;
b.Append(str.Substring(i, chunkSize));
if (i+chunkSize != stringLength)
b.Append(separator);
}
return b.ToString();
}
En una combinación de las respuestas de Dove + Konstatin ...
static IEnumerable<string> WholeChunks(string str, int chunkSize) {
for (int i = 0; i < str.Length; i += chunkSize)
yield return str.Substring(i, chunkSize);
}
Esto funcionará para todas las cadenas que se pueden dividir en un número entero de fragmentos, y arrojará una excepción de lo contrario.
Si desea admitir cadenas de cualquier longitud, puede usar el siguiente código:
static IEnumerable<string> ChunksUpto(string str, int maxChunkSize) {
for (int i = 0; i < str.Length; i += maxChunkSize)
yield return str.Substring(i, Math.Min(maxChunkSize, str.Length-i));
}
Sin embargo, el OP declaró explícitamente que no necesita esto; es algo más largo y más difícil de leer, un poco más lento. Siguiendo el espíritu de KISS y YAGNI, elegiría la primera opción: probablemente sea la implementación más eficiente posible, y es muy corta, legible y, sobre todo, arroja una excepción para los datos no conformes.
Esto debería ser mucho más rápido y más eficiente que usar LINQ u otros enfoques utilizados aquí.
public static IEnumerable<string> Splice(this string s, int spliceLength)
{
if (s == null)
throw new ArgumentNullException("s");
if (spliceLength < 1)
throw new ArgumentOutOfRangeException("spliceLength");
if (s.Length == 0)
yield break;
var start = 0;
for (var end = spliceLength; end < s.Length; end += spliceLength)
{
yield return s.Substring(start, spliceLength);
start = end;
}
yield return s.Substring(start);
}
Esto está basado en la solución @dove pero implementado como un método de extensión.
Beneficios:
- Método de extensión
- Cubiertas de casos de esquina
- Divide la cadena con cualquier carácter: números, letras, otros símbolos
Código
public static class EnumerableEx
{
public static IEnumerable<string> SplitBy(this string str, int chunkLength)
{
if (String.IsNullOrEmpty(str)) throw new ArgumentException();
if (chunkLength < 1) throw new ArgumentException();
for (int i = 0; i < str.Length; i += chunkLength)
{
if (chunkLength + i > str.Length)
chunkLength = str.Length - i;
yield return str.Substring(i, chunkLength);
}
}
}
Uso
var result = "bobjoecat".SplitBy(3); // bob, joe, cat
Pruebas unitarias eliminadas por brevedad (ver revisión anterior )
Esto se puede hacer de esta manera también
string actualString = "1111222233334444";
var listResult = new List<string>();
int groupingLength = actualString.Length % 4;
if (groupingLength > 0)
listResult.Add(actualString.Substring(0, groupingLength));
for (int i = groupingLength; i < actualString.Length; i += 4)
{
listResult.Add(actualString.Substring(i, 4));
}
foreach(var res in listResult)
{
Console.WriteLine(res);
}
Console.Read();
La mejor, más fácil y genérica respuesta :).
string originalString = "1111222233334444";
List<string> test = new List<string>();
int chunkSize = 4; // change 4 with the size of strings you want.
for (int i = 0; i < originalString.Length; i = i + chunkSize)
{
if (originalString.Length - i >= chunkSize)
test.Add(originalString.Substring(i, chunkSize));
else
test.Add(originalString.Substring(i,((originalString.Length - i))));
}
Me he acumulado un poco en la solución de João. Lo que he hecho diferente es que con mi método puedes especificar si quieres devolver la matriz con los caracteres restantes o si quieres truncarlos si los caracteres finales no coinciden con la longitud de fragmento requerida, creo que es bastante flexible y el el código es bastante sencillo:
using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace SplitFunction
{
class Program
{
static void Main(string[] args)
{
string text = "hello, how are you doing today?";
string[] chunks = SplitIntoChunks(text, 3,false);
if (chunks != null)
{
chunks.ToList().ForEach(e => Console.WriteLine(e));
}
Console.ReadKey();
}
private static string[] SplitIntoChunks(string text, int chunkSize, bool truncateRemaining)
{
string chunk = chunkSize.ToString();
string pattern = truncateRemaining ? ".{" + chunk + "}" : ".{1," + chunk + "}";
string[] chunks = null;
if (chunkSize > 0 && !String.IsNullOrEmpty(text))
chunks = (from Match m in Regex.Matches(text,pattern)select m.Value).ToArray();
return chunks;
}
}
}
Modificado (ahora acepta cualquier string
no nula y cualquier chunkSize
positivo) chunkSize
Konstantin Spirin :
public static IEnumerable<String> Split(String value, int chunkSize) {
if (null == value)
throw new ArgumentNullException("value");
else if (chunkSize <= 0)
throw new ArgumentOutOfRangeException("chunkSize", "Chunk size should be positive");
return Enumerable
.Range(0, value.Length / chunkSize + ((value.Length % chunkSize) == 0 ? 0 : 1))
.Select(index => (index + 1) * chunkSize < value.Length
? value.Substring(index * chunkSize, chunkSize)
: value.Substring(index * chunkSize));
}
Pruebas:
String source = @"ABCDEF";
// "ABCD,EF"
String test1 = String.Join(",", Split(source, 4));
// "AB,CD,EF"
String test2 = String.Join(",", Split(source, 2));
// "ABCDEF"
String test3 = String.Join(",", Split(source, 123));
No es bonito y no es rápido, pero funciona, es de una sola línea y es LINQy:
List<string> a = text.Select((c, i) => new { Char = c, Index = i }).GroupBy(o => o.Index / 4).Select(g => new String(g.Select(o => o.Char).ToArray())).ToList();
No recuerdo quién me dio esto, pero funciona muy bien. La velocidad probó varias formas de dividir los tipos de Enumerable en grupos. El uso sería así ...
List<string> Divided = Source3.Chunk(24).Select(Piece => string.Concat<char>(Piece)).ToList();
El código de extensión se vería así ...
#region Chunk Logic
private class ChunkedEnumerable<T> : IEnumerable<T>
{
class ChildEnumerator : IEnumerator<T>
{
ChunkedEnumerable<T> parent;
int position;
bool done = false;
T current;
public ChildEnumerator(ChunkedEnumerable<T> parent)
{
this.parent = parent;
position = -1;
parent.wrapper.AddRef();
}
public T Current
{
get
{
if (position == -1 || done)
{
throw new InvalidOperationException();
}
return current;
}
}
public void Dispose()
{
if (!done)
{
done = true;
parent.wrapper.RemoveRef();
}
}
object System.Collections.IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
position++;
if (position + 1 > parent.chunkSize)
{
done = true;
}
if (!done)
{
done = !parent.wrapper.Get(position + parent.start, out current);
}
return !done;
}
public void Reset()
{
// per http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.reset.aspx
throw new NotSupportedException();
}
}
EnumeratorWrapper<T> wrapper;
int chunkSize;
int start;
public ChunkedEnumerable(EnumeratorWrapper<T> wrapper, int chunkSize, int start)
{
this.wrapper = wrapper;
this.chunkSize = chunkSize;
this.start = start;
}
public IEnumerator<T> GetEnumerator()
{
return new ChildEnumerator(this);
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private class EnumeratorWrapper<T>
{
public EnumeratorWrapper(IEnumerable<T> source)
{
SourceEumerable = source;
}
IEnumerable<T> SourceEumerable { get; set; }
Enumeration currentEnumeration;
class Enumeration
{
public IEnumerator<T> Source { get; set; }
public int Position { get; set; }
public bool AtEnd { get; set; }
}
public bool Get(int pos, out T item)
{
if (currentEnumeration != null && currentEnumeration.Position > pos)
{
currentEnumeration.Source.Dispose();
currentEnumeration = null;
}
if (currentEnumeration == null)
{
currentEnumeration = new Enumeration { Position = -1, Source = SourceEumerable.GetEnumerator(), AtEnd = false };
}
item = default(T);
if (currentEnumeration.AtEnd)
{
return false;
}
while (currentEnumeration.Position < pos)
{
currentEnumeration.AtEnd = !currentEnumeration.Source.MoveNext();
currentEnumeration.Position++;
if (currentEnumeration.AtEnd)
{
return false;
}
}
item = currentEnumeration.Source.Current;
return true;
}
int refs = 0;
// needed for dispose semantics
public void AddRef()
{
refs++;
}
public void RemoveRef()
{
refs--;
if (refs == 0 && currentEnumeration != null)
{
var copy = currentEnumeration;
currentEnumeration = null;
copy.Source.Dispose();
}
}
}
/// <summary>Speed Checked. Works Great!</summary>
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
if (chunksize < 1) throw new InvalidOperationException();
var wrapper = new EnumeratorWrapper<T>(source);
int currentPos = 0;
T ignore;
try
{
wrapper.AddRef();
while (wrapper.Get(currentPos, out ignore))
{
yield return new ChunkedEnumerable<T>(wrapper, chunksize, currentPos);
currentPos += chunksize;
}
}
finally
{
wrapper.RemoveRef();
}
}
#endregion
Personalmente prefiero mi solución :-)
Lo maneja:
- Longitudes de cadena que son un múltiplo del tamaño del fragmento.
- Longitudes de cadena que NO son un múltiplo del tamaño del fragmento.
- Longitudes de cadena que son más pequeñas que el tamaño del fragmento.
- NULL y cadenas vacías (arroja una excepción).
- Tamaños de trozos más pequeños que 1 (arroja una excepción).
Se implementa como un método de extensión y calcula el número de fragmentos que se generarán de antemano. Comprueba el último fragmento porque, en caso de que la longitud del texto no sea múltiple, debe ser más corto. Limpio, corto, fácil de entender ... ¡y funciona!
public static string[] Split(this string value, int chunkSize)
{
if (string.IsNullOrEmpty(value)) throw new ArgumentException("The string cannot be null.");
if (chunkSize < 1) throw new ArgumentException("The chunk size should be equal or greater than one.");
int remainder;
int divResult = Math.DivRem(value.Length, chunkSize, out remainder);
int numberOfChunks = remainder > 0 ? divResult + 1 : divResult;
var result = new string[numberOfChunks];
int i = 0;
while (i < numberOfChunks - 1)
{
result[i] = value.Substring(i * chunkSize, chunkSize);
i++;
}
int lastChunkSize = remainder > 0 ? remainder : chunkSize;
result[i] = value.Substring(i * chunkSize, lastChunkSize);
return result;
}
Puedes usar morelinq por Jon Skeet. Use Batch como:
string str = "1111222233334444";
int chunkSize = 4;
var chunks = str.Batch(chunkSize).Select(r => new String(r.ToArray()));
Esto devolverá 4 fragmentos para la cadena "1111222233334444"
. Si la longitud de la cadena es menor o igual que el tamaño del fragmento, Batch
devolverá la cadena como el único elemento de IEnumerable<string>
Para salida:
foreach (var chunk in chunks)
{
Console.WriteLine(chunk);
}
y dará:
1111
2222
3333
4444
Recientemente tuve que escribir algo que logra esto en el trabajo, así que pensé en publicar mi solución a este problema. Como una ventaja adicional, la funcionalidad de esta solución proporciona una forma de dividir la cadena en la dirección opuesta y maneja correctamente los caracteres unicode como mencionó anteriormente Marvin Pinto. Asi que aqui esta:
using System;
using Extensions;
namespace TestCSharp
{
class Program
{
static void Main(string[] args)
{
string asciiStr = "This is a string.";
string unicodeStr = "これは文字列です。";
string[] array1 = asciiStr.Split(4);
string[] array2 = asciiStr.Split(-4);
string[] array3 = asciiStr.Split(7);
string[] array4 = asciiStr.Split(-7);
string[] array5 = unicodeStr.Split(5);
string[] array6 = unicodeStr.Split(-5);
}
}
}
namespace Extensions
{
public static class StringExtensions
{
/// <summary>Returns a string array that contains the substrings in this string that are seperated a given fixed length.</summary>
/// <param name="s">This string object.</param>
/// <param name="length">Size of each substring.
/// <para>CASE: length > 0 , RESULT: String is split from left to right.</para>
/// <para>CASE: length == 0 , RESULT: String is returned as the only entry in the array.</para>
/// <para>CASE: length < 0 , RESULT: String is split from right to left.</para>
/// </param>
/// <returns>String array that has been split into substrings of equal length.</returns>
/// <example>
/// <code>
/// string s = "1234567890";
/// string[] a = s.Split(4); // a == { "1234", "5678", "90" }
/// </code>
/// </example>
public static string[] Split(this string s, int length)
{
System.Globalization.StringInfo str = new System.Globalization.StringInfo(s);
int lengthAbs = Math.Abs(length);
if (str == null || str.LengthInTextElements == 0 || lengthAbs == 0 || str.LengthInTextElements <= lengthAbs)
return new string[] { str.ToString() };
string[] array = new string[(str.LengthInTextElements % lengthAbs == 0 ? str.LengthInTextElements / lengthAbs: (str.LengthInTextElements / lengthAbs) + 1)];
if (length > 0)
for (int iStr = 0, iArray = 0; iStr < str.LengthInTextElements && iArray < array.Length; iStr += lengthAbs, iArray++)
array[iArray] = str.SubstringByTextElements(iStr, (str.LengthInTextElements - iStr < lengthAbs ? str.LengthInTextElements - iStr : lengthAbs));
else // if (length < 0)
for (int iStr = str.LengthInTextElements - 1, iArray = array.Length - 1; iStr >= 0 && iArray >= 0; iStr -= lengthAbs, iArray--)
array[iArray] = str.SubstringByTextElements((iStr - lengthAbs < 0 ? 0 : iStr - lengthAbs + 1), (iStr - lengthAbs < 0 ? iStr + 1 : lengthAbs));
return array;
}
}
}
Además, aquí hay un enlace de imagen a los resultados de ejecutar este código: http://i.imgur.com/16Iih.png
Se modificó ligeramente para devolver partes cuyo tamaño no es igual a chunkSize
public static IEnumerable<string> Split(this string str, int chunkSize)
{
var splits = new List<string>();
if (str.Length < chunkSize) { chunkSize = str.Length; }
splits.AddRange(Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize)));
splits.Add(str.Length % chunkSize > 0 ? str.Substring((str.Length / chunkSize) * chunkSize, str.Length - ((str.Length / chunkSize) * chunkSize)) : string.Empty);
return (IEnumerable<string>)splits;
}
Un consejo importante si la cadena que se está fragmentando necesita ser compatible con todos los caracteres Unicode.
Si la cadena es para admitir caracteres internacionales como 𠀋
, entonces divida la cadena usando la clase System.Globalization.StringInfo. Usando StringInfo, puede dividir la cadena en función del número de elementos de texto.
string internationalString = ''𠀋'';
La cadena anterior tiene una longitud de 2, porque la propiedad String.Length
devuelve el número de objetos Char en esta instancia, no el número de caracteres Unicode.
Usando expresiones regulares y Linq :
List<string> groups = (from Match m in Regex.Matches(str, @"/d{4}")
select m.Value).ToList();
Encuentro esto más legible, pero es solo una opinión personal. También puede ser un trazador de líneas:).
Usando las extensiones de Buffer de la biblioteca IX
static IEnumerable<string> Split( this string str, int chunkSize )
{
return str.Buffer(chunkSize).Select(l => String.Concat(l));
}
Seis años después o_O
Simplemente porque
public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
{
var count = (int) Math.Ceiling(str.Length/(double) chunkSize);
Func<int, int> start = index => remainingInFront ? str.Length - (count - index)*chunkSize : index*chunkSize;
Func<int, int> end = index => Math.Min(str.Length - Math.Max(start(index), 0), Math.Min(start(index) + chunkSize - Math.Max(start(index), 0), chunkSize));
return Enumerable.Range(0, count).Select(i => str.Substring(Math.Max(start(i), 0),end(i)));
}
o
private static Func<bool, int, int, int, int, int> start = (remainingInFront, length, count, index, size) =>
remainingInFront ? length - (count - index) * size : index * size;
private static Func<bool, int, int, int, int, int, int> end = (remainingInFront, length, count, index, size, start) =>
Math.Min(length - Math.Max(start, 0), Math.Min(start + size - Math.Max(start, 0), size));
public static IEnumerable<string> Split(this string str, int chunkSize, bool remainingInFront)
{
var count = (int)Math.Ceiling(str.Length / (double)chunkSize);
return Enumerable.Range(0, count).Select(i => str.Substring(
Math.Max(start(remainingInFront, str.Length, count, i, chunkSize), 0),
end(remainingInFront, str.Length, count, i, chunkSize, start(remainingInFront, str.Length, count, i, chunkSize))
));
}
AFAIK se manejan todos los casos de borde.
Console.WriteLine(string.Join(" ", "abc".Split(2, false))); // ab c
Console.WriteLine(string.Join(" ", "abc".Split(2, true))); // a bc
Console.WriteLine(string.Join(" ", "a".Split(2, true))); // a
Console.WriteLine(string.Join(" ", "a".Split(2, false))); // a
Simple y corto:
// this means match a space or not a space (anything) up to 4 characters
var lines = Regex.Matches(str, @"[/s/S]{0,4}").Cast<Match>().Select(x => x.Value);
Si es necesario dividir por una longitud diferente: Por ejemplo, tiene la fecha y la hora en formato de especificación cadena strangeStr = "07092016090532";
07092016090532 (Fecha: 07.09.2016 Hora: 09:05:32)
public static IEnumerable<string> SplitBy(this string str, int[] chunkLength)
{
if (String.IsNullOrEmpty(str)) throw new ArgumentException();
int i = 0;
for (int j = 0; j < chunkLength.Length; j++)
{
if (chunkLength[j] < 1) throw new ArgumentException();
if (chunkLength[j] + i > str.Length)
{
chunkLength[j] = str.Length - i;
}
yield return str.Substring(i, chunkLength[j]);
i += chunkLength[j];
}
}
utilizando:
string[] dt = strangeStr.SplitBy(new int[] { 2, 2, 4, 2, 2, 2, 2 }).ToArray();
public static List<string> SplitByMaxLength(this string str)
{
List<string> splitString = new List<string>();
for (int index = 0; index < str.Length; index += MaxLength)
{
splitString.Add(str.Substring(index, Math.Min(MaxLength, str.Length - index)));
}
return splitString;
}
List<string> SplitString(int chunk, string input)
{
List<string> list = new List<string>();
int cycles = input.Length / chunk;
if (input.Length % chunk != 0)
cycles++;
for (int i = 0; i < cycles; i++)
{
try
{
list.Add(input.Substring(i * chunk, chunk));
}
catch
{
list.Add(input.Substring(i * chunk));
}
}
return list;
}
class StringHelper
{
static void Main(string[] args)
{
string str = "Hi my name is vikas bansal and my email id is [email protected]";
int offSet = 10;
List<string> chunks = chunkMyStr(str, offSet);
Console.Read();
}
static List<string> chunkMyStr(string str, int offSet)
{
List<string> resultChunks = new List<string>();
for (int i = 0; i < str.Length; i += offSet)
{
string temp = str.Substring(i, (str.Length - i) > offSet ? offSet : (str.Length - i));
Console.WriteLine(temp);
resultChunks.Add(temp);
}
return resultChunks;
}
}
public static IEnumerable<IEnumerable<T>> SplitEvery<T>(this IEnumerable<T> values, int n)
{
var ls = values.Take(n);
var rs = values.Skip(n);
return ls.Any() ?
Cons(ls, SplitEvery(rs, n)) :
Enumerable.Empty<IEnumerable<T>>();
}
public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> xs)
{
yield return x;
foreach (var xi in xs)
yield return xi;
}
static IEnumerable<string> Split(string str, double chunkSize)
{
return Enumerable.Range(0, (int) Math.Ceiling(str.Length/chunkSize))
.Select(i => new string(str
.Skip(i * (int)chunkSize)
.Take((int)chunkSize)
.ToArray()));
}
y otro enfoque:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var x = "Hello World";
foreach(var i in x.ChunkString(2)) Console.WriteLine(i);
}
}
public static class Ext{
public static IEnumerable<string> ChunkString(this string val, int chunkSize){
return val.Select((x,i) => new {Index = i, Value = x})
.GroupBy(x => x.Index/chunkSize, x => x.Value)
.Select(x => string.Join("",x));
}
}
static IEnumerable<string> Split(string str, int chunkSize)
{
return Enumerable.Range(0, str.Length / chunkSize)
.Select(i => str.Substring(i * chunkSize, chunkSize));
}
Tenga en cuenta que es posible que se requiera un código adicional para manejar casos de borde (cadena de entrada null
o vacía, chunkSize == 0
, longitud de cadena de entrada no divisible por chunkSize
de chunkSize
, etc.). La pregunta original no especifica ningún requisito para estos casos extremos y en la vida real los requisitos pueden variar por lo que están fuera del alcance de esta respuesta.