Birden çok beyaz alanı tek bir boşlukla değiştirme


108

Diyelim ki benim gibi bir dizem var:

"Hello     how are   you           doing?"

Birden fazla alanı tek bir alana dönüştüren bir işlev istiyorum.

Böylece alırdım:

"Hello how are you doing?"

Normal ifade kullanabileceğimi veya arayabileceğimi biliyorum

string s = "Hello     how are   you           doing?".replace("  "," ");

Ancak, tüm sıralı boşlukların yalnızca bir boşlukla değiştirildiğinden emin olmak için bunu birden çok kez aramam gerekirdi.

Bunun için zaten yerleşik bir yöntem var mı?


Açıklayabilir misiniz: Yalnızca boşluklarla mı yoksa "tümü" boşluklarla mı ilgileniyorsunuz?
Jon Skeet

Ve boşluk olmayan beyaz uzayın boşluklara dönüştürülmesini istiyor musunuz?
Jon Skeet

Serideki tüm boşlukların en fazla 1 olması gerektiğini kastetmiştim
Mat


Dikkate alınacak 2 şey: 1. char.IsWhiteSpace, satır başı, satır besleme vb. İçerir. 2. 'beyaz boşluk', Char.GetUnicodeCategory (ch) = Globalization.UnicodeCategory.SpaceSeparator
smirkingman

Yanıtlar:


196
string cleanedString = System.Text.RegularExpressions.Regex.Replace(dirtyString,@"\s+"," ");

40
imo, eğer rahat ediyorsanız, erken optimizasyon ise normal
ifadeden kaçınmak

8
Uygulamanız zaman açısından kritik değilse, 1 mikrosaniye işlem ek yükünü karşılayabilir.
Daniel

16
'\ S' yalnızca beyaz boşlukların değil, aynı zamanda yeni satır karakterlerinin de yerini aldığını unutmayın.
Bart Kiers

12
iyi yakalayış, sadece boşluk istiyorsanız, kalıbı "[] +" olarak değiştirin
Tim Hoolihan

9
Tek boşlukları değiştirmekten kaçınmak için "+" yerine "{2,}" kullanmanız gerekmez mi?
angularsen

52

Bu soru, diğer afişlerin ortaya koyduğu kadar basit değil (ve aslında olduğuna inandığım gibi) - çünkü soru olması gerektiği kadar kesin değil.

"Boşluk" ve "boşluk" arasında bir fark vardır. Eğer varsa sadece boşluk anlamına, o zaman bir düzenli ifade kullanmalıdır " {2,}". Herhangi bir boşluktan bahsediyorsanız, bu farklı bir konudur. Should tüm boşluk alanlara dönüştürülebilir? Başlangıçta ve sonda uzaya ne olmalı?

Aşağıdaki kıyaslama için, yalnızca boşlukları önemsediğinizi ve başlangıçta ve sonda bile tek boşluklara hiçbir şey yapmak istemediğinizi varsaydım.

Doğruluğun neredeyse her zaman performanstan daha önemli olduğunu unutmayın. Split / Join çözümünün baştaki / sondaki beyaz boşlukları (tek boşluklar bile) kaldırması gerçeği, belirtilen gereksinimlerinize göre yanlıştır (tabii ki eksik olabilir).

Kıyaslama MiniBench kullanıyor .

using System;
using System.Text.RegularExpressions;
using MiniBench;

internal class Program
{
    public static void Main(string[] args)
    {

        int size = int.Parse(args[0]);
        int gapBetweenExtraSpaces = int.Parse(args[1]);

        char[] chars = new char[size];
        for (int i=0; i < size/2; i += 2)
        {
            // Make sure there actually *is* something to do
            chars[i*2] = (i % gapBetweenExtraSpaces == 1) ? ' ' : 'x';
            chars[i*2 + 1] = ' ';
        }
        // Just to make sure we don't have a \0 at the end
        // for odd sizes
        chars[chars.Length-1] = 'y';

        string bigString = new string(chars);
        // Assume that one form works :)
        string normalized = NormalizeWithSplitAndJoin(bigString);


        var suite = new TestSuite<string, string>("Normalize")
            .Plus(NormalizeWithSplitAndJoin)
            .Plus(NormalizeWithRegex)
            .RunTests(bigString, normalized);

        suite.Display(ResultColumns.All, suite.FindBest());
    }

