21
\$\begingroup\$

Given a string and a substring and a positive integer n.

Remove the n occurences of a substring from the end of the originally given string. If the substring is not present in the string or the number of times the substring appears is less than n, then the output is the original string. No space should be present in place of the removed substring.

RULES

  1. The shortest code wins the challenge (as this is ).

Test-cases:

Input:
String = "Cats do meow-meow while lions do not meow."
Substring = "meow"
n = 2

Output:
"Cats do meow- while lions do not ."


Input:
String = "Cats do not memeowow, they do meow-ing."
Substring = "meow"
n = 2

Output:
"Cats do not meow, they do -ing."


Input:
String = "Cats do meow-meow while lions do not meow."
Substring = "meow"
n = 4

Output:
"Cats do meow-meow while lions do not meow."
\$\endgroup\$
23
  • 9
    \$\begingroup\$ What happens if removing a substring once would create more of it, like memeowow? What if matches overlap? \$\endgroup\$
    – xnor
    Commented Oct 25, 2022 at 7:49
  • 5
    \$\begingroup\$ The phrase 'Remove the 'n' duplicates of substring from the end of' is also a bit confusing. When I first read it, I assumed that the substrings would be at the end of the string, not also contained within it. It only becomes clear from the examples... \$\endgroup\$ Commented Oct 25, 2022 at 10:42
  • 4
    \$\begingroup\$ @KevinCruijssen As made clear now in question, it would return the original string without doing anything. I am very sorry for not framing question properly. \$\endgroup\$
    – anoman
    Commented Oct 25, 2022 at 10:51
  • 4
    \$\begingroup\$ Follow up question on the nested substrings. What is your desired result for Cats do meow not memeowow, they do meow-ing. where n=3? \$\endgroup\$
    – JvdV
    Commented Oct 25, 2022 at 19:49
  • 4
    \$\begingroup\$ I think the term "duplicates" is also a little confusing; it also led me to expect that the substring was expected to be directly repeated n times, with nothing else between, at the end of the string. I would phrase this as "Remove n occurrences of the substring from the input. If the input contains more than than n occurrences of the substring, only the last n should be removed. If the input contains fewer than n occurrences, it should be returned unchanged". \$\endgroup\$
    – Ben
    Commented Oct 26, 2022 at 7:48

28 Answers 28

12
\$\begingroup\$

Excel (ms365), 115, 83 bytes

-32 also thanks to @jdt and the idea to split instead of loop.

=LET(a,TEXTSPLIT(A1,,B1),b,ROWS(a),IF(b>C1,CONCAT(IF(SEQUENCE(b)<b-C1,a&B1,a)),A1))

enter image description here


Note: This answer has been edited to no longer loop to remove the substring from the end of the string, but split the input instead. This would now give the appropriate answer for input like 'Cats do meow not memeowow, they do meow-ing.' where 'n=3'.

\$\endgroup\$
0
10
\$\begingroup\$

Python, 66 53 52 bytes

  • -1 byte thanks to @92.100.97.109 (though not exactly in the way they intended)
lambda i,j,k:[i,''.join(a:=i.rsplit(j,k))][len(a)>k]

Attempt This Online!

Returning identity when the occurrences are less than N is annoying.

\$\endgroup\$
11
  • 1
    \$\begingroup\$ <s>Alternative 53 byte solution</s> \$\endgroup\$ Commented Oct 25, 2022 at 14:29
  • 1
    \$\begingroup\$ Your version flipped the condition I think, it didn't work properly in the equals case but I'm not really sure what I changed now. \$\endgroup\$
    – mousetail
    Commented Oct 25, 2022 at 14:38
  • 4
    \$\begingroup\$ 51 bytes feels like it could go even lower \$\endgroup\$ Commented Oct 25, 2022 at 15:39
  • 1
    \$\begingroup\$ @mousetail Hi, I am learning python as my first language but I never saw any code like this.Like the functions I learnt (for,while,if,else) and datatypes(integer,string,list) are not here. This code seems very alien/scary. Is this a coding by hiding functions? Thanks. \$\endgroup\$
    – anoman
    Commented Oct 26, 2022 at 4:48
  • 1
    \$\begingroup\$ @anoman you will find many such answers on this site. In real life, you should avoid golfing your programs, for that makes it hard to understand. But this website is where weird but short programs live. \$\endgroup\$ Commented May 6 at 1:57