    private static readonly Regex MultipleSpaces = 
        new Regex(@" {2,}", RegexOptions.Compiled);

    static string NormalizeWithRegex(string input)
    {
        return MultipleSpaces.Replace(input, " ");
    }

    // Guessing as the post doesn't specify what to use
    private static readonly char[] Whitespace =
        new char[] { ' ' };

    static string NormalizeWithSplitAndJoin(string input)
    {
        string[] split = input.Split
            (Whitespace, StringSplitOptions.RemoveEmptyEntries);
        return string.Join(" ", split);
    }
}

Birkaç test çalışması:

c:\Users\Jon\Test>test 1000 50
============ Normalize ============
NormalizeWithSplitAndJoin  1159091 0:30.258 22.93
NormalizeWithRegex        26378882 0:30.025  1.00

c:\Users\Jon\Test>test 1000 5
============ Normalize ============
NormalizeWithSplitAndJoin  947540 0:30.013 1.07
NormalizeWithRegex        1003862 0:29.610 1.00


c:\Users\Jon\Test>test 1000 1001
============ Normalize ============
NormalizeWithSplitAndJoin  1156299 0:29.898 21.99
NormalizeWithRegex        23243802 0:27.335  1.00

Buradaki ilk sayı yineleme sayısı, ikincisi geçen süre ve üçüncüsü 1.0 en iyi olan ölçeklendirilmiş bir puandır.

Yani gösterileri ki en azından (bu dahil) bazı durumlarda normal bir ifade olabilir Böl daha iyi performans / çok önemli bir farkla bazen çözüm katılın.

Ancak, bir "tüm boşluk" gereksinimine geçerseniz, Böl / Birleştir kazanmış gibi görünür. Çoğu zaman olduğu gibi, şeytan ayrıntıda gizlidir ...


1
Harika analiz. Görünüşe göre ikimiz de değişen derecelerde haklıyız. Cevabımdaki kod, tüm beyaz boşlukları ve / veya kontrol karakterlerini bir dizeden ve baştan ve sondan normalleştirme yeteneğine sahip daha büyük bir işlevden alındı.
Scott Dorman

1
Yalnızca belirttiğiniz boşluk karakterleriyle, testlerimin çoğunda regex ve Split / Join neredeyse eşitti - S / J'nin doğruluk ve karmaşıklık pahasına küçük, çok küçük bir yararı vardı. Bu nedenlerden dolayı normalde normal ifadeyi tercih ederim. Beni yanlış anlamayın - bir regex hayranı olmaktan çok uzağım, ancak performansı gerçekten test etmeden performans uğruna daha karmaşık kodlar yazmayı sevmiyorum.
Jon Skeet

NormalizeWithSplitAndJoin çok daha fazla çöp yaratacaktır, gerçek bir sorunun banchmark'tan daha fazla GC süresine çarpıp çarpmayacağını söylemek zordur.
Ian Ringrose

@IanRingrose Ne tür çöpler yaratılabilir?
Dronz

18

Normal bir espresso, en kolay yol olacaktır. Normal ifadeyi doğru şekilde yazarsanız, birden fazla çağrı yapmanız gerekmez.

Şununla değiştirin:

string s = System.Text.RegularExpressions.Regex.Replace(s, @"\s{2,}", " "); 

Tek sorunum, @"\s{2,}"tek sekmeleri ve diğer Unicode boşluk karakterlerini boşlukla değiştirememesidir. 2 sekmeyi boşlukla değiştirecekseniz, muhtemelen 1 sekmeyi boşlukla değiştirmelisiniz. @"\s+"bunu senin için yapacak.
David Specht

17

Varolan cevaplar ince olsa da, ben bir yaklaşım işaret etmek istiyorum değil iş:

public static string DontUseThisToCollapseSpaces(string text)
{
    while (text.IndexOf("  ") != -1)
    {
        text = text.Replace("  ", " ");
    }
    return text;
}

Bu sonsuza kadar dönebilir. Nedenini tahmin etmek isteyen var mı? (Buna sadece birkaç yıl önce bir haber grubu sorusu olarak sorulduğunda karşılaştım ... biri aslında bir sorun olarak karşılaştı.)


Sanırım bu sorunun SO'da bir süre sorulduğunu hatırlıyorum. IndexOf, Replace'in yapmadığı belirli karakterleri yok sayar. Yani çift boşluk her zaman oradaydı, asla kaldırılmadı.
Brandon

19
Bunun nedeni, IndexOf'un bazı Unicode karakterlerini yok saymasıdır, bu durumda özel suç, bazı Asya karakterleri iirc'dir. Hmm, Google'a göre sıfır genişlikli birleştirici.
ahawker


Zor yoldan öğrendim. Özellikle iki Sıfır Genişlikli Birleştirici Olmayan (\ u200C \ u200C). IndexOf bu "çift boşluğun" dizinini döndürür, ancak Replace onu değiştirmez. Bence IndexOf için Değiştir ile aynı şekilde davranması için StringComparsion (Ordinal) belirtmeniz gerekiyor. Bu şekilde, bu ikisinin hiçbiri "çift boşluk" bulmayacaktır. StringComparsion hakkında daha fazla bilgi docs.microsoft.com/en-us/dotnet/api/…
Martin Brabec

4

Daha önce de belirtildiği gibi, bu, normal bir ifade ile kolayca yapılır. Baştaki / sondaki boşluktan kurtulmak için buna bir .trim () eklemek isteyebileceğinizi ekleyeceğim.


4

İşte çalıştığım Çözüm. RegEx ve String.Split olmadan.

public static string TrimWhiteSpace(this string Value)
{
    StringBuilder sbOut = new StringBuilder();
    if (!string.IsNullOrEmpty(Value))
    {
        bool IsWhiteSpace = false;
        for (int i = 0; i < Value.Length; i++)
        {
            if (char.IsWhiteSpace(Value[i])) //Comparion with WhiteSpace
            {
                if (!IsWhiteSpace) //Comparison with previous Char
                {
                    sbOut.Append(Value[i]);
                    IsWhiteSpace = true;
                }
            }
            else
            {
                IsWhiteSpace = false;
                sbOut.Append(Value[i]);
            }
        }
    }
    return sbOut.ToString();
}

Böylece yapabilirsiniz:

string cleanedString = dirtyString.TrimWhiteSpace();

4

Felipe Machado tarafından hızlı bir ekstra boşluk temizleyici. (Çoklu alan kaldırma için RW tarafından değiştirildi)

static string DuplicateWhiteSpaceRemover(string str)
{
    var len = str.Length;
    var src = str.ToCharArray();
    int dstIdx = 0;
    bool lastWasWS = false; //Added line
    for (int i = 0; i < len; i++)
    {
        var ch = src[i];
        switch (ch)
        {
            case '\u0020': //SPACE
            case '\u00A0': //NO-BREAK SPACE
            case '\u1680': //OGHAM SPACE MARK
            case '\u2000': // EN QUAD
            case '\u2001': //EM QUAD
            case '\u2002': //EN SPACE
            case '\u2003': //EM SPACE
            case '\u2004': //THREE-PER-EM SPACE
            case '\u2005': //FOUR-PER-EM SPACE
            case '\u2006': //SIX-PER-EM SPACE
            case '\u2007': //FIGURE SPACE
            case '\u2008': //PUNCTUATION SPACE
            case '\u2009': //THIN SPACE
            case '\u200A': //HAIR SPACE
            case '\u202F': //NARROW NO-BREAK SPACE
            case '\u205F': //MEDIUM MATHEMATICAL SPACE
            case '\u3000': //IDEOGRAPHIC SPACE
            case '\u2028': //LINE SEPARATOR
            case '\u2029': //PARAGRAPH SEPARATOR
            case '\u0009': //[ASCII Tab]
            case '\u000A': //[ASCII Line Feed]
            case '\u000B': //[ASCII Vertical Tab]
            case '\u000C': //[ASCII Form Feed]
            case '\u000D': //[ASCII Carriage Return]
            case '\u0085': //NEXT LINE
                if (lastWasWS == false) //Added line
                {
                    src[dstIdx++] = ' '; // Updated by Ryan
                    lastWasWS = true; //Added line
                }
                continue;
            default:
                lastWasWS = false; //Added line 
                src[dstIdx++] = ch;
                break;
        }
    }
    return new string(src, 0, dstIdx);
}