7
\$\begingroup\$

Python, 51 bytes

lambda A,B,n:"".join(A.rsplit(B,n*(n<=A.count(B))))
\$\endgroup\$
7
\$\begingroup\$

C (clang), 109 107 112 105 100 bytes

-11 bytes thanks to c--

-2 bytes thanks to ceilingcat

+5 bytes for fixing an error pointed out by JvdV.

f(*a,i,*b,j,n,**r){for(*r=wcsdup(a);i--*n>0;)!bcmp(a+i,b,j)?wcscpy(a+i,a+i+j),n--,i-=j:0;!n?*r=a:0;}

Try it online!

\$\endgroup\$
0
6
\$\begingroup\$

T-SQL 135 bytes

Is not looping to avoid the string being created again "memeowow"

WITH c(z)as(SELECT 1UNION ALL 
SELECT charindex(@r,@,z+1)FROM c WHERE z>0)SELECT top(@n)@=stuff(@,z,len(@r),'')FROM
c ORDER BY-z PRINT @

Try it online

\$\endgroup\$
6
\$\begingroup\$

Ruby, 67 61 75 bytes

Updated after clarification by OP, which unfortunately made it longer.

->s,u,n,*a{s.scan(u){a<<$`.size}
a.size<n||eval("s[a.pop,u.size]='';"*n)
s}

Attempt This Online!

\$\endgroup\$
0
5
\$\begingroup\$

05AB1E, 13 bytes

¡`.gI›I*F«}¹ý

Inputs in the order substring, string, n.

Try it online or verify some more n.

Explanation:

¡           # Split the (implicit) second input-sentence on the (implicit) first substring
 `          # Pop and push all parts separated to the stack
  .g        # Get the amount of items on the stack
    I›      # Check if this is larger than the third input-integer `n`
      I*    # Multiply it by the third input-integer `n`
        F   # Loop that many times:
         «  #  Concat the top two parts on the stack together
        }¹ý # After the loop: join the stack with the first input substring as delimiter
            # (after which the result is output implicitly)
\$\endgroup\$
2
  • \$\begingroup\$ @TheThonnu Ah ok. That's just the way I make my comments for filters/maps/if-statement. I place the } below where it was opened. Here another example of that. \$\endgroup\$ Commented Oct 25, 2022 at 12:55
  • 1
    \$\begingroup\$ Oh, ok. Thanks for the clarification. \$\endgroup\$
    – The Thonnu
    Commented Oct 25, 2022 at 13:02
5
+200
\$\begingroup\$

Pip, 30 bytes

d:a^b#d>c?dZJ(bRL#d-UcALxRLc)a

Try It Online!

How?

d:a^b#d>c?dZJ(bRL#d-UcALxRLc)a
  a                            : First arg(string)
    b                          : Second arg(substring)
        c                      : Third arg(n)
d:                             : Assign d to the result of
  a^b                          : Split a on separator b
         ?                     : If
     #d>c                      : Length of d is greater then c
                    Uc           : Increment c
             bRL                 : Repeat b 
                #d-              : Length of d - c times
                      AL         : Append list
                        x        : Empty String
                         RL      : Repeat x
                           c     : c times
          dZJ                    : Zip with iterable d
                             a : Else return a
\$\endgroup\$
5
\$\begingroup\$

JavaScript (ES6), 62 bytes

Expects (string, substring, n).

(s,q,n)=>(a=s.split(q))[n]+1?a.map(s=>a[++n]+1?s+q:s).join``:s

Try it online!

Commented

(s, q, n) => (   // s = string, q = substring, n = integer
  a = s.split(q) // split s on q and save the result in a[]
                 // if q appears x times, we get x + 1 entries
                 // some of these entries may be empty strings (falsy)
                 // so we do '+ 1' to distinguish between empty and undefined
)[n] + 1 ?       // if a[n] is defined:
  a.map(s =>     //   for each entry s in a[]:
    a[++n] + 1 ? //     increment n; if a[n] is still defined:
      s + q      //       append q to s
    :            //     else:
      s          //       leave s unchanged
  ).join``       //   end of map(); join everything back together
:                // else:
  s              //   return s unchanged
\$\endgroup\$
5
\$\begingroup\$

Vyxal s, 11 bytes

O≤[Ẇy?NẎY|_

Try it Online!

12 bytes without the flag. Takes substring, text, n

Explained

O≤[Ẇy?NẎY|_
O≤          # is the count of the substring in text less than n?
  [      |  # if so:
   Ẇy       #   Split text on substring, keeping the delimiter. Then uninterleave - this gives a list of splits and a list of delimiters
     ?NẎ    #   Take up to the -nth item of the list of delimiters
        Y   #   Reinterleave the delimiters, which will now be shorter than before. The s flag combines all the pieces into a single sting
         |_ # Otherwise, just return the original text
\$\endgroup\$
5
\$\begingroup\$

Desmos, 169 bytes

S=s.length
L=l.length
I=[L-S...1]
A=I[[(l[i...i+S]-s)^2.totalfori=I]=0]
B=\{n>A.\length:[-L],A[1...n]\}
f(l,s,n)=l[[[0^{(b-B)^2}.maxforb=z-[0...S-1]].maxforz=[1...L]]=0]

Try It On Desmos!

Try It On Desmos! - Prettified

\$f(l,s,n)\$ takes in two lists of codepoints, with \$l\$ representing the string and \$s\$ representing the substring, and a positive integer \$n\$, representing the number of substrings to remove. The output is also a list of codepoints.

As you can tell by the byte size of the code, Desmos is not really well suited for problems related with string manipulation (When was using Desmos on a string problem ever a good idea?!?!). Also fun fact, this is my first time using a nested list comprehension in Desmos, which is pretty nice. There's probably a better way of doing that part, but nested list comprehensions look cool :P (to be completely honest, I can't think of any better way to do that lol).

\$\endgroup\$
5
\$\begingroup\$

Red, 89 bytes

func[s t n][reverse t either parse r: reverse copy s[n to remove t to end][reverse r][s]]

Try it online!

Working on a reversed copy of the string, I use parse to remove n times the reversed substring. If parse succeeds, I return the modified string reversed, otherwise - the original string.

\$\endgroup\$
5
\$\begingroup\$

Japt v2.0a0, 21 bytes

qW ÔË+WpE>VÃw
qWpUʧV

Try it

Input as string, n, substring

v2.0a0 is necessary because v1.4.6 errors if you try to use p this way.

Explanation:

qW ÔË+WpE>VÃw
qW            # Split <string> where <substring> appears
   Ô          # Reverse the array
    Ë      Ã  # For each item in the array:
     +        #  Append:
      Wp      #   <substring> repeated a number of times equal to:
        E>V   #    The current index is greater than <n>
              #    (Boolean gets converted to 1 or 0)
            w # Reverse the new array
              # Store as U

qWpUʧV
q             # Turn U into a string by inserting this between each item:
 Wp           #  <substring> repeated a number of times equal to:
   UÊ         #   Length of U (i.e. 1 + number of times <substring> appeared)
     §V       #   Is less than or equal to <n>
              # Output that string

I've tried an alternate using ð but the best I got was 25 bytes.

\$\endgroup\$
0
4
\$\begingroup\$

Pyth, 21 bytes

s.iJcwz*]zt-lJ*KE>lJK

Try it online!

Explanation

s                        concatenate the elements of
 .i                      interleave
   Jcwz                  the input string split on the substring (assigned to J) and
        ]z               a singleton list of the substring
       *                 duplicated
          t              one less than
           -lJ           the length of J minus
              *KE>lJK    0 if substring has less than n occurrences, n otherwise
\$\endgroup\$
4
\$\begingroup\$

simply, 99 bytes

Creates an anonymous function that returns the expected result.

The argument order is $full_string, $substring and then $number.

fn($F$S$N)&iff(run&len(&str_split($F$S))>$N&str_rev(&str_replace(&str_rev($F)&str_rev($S)''$N))$F);

How does it work?

First, splits the full string by the substring, and counts the number of elements.

When splitting the string:

  • If the substring isn't present, returns 1 element.
  • If it is present, returns 1 extra element.
    Example: &str_split("abcbdbebf", 'b') -> ["a", "c", "d", "e", "f"]
    For 4 'b's, returns 5 elements.

If the number of elements is higher than $number, that means there's at least $number substrings in the full string.

The &iff() function is just the same as a ternary operator, in C-like languages.

To remove from the end of the string, I need to reverse the string, since there isn't a way to indicate to remove from the end of the string.
However, it takes an argument that indicates how many times to replace, which means, it will replace up to $number occurrences.

The result is returned automatically, since it is a function without a scope, just like an arrow function in JavaScript.

Ungolfed

Code-y looking:

$fn = fn($full_string, $substring, $num) => &iff(
    (call &len(&str_split($full_string, $substring))) > $num,
    &str_rev(
        &str_replace(
            &str_rev($full_string),
            &str_rev($substring),
            '', $num
        )
    ),
    $full_string
);

Pseudo-code looking:

Set $fn to an anonymous function ($full_string, $substring, $num)
Begin.
    Set $count to the result of calling &len(Call &str_split($full_string, $substring)).
    
    Return the result of calling &iff(
        $count > $num,
        Call &str_rev(
            Call &str_replace(
                Call &str_rev($full_string),
                Call &str_rev($substring),
                '', $num
            )
        ),
        $full_string
    ).
End.
\$\endgroup\$
4
\$\begingroup\$

Retina 0.8.2, 87 bytes

.+$
$*
(?=.*¶(.+)¶)\1
¶
rT`¶``(?(1)$)(?<-1>¶.*)+(?=¶.+¶(1)+$)
+`¶(.*¶(.+)¶1+$)
$2$1
1G`

Try it online! Takes the string, substring and count on three separate lines. Explanation:

.+$
$*

Convert the count to unary.

(?=.*¶(.+)¶)\1
¶

Replace occurrences of the substring in the string with newlines.

rT`¶``(?(1)$)(?<-1>¶.*)+(?=¶.+¶(1)+$)

Remove the last n newlines from the string if there are in fact that many. (Today I discovered that an empty "to" string in transliteration is treated as _, i.e. delete matching characters.) The r causes the regular expression to be evaluated from right to left, so the lookahead gets processed first (setting $#1), then the newline-matching loop in the middle, then the count check at the beginning.

+`¶(.*¶(.+)¶1+$)
$2$1

Replace (remaining) newlines with the substring.

1G`

Keep only the (final) string.

\$\endgroup\$
4
\$\begingroup\$

Charcoal, 25 bytes

Nθ≔⪪ηζηF›Lηθ⊞η⪫⮌E⊕θ⊟ηω⪫ηζ

Try it online! Link is to verbose version of code. Takes inputs in the order count, string, substring. Explanation:

Nθ

Input the count as an integer.

≔⪪ηζη

Split the string on the substring.

F›Lηθ

If this results in more than enough pieces, then...

⊞η⪫⮌E⊕θ⊟ηω

... join the last n+1 pieces together.

⪫ηζ

Join the (remaining) pieces with the substring.

\$\endgroup\$
4
\$\begingroup\$

PHP 8.x, 65 bytes

This anonymous function does the replacement, up to $n, and checks how many replacements were made.
If there were fewer than $n replacements made, returns the original string.

fn($F,$S,$N)=>($r=preg_replace("@$S@",'',$F,$N,$x))&&$N>$x?$F:$r;

Due to the use of preg_replace, the substring must be a valid regular expression.
Regular words will always be valid regular expressions, and there's no need to worry about it.

How does it work?

The function preg_replace takes the following arguments:

  • $pattern - Which will be the substring, $S
  • $replacement - Which is an empty string
  • $subject - The full string, $F
  • $limit - Maximum number of replacements, $N
  • &$count - Variable which will hold the number of replacements made

If $N is higher than $count, returns the full string, otherwise returns the result of the replacement.

Example

$fn=fn($F,$S,$N)=>($r=preg_replace("@$S@",'',$F,$N,$x))&&$N>$x?$F:$r;

echo $fn('Cats do not memeowow, they do meow-ing.', 'meow', 2);
// Should display: Cats do not meow, they do -ing.

You can try this on https://onlinephp.io/c/d08f3

\$\endgroup\$
4
\$\begingroup\$

PHP, 75 111 bytes

Okay so... lot of change

+12 bytes for a bug found by @Ismael Miguel where loop keep running if no instance of searched string given (strrpos()===false)

+26 bytes for using $argv instead or pre defined variable as indicated by @Sʨɠɠan

-2 bytes (Hey! an actual upgrade :D) from @Sʨɠɠan who remind me i can just get ride of {} for a single line instruction ^^'

for($s=$argv[1];$argv[3]--&&false!==$c=strrpos($s,$argv[2]);)$s=substr($s,0,$c).substr($s,$c+strlen($argv[2]));

Try it online!

\$\endgroup\$
13
  • 1
    \$\begingroup\$ If you don't mind using older versions, like 5.3-5.6 or some 7.x versions, you can reduce by 2 bytes by removing the double quotes. \$\endgroup\$ Commented Oct 25, 2022 at 14:18
  • 1
    \$\begingroup\$ Also, you have a bug. The rules say: "If the substring is not present in the string or the number of times the substring appears is less than n, then the output is the original string.". For an n = 5, on test case 0, it displays "not , they do -ing.". \$\endgroup\$ Commented Oct 25, 2022 at 14:23
  • 2
    \$\begingroup\$ You don't need the {}. \$\endgroup\$
    – naffetS
    Commented Oct 26, 2022 at 1:36
  • 2
    \$\begingroup\$ However, by our consensus, we can't take input/output via a variable. You can try taking input via ARGV, though. That's often shorter than a function \$\endgroup\$
    – naffetS
    Commented Oct 26, 2022 at 1:37
  • 2
    \$\begingroup\$ BTW, it's actually shorter to reference substr twice than storing it in a variable, even without the quotes. \$\endgroup\$
    – naffetS
    Commented Oct 26, 2022 at 1:38
4
\$\begingroup\$

Raku, 42 bytes

{$^a.flip.subst($^b.flip,'',:x($^c)).flip}

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ Welcome to Code Golf! \$\endgroup\$ Commented Nov 1, 2022 at 19:54
2
\$\begingroup\$

Shell, 238 236 220 200 199 194 191 186 bytes

(counting the last newline to make the script file containing those commands a real unix text file. Otherwise 185 bytes.)

Can probably be highly optimized, but I found it funny to attempt.

usage: cat Text-cases | this_script , or this_script < Test-cases

sed -e'/^O/N;s,\n,,;s,",,g;s, = ,:,g'|awk -F: '/^St/{I=$2}
/^Su/{S=$2}
/^n/{n=$2
R=s=""
for(i=1;i<=n;i++){R=R sprintf("\\(.*\\)%s",S)
s=s"\\"i}
system("echo "I"|sed -e\"s,"R","s",\"")}'

Explanation:

the sed : 
  1) put the line following "Output:" at the end of that line
  2) takes out the doublequotes
  3) replace " = " by ":"
Then the awk:
  retrieve (I)nput String,
           (S)ubstring, 
       and (n),
  loops on n to construct:
         the (r)egex "\(.*\)S\(.*\)S\(.*\)S" (if n==3)
     and its (s)ubstitution "\1\2\3"
  and use sed to do this search/replace on the (I)nput string.

I used "\n" instead of ";" when I could (in the awk part) as it 
is 1 caracter all the same, and is more legible.
\$\endgroup\$
1
\$\begingroup\$

Ruby, 64 bytes

->s,u,n,*a{x=s.split(u);l=x.size-n;x[0...l].join(u)+x[l..].join}

Try it online!

Code is mine - I thought split and join would be more efficient, but I will credit Jordan above for the tests and framework.

\$\endgroup\$
1
\$\begingroup\$

K (ngn/k), 46 bytes

{x(!#x)^,/(!#y)+/:(-z*(~z>#t))#t:&(#y)(y~)':x}

Try it online!

\$\endgroup\$
1
\$\begingroup\$

R, 124 109 108 bytes (new)

  • -11 and -1 bytes thanks to @pajonk
f=\(S,s,n,`?`=\(x)intToUtf8(rev(utf8ToInt(x))))`if`(sum(el(gregexpr(s,S))|1)>=n&n,f(?sub(?s,"",?S),s,n-1),S)

Attempt This Online!

Edit:

The old version of my golf has incorrectly treated a case, when the substring was matching exactly the end of the string. Therefore I have abandoned the old approach.

In this new version a chain of functions is used to reverse the string as well as the substring, sub is applied n times and then the string get reversed again.

Below is the old version.

R, 100 bytes
\(S,s,n,m=length(V<-el(strsplit(S,s))))
`if`(m>n,paste0(V,c(rep(s,m-1-n),rep("",n+1)),collapse=""),S)

Attempt This Online!

This answer is similar to the answer in Python. The main string S is splitted by a substring s, and then combined with a same length vector containing a reduced by n number of substrings.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ I think this fails when the substring is present at the end of the input, like Cats do meow-meow while lions do not meow. \$\endgroup\$
    – pajonk
    Commented May 4 at 19:11
  • \$\begingroup\$ @pajonk you are absolutely right - this happens since strsplit returns only the left part of the string when the match is exactly at the end. So I have coded another longer solution. \$\endgroup\$ Commented May 4 at 21:06
  • 1
    \$\begingroup\$ Nice approach! -11 bytes mostly by rewriting the string-reversing function. Also (again) - recursive function needs a name :) \$\endgroup\$
    – pajonk
    Commented May 5 at 15:38
  • \$\begingroup\$ @pajonk recursive function needs a name - Arrggh! This time i'll remember it :) \$\endgroup\$ Commented May 5 at 17:27
  • 1
    \$\begingroup\$ Another one by using >=: ATO! (I don't know how none of us spotted this before) \$\endgroup\$
    – pajonk
    Commented May 5 at 18:28
0
\$\begingroup\$

Perl 5, 74 bytes

sub{($_,$s,$n)=@_;$n*2>(@a=split/($s)/)?$_:join'',grep!/$s/||++$n*2<@a,@a}

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Python 3, 78 bytes

i=input
a=i()
b=i()
c=int(i())
d=a.rsplit(b,c)
print([a,''.join(d)][len(d)>c])

Pretty direct approach using Python 3. Try it online!

\$\endgroup\$
0
\$\begingroup\$

Perl 5 -pl, 52 bytes

$t=<>;$c=<>;chop$t;s/.*\K($t.*?){$c}/$&=~s|$t||gr/ge

Try it online!

\$\endgroup\$
0
\$\begingroup\$

Scala 3, 422 bytes

A port of @mousetail's Python answer in Scala.

Use user-defined rsplit.


422 bytes, it can be golfed more.


Golfed version. Attempt This Online!

def r(s: String, d: String, m: Int) = {
  var (r, a, n) = (List[String](), s, m)
  while (n > 0) {
    a.lastIndexOf(d) match {
      case -1 => n = 0; r ::= a; a=""
      case p => r ::= a.substring(p + d.size); a = a.substring(0, p); n -= 1
    }
  }
  (if (a.nonEmpty || m == 0) a :: r else r).reverse.toArray
}
def f(s: String, d: String, m: Int) = {
  val p = r(s, d, m)
  if (p.size > m) p.mkString("").trim else s
}

Ungolfed version. Attempt This Online!

object Main {
  def rsplit(string: String, delimiter: String, maxSplits: Int): Array[String] = {
    var remainingSplits = maxSplits
    var str = string
    var result = List[String]()

    while (remainingSplits > 0) {
      val pos = str.lastIndexOf(delimiter)
      if (pos == -1) {
        result = str :: result
        remainingSplits = 0
        str=""
      } else {
        result = str.substring(pos + delimiter.length) :: result
        str = str.substring(0, pos)
        remainingSplits -= 1
      }
    }

    if (str.nonEmpty || maxSplits == 0) {
      result = str :: result
    }

    result.toArray
  }

  def splitAndJoin(string: String, delimiter: String, maxSplits: Int): String = {
    val splitParts = rsplit(string, delimiter, maxSplits)
    if (splitParts.length > maxSplits) splitParts.mkString("") else string
  }

  def main(args: Array[String]): Unit = {
    // Test cases
    println(splitAndJoin("bor tor tor tor", "tor", 2))  // More than maxSplits
    println(splitAndJoin("bor tor tor", "tor", 2))      // Exactly equal to maxSplits
    println(splitAndJoin("bor tor", "tor", 2))      // Less than maxSplits
  }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.