Kriterler ...

|                           | Time  |   TEST 1    |   TEST 2    |   TEST 3    |   TEST 4    |   TEST 5    |
| Function Name             |(ticks)| dup. spaces | spaces+tabs | spaces+CR/LF| " " -> " "  | " " -> " " |
|---------------------------|-------|-------------|-------------|-------------|-------------|-------------|
| SwitchStmtBuildSpaceOnly  |   5.2 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
| InPlaceCharArraySpaceOnly |   5.6 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
| DuplicateWhiteSpaceRemover|   7.0 |    PASS     |    PASS     |    PASS     |    PASS     |    PASS     |
| SingleSpacedTrim          |  11.8 |    PASS     |    PASS     |    PASS     |    FAIL     |    FAIL     |
| Fubo(StringBuilder)       |    13 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
| User214147                |    19 |    PASS     |    PASS     |    PASS     |    FAIL     |    FAIL     | 
| RegExWithCompile          |    28 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
| SwitchStmtBuild           |    34 |    PASS     |    FAIL     |    FAIL     |    PASS     |    PASS     |
| SplitAndJoinOnSpace       |    55 |    PASS     |    FAIL     |    FAIL     |    FAIL     |    FAIL     |
| RegExNoCompile            |   120 |    PASS     |    PASS     |    PASS     |    PASS     |    PASS     |
| RegExBrandon              |   137 |    PASS     |    FAIL     |    PASS     |    PASS     |    PASS     |

Karşılaştırma notları: Yayın Modu, hata ayıklayıcı takılı değil, i7 işlemci, ortalama 4 çalıştırma, yalnızca kısa diziler test edildi

SwitchStmtBuildSpaceOnly Felipe Machado 2015 tarafından ve Sunsetquest tarafından değiştirildi

InPlaceCharArraySpaceOnly tarafından Felipe Machado 2015 ve değiştirilen Sunsetquest

SwitchStmtBuild Felipe Machado 2015 tarafından ve Sunsetquest tarafından değiştirildi

SwitchStmtBuild2 Felipe Machado 2015 tarafından hazırlanmıştır ve Sunsetquest tarafından değiştirilmiştir

SingleSpacedTrim David S 2013 tarafından

Fubo (StringBuilder) fubo 2014 tarafından

SplitAndJoinOnSpace Jon Skeet tarafından 2009

RegExWithCompile Jon Skeet tarafından 2009

User214147 tarafından user214147

RegExBrandon, Brandon tarafından

RegExNoCompile Tim Hoolihan tarafından

Karşılaştırma kodu Github'da


1
Burada atıfta bulunulan makalemi görmek güzel! (Ben Felipe Machado) BenchmarkDotNet adlı uygun bir kıyaslama aracı kullanarak güncellemek üzereyim! Tüm çalışma zamanlarında çalıştırmalar kurmaya çalışacağım (artık DOT NET CORE ve beğenilerimiz olduğuna göre ...
Loudenvier

1
@Loudenvier - Bu konuda iyi iş çıkardınız. Sizinki neredeyse% 400 ile en hızlı olanıydı! .Net Core, ücretsiz% 150-200 performans artışı gibidir. C ++ performansına yaklaşıyor ancak kodlaması çok daha kolay. Yorum için teşekkürler.
Sunsetquest

1
Bu yalnızca boşluklar yapar, diğer beyaz boşluk karakterlerini değil. Belki src [i] == '\ u0020' yerine char.IsWhiteSpace (ch) istersiniz. Bunun topluluk tarafından düzenlendiğini fark ettim. Onlar bunu yaptılar mı?
Evil Güvercin

3

Kullandığımı paylaşıyorum, çünkü farklı bir şey bulduğum görülüyor. Bunu bir süredir kullanıyorum ve benim için yeterince hızlı. Diğerlerine karşı nasıl yığıldığından emin değilim. Bunu sınırlandırılmış bir dosya yazıcısında kullanıyorum ve büyük veri tablolarını her seferinde bir alan üzerinden çalıştırıyorum.

    public static string NormalizeWhiteSpace(string S)
    {
        string s = S.Trim();
        bool iswhite = false;
        int iwhite;
        int sLength = s.Length;
        StringBuilder sb = new StringBuilder(sLength);
        foreach(char c in s.ToCharArray())
        {
            if(Char.IsWhiteSpace(c))
            {
                if (iswhite)
                {
                    //Continuing whitespace ignore it.
                    continue;
                }
                else
                {
                    //New WhiteSpace

                    //Replace whitespace with a single space.
                    sb.Append(" ");
                    //Set iswhite to True and any following whitespace will be ignored
                    iswhite = true;
                }  
            }
            else
            {
                sb.Append(c.ToString());
                //reset iswhitespace to false
                iswhite = false;
            }
        }
        return sb.ToString();
    }

2

Jon Skeet'in gönderdiği test programını kullanarak, daha hızlı çalışması için elle yazılmış bir döngü alıp alamayacağımı görmeye çalıştım.
NormalizeWithSplitAndJoin'i her seferinde yenebilirim, ancak NormalizeWithRegex'i yalnızca 1000, 5 girdilerle geçebilirim.

static string NormalizeWithLoop(string input)
{
    StringBuilder output = new StringBuilder(input.Length);

    char lastChar = '*';  // anything other then space 
    for (int i = 0; i < input.Length; i++)
    {
        char thisChar = input[i];
        if (!(lastChar == ' ' && thisChar == ' '))
            output.Append(thisChar);

        lastChar = thisChar;
    }

    return output.ToString();
}

Jitter'in ürettiği makine koduna bakmadım, ancak problemin StringBuilder.Append () çağrısının harcadığı zamandır ve çok daha iyisini yapmak için güvenli olmayan kodun kullanılması gerekir.

Yani Regex.Replace () çok hızlı ve yenmesi zor !!


2

VB.NET

Linha.Split(" ").ToList().Where(Function(x) x <> " ").ToArray

C #

Linha.Split(" ").ToList().Where(x => x != " ").ToArray();

LINQ = D'nin gücünün tadını çıkarın


Kesinlikle! Bana göre bu da en zarif yaklaşım. Öyleyse kayıt için, C # 'da şöyle olurdu:string.Join(" ", myString.Split(' ').Where(s => s != " ").ToArray())
Efrain

1
SplitTüm boşlukları yakalamak ve Wheremaddeyi kaldırmak için küçük bir gelişme :myString.Split(null as char[], StringSplitOptions.RemoveEmptyEntries)
David

1
Regex regex = new Regex(@"\W+");
string outputString = regex.Replace(inputString, " ");

Bu, tüm sözcük olmayan karakterleri boşlukla değiştirir. Bu nedenle, parantez ve tırnak işaretleri gibi şeylerin yerini alır, ki bu istediğiniz şey olmayabilir.
Herman

0

En küçük çözüm:

var regExp = / \ s + / g, newString = oldString.replace (regExp, '');


0

Bunu deneyebilirsiniz:

    /// <summary>
    /// Remove all extra spaces and tabs between words in the specified string!
    /// </summary>
    /// <param name="str">The specified string.</param>
    public static string RemoveExtraSpaces(string str)
    {
        str = str.Trim();
        StringBuilder sb = new StringBuilder();
        bool space = false;
        foreach (char c in str)
        {
            if (char.IsWhiteSpace(c) || c == (char)9) { space = true; }
            else { if (space) { sb.Append(' '); }; sb.Append(c); space = false; };
        }
        return sb.ToString();
    }

0

Değiştirme grupları, birden çok beyaz boşluk karakterinin aynı tek karakterle değiştirilmesini çözümleyen impler yaklaşımı sağlar :

    public static void WhiteSpaceReduce()
    {
        string t1 = "a b   c d";
        string t2 = "a b\n\nc\nd";

        Regex whiteReduce = new Regex(@"(?<firstWS>\s)(?<repeatedWS>\k<firstWS>+)");
        Console.WriteLine("{0}", t1);
        //Console.WriteLine("{0}", whiteReduce.Replace(t1, x => x.Value.Substring(0, 1))); 
        Console.WriteLine("{0}", whiteReduce.Replace(t1, @"${firstWS}"));
        Console.WriteLine("\nNext example ---------");
        Console.WriteLine("{0}", t2);
        Console.WriteLine("{0}", whiteReduce.Replace(t2, @"${firstWS}"));
        Console.WriteLine();
    }

Lütfen ikinci örneğin tek tuttuğunu \n, kabul edilen cevabın satır sonunu boşlukla değiştireceğini unutmayın.

Beyaz boşluk karakterlerinin herhangi bir kombinasyonunu ilkiyle değiştirmeniz gerekirse , arka referansı \kdesenden kaldırmanız yeterlidir .


0

2 veya daha fazla beyaz alanı tek boşlukla değiştirmek için normal ifadeyi kullanmak da iyi bir çözümdür.

Normal ifade kalıbını " \ s + " olarak kullanıyoruz.

  • \ s; boşluk, sekme, yeni satır, satır başı, form besleme veya dikey sekmeyle eşleşir.

  • "+" bir veya daha fazla tekrarı belirtir.

Normal İfade Örneği

String blogName = "  Sourav .  Pal.   "

 String nameWithProperSpacing = blogName.replaceAll("\\s+", " ");   
System.out.println( nameWithProperSpacing );

-1

Bunu yapmak için yerleşik bir yol yok. Bunu deneyebilirsiniz:

private static readonly char[] whitespace = new char[] { ' ', '\n', '\t', '\r', '\f', '\v' };
public static string Normalize(string source)
{
   return String.Join(" ", source.Split(whitespace, StringSplitOptions.RemoveEmptyEntries));
}

Bu, baştaki ve sondaki beyaz boşlukları kaldıracak ve herhangi bir dahili beyaz boşluğu tek bir boşluk karakterine daraltacaktır. Eğer gerçekten sadece boşlukları daraltmak istiyorsanız, düzenli ifade kullanan çözümler daha iyidir; aksi takdirde bu çözüm daha iyidir. ( Jon Skeet tarafından yapılan analize bakın .)


7
Normal ifade derlenmiş ve önbelleğe alınmışsa, bunun bölme ve birleştirmeden daha fazla ek yüke sahip olduğundan emin değilim, bu da bir sürü ara çöp dizesi oluşturabilir. Yolunuzun daha hızlı olduğunu varsaymadan önce her iki yaklaşımı da dikkatli bir şekilde karşılaştırdınız mı?
Jon Skeet

1
boşluk burada bildirilmemiştir
Tim Hoolihan

3
Genel gider demişken, neden arıyorsun source.ToCharArray()ve sonra sonucu çöpe atıyorsun?
Jon Skeet

2
Ve çağıran ToCharArray()bir yazı olması için string.join sonucuna, sadece yükü şikayetçi sadece dikkat çekicidir, yeni bir dize ... vay oluşturun. -1.
Jon Skeet

1
Oh, ve varsayarsak whitespace, new char[] { ' ' }girdi dizesi bir boşlukla başlarsa veya biterse bu yanlış sonuç verir.
Jon Skeet
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.