16
\$\begingroup\$

The Challenge

Given a multiline string, return the longest substring of leading whitespace present in every line, ignoring empty lines or lines composed only of whitespace. If input is empty or whitespace, return nullish or the empty string--up to you.

Update: Per Meta consensus, the input may also be treated as an array of strings. My examples below could be shortened by excluding the .split() calls.


I wrote a quick script for my iPhone that comments or uncomments JavaScript code (since I can't use ctrl+/).

I want the // placed the furthest right that it can, given that each // is aligned with the other lines. If a line is only whitespace (or empty), I want to skip adding a comment.

To do this, I must determine the longest leading whitespace substring that exists in every line. So, given the input string

 const x = () =>
 y.split()
 .map(s =>
 s.length
 )
 .join()
 
 console.log(x)

the result of the algorithm would be 2 spaces. (Note: Line 7 has 4 leading spaces, but the browser may or may not remove them. It does not appear to remove them in the example solutions below.) If the input was only lines 3-7, the result would be 6 spaces.

A simple JS solution to achieve this would be

const solution = text => {
 const lines = text.split('\n').filter(l => !!l.trim())
 let result = ''
 while (lines.every(l => l.startsWith(result + ' '))) {
 result += ' '
 }
 return result
}
// test
const text = 
` const x = () =>
 y.split()
 .map(s =>
 s.length
 )
 .join()
 console.log(x)`
let result = solution(text);
console.log(`|${result}| ${result.length} spaces`)
const lines3Through7 = text.split('\n').slice(2, 7).join('\n')
result = solution(lines3Through7)
console.log(`|${result}| ${result.length} spaces`)
result = solution(
`abc
 def`)
console.log(`|${result}| ${result.length} spaces`)

I'm sure you can do better than this.

Another would be

const solution = text =>
 ' '.repeat(
 text.split('\n')
 .filter(l => !!l.trim())
 .map(l => l.length - l.trimStart().length)
 .reduce((m, v) => Math.min(m, v))
 )
// test
const text =
` const x = () =>
 y.split()
 .map(s =>
 s.length
 )
 .join()
 
 console.log(x)`
let result = solution(text);
console.log(`|${result}| ${result.length} spaces`)
const lines3Through7 = text.split('\n').slice(2, 7).join('\n')
result = solution(lines3Through7)
console.log(`|${result}| ${result.length} spaces`)
result = solution(
`abc
 def`)
console.log(`|${result}| ${result.length} spaces`)


Assume all whitespaces are space characters (i.e. not \t). For input that has no non-whitespace characters, it should return nullish or 0 spaces, up to you.

You can use any language(削除) , but I'd also like someone to present an answer in JavaScript (削除ここまで) ✅.

Shortest answer wins. Include your answer, a prettified version of your answer for readability, and for esoteric languages, an explanation of how your script works.

asked Feb 14, 2022 at 21:53
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Is the input guaranteed to contain at least one line which is neither empty nor composed only of whitespace? \$\endgroup\$ Commented Feb 14, 2022 at 22:40
  • 1
    \$\begingroup\$ Yes, that was supposed to read lines 3-7, the result would be 6 spaces. And no, not guaranteed. \$\endgroup\$ Commented Feb 14, 2022 at 23:02
  • 4
    \$\begingroup\$ Can we assume the input only contains printable ASCII including spaces (and newlines)? \$\endgroup\$ Commented Feb 15, 2022 at 12:44
  • 2
    \$\begingroup\$ What should be the output for an input that consists of nothing but empty/whitespace-only lines? \$\endgroup\$ Commented Feb 15, 2022 at 16:30
  • \$\begingroup\$ @DLosc updated! Return nullish or the empty string, up to you. \$\endgroup\$ Commented Feb 15, 2022 at 20:45

19 Answers 19

7
\$\begingroup\$

Jelly, (削除) 9 (削除ここまで) 8 bytes

=6Pi0’6ẋ

Try it online!

-1 thanks to Jonathan Allan reminding me of meta consensus on being able to take a list of lines.

=6 For each character on each line, is it a space?
 P Take the product along each column,
 i0 and find the first 1-index of 0 (or 0 if not found).
 ’ Decrement
 6ẋ and yield a space repeated that many times (or none if not positive).

n6SĬS6ẋ works similarly, but does not ignore all-whitespace lines if there's nothing else.

If we can assume the input contains no codepoints under 32 other than newlines, this almost works:

Jelly, (削除) 7 (削除ここまで) (削除) 6 (削除ここまで) 5 bytes

Ṁ»\f6

Try it online!

-1 because I tried replacing »/ with just in case that would work, and after further consideration, of course it would work--the lexicographically greatest line is precisely some line with the fewest leading spaces that has something else after them (under the assumption stated above).

-1 thanks to Jonathan Allan reminding me of meta consensus on being able to take a list of lines.

Fails to ignore all-whitespace lines if there's nothing else. A fix in fewer than 3 bytes seems unlikely (though Ṁ»\ḟȧfɗ6 in 3 bytes seems worth mentioning).

Ỵ Split on newlines.
 Ṁ Take the lexicographically greatest row,
 »\ find the largest character in each of its prefixes,
 f6 and remove any that aren't spaces.
answered Feb 15, 2022 at 7:17
\$\endgroup\$
3
  • 3
    \$\begingroup\$ These do not deal with the edge case where all the lines in the input are space-only. As I read it such an input should yield an empty string. \$\endgroup\$ Commented Feb 15, 2022 at 12:27
  • 1
    \$\begingroup\$ Also, we can actually take a list of lines by Meta consensus. \$\endgroup\$ Commented Feb 15, 2022 at 13:01
  • 1
    \$\begingroup\$ What a horrible, horrible language. 😆 Well done! \$\endgroup\$ Commented Feb 15, 2022 at 20:48
5
\$\begingroup\$

Jelly, (削除) 10 (削除ここまで) 9 bytes

-1 by using the default ruling that Submissions may use list of strings instead of multi-line strings.

n6T€FṂ’6ẋ

A monadic Link accepting a list of lines (lists of characters) that yields a list of characters.

Try it online!

How?

n6T€FṂ’6ẋ - Link: list lists of characters, Lines
 6 - space character
n - not equals? (vectorises across all the characters in Lines)
 € - for each:
 T - truthy indices (1-indexed)
 F - flatten
 Ṃ - minimum (minimum of the empty list is 0)
 ’ - decrement -> number of "common" leading spaces, N
 6 - space character
 ẋ - repeat N times (repeating -1 or 0 times gives an empty list)
answered Feb 15, 2022 at 0:37
\$\endgroup\$
4
\$\begingroup\$

05AB1E (legacy), 9 bytes

εDðKk×ばつ

Input as a list of character-lists.

Try it online (footer is added to pretty-print the string of spaces) or verify all test cases.

Explanation:

ε # Map over each inner character-list of the (implicit) input-list:
 D # Duplicate the current character-list
 ðK # Remove all spaces from the copy
 k # Get the first 0-based index of each of these characters in the list
}ß # After the map: pop and push the flattened minimum
 ×ばつ # Convert it to a string with that many spaces
 # (after which the result is output implicitly)
answered Feb 15, 2022 at 8:11
\$\endgroup\$
0
3
\$\begingroup\$

Retina 0.8.2, 16 bytes

G`\S
O^`.+
!`^ +

Try it online! Explanation:

G`\S

Discard lines that only consist of whitespace.

O^`.+

Sort them in reverse order, so the line with the least space sorts first.

!`^ +

Output the leading spaces.

If just the count of leading spaces suffices, then for 14 bytes:

G`\S
O^`.+
\G 

Try it online! Explanation: The last stage \G counts consecutive spaces at the start of the buffer.

answered Feb 15, 2022 at 0:42
\$\endgroup\$
3
\$\begingroup\$

Vyxal, 10 bytes

↵ƛðv≠TgI;g

Try it Online!

A port of the jelly answer. 8 bytes with the g flag.

Explained

↵ƛðv≠TgI;g
↵ƛ # For each item n in the input split on newlines:
 ðv≠ # Is each character not a space?
 Tg # Smallest index where ^ is truthy. Returns an empty list if all spaces. 
 I # Push that many spaces or two empty lists if it's all spaces.
 ;g # Return the smallest item of that. 
answered Feb 14, 2022 at 22:06
\$\endgroup\$
1
  • 2
    \$\begingroup\$ There no longer is "the" Jelly answer ;-) \$\endgroup\$ Commented Feb 15, 2022 at 8:06
3
\$\begingroup\$

Python, 39 bytes

lambda l:max(l)[:-len(max(l).lstrip())]

Attempt This Online!

Takes input as a list of lines, per default I/O rules.

Uses Unrelated String's observation that the lexicographically greatest line will be the one with the least leading whitespace. This means the input must not contain unprintables other than newline.

answered Feb 15, 2022 at 8:24
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I don't think we can assume that characters less than spaces will never appear. You should confirm that before using it in your answer. \$\endgroup\$ Commented Feb 15, 2022 at 12:39
3
\$\begingroup\$

Husk, (削除) 9 (削除ここまで) (削除) 11 (削除ここまで) (削除) 10 (削除ここまで) 9 bytes

Thanks to Jonathan Allan for bug-spotting (both versions)

←T↑o¬wTfw

Try it online!

Takes a list of lines, removes all-space lines (fw), transposes (T), keeps longest prefix of all spaces (↑o¬w), transposes back again (T), and output first element ().


Husk, (削除) 7 (削除ここまで) (削除) 9 (削除ここまで) 8 bytes

↑=' →Ofw

Try it online!

Uses Unrelated string's lexicographic sorting approach: Takes a list of lines, removes all-space lines (fw), sorts (O), keeps last element (), and keeps the longest prefix of spaces (↑=' ).

answered Feb 15, 2022 at 12:32
\$\endgroup\$
1
  • 2
    \$\begingroup\$ You're right. Drat. Should be fixed now. \$\endgroup\$ Commented Feb 15, 2022 at 12:59
3
\$\begingroup\$

JavaScript (ES6), 43 bytes

s=>(s.match(/^ *(?=\S)/gm)||[""]).sort()[0]

Try it online!

Or 40 bytes by using optional chaining, as suggested by the OP:

s=>s.match(/^ *(?=\S)/gm)?.sort()[0]||''

(doesn't work on TIO)

answered Feb 14, 2022 at 22:47
\$\endgroup\$
9
  • \$\begingroup\$ ... why did I not think of this? \$\endgroup\$ Commented Feb 14, 2022 at 22:57
  • \$\begingroup\$ With the optional chaining syntax ?., you could save 3 bytes. s=>s.match(/^ *(?=\S)/gm)?.sort()[0]||'' \$\endgroup\$ Commented Feb 14, 2022 at 23:05
  • 5
    \$\begingroup\$ Thank you for accepting this answer, but I'd suggest to un-accept it. We usually do not accept any answer at all on Code Golf -- or there should be a delay of at least one week or so before doing so. \$\endgroup\$ Commented Feb 14, 2022 at 23:06
  • \$\begingroup\$ Why need you ||[""] if OP's first example don't handle empty? \$\endgroup\$ Commented Feb 15, 2022 at 3:07
  • 1
    \$\begingroup\$ Oh, good point. FWIW, I was a bit confused because I only saw @l4m2's second reply (my eyes skipped over the reply above it), and I thought you got the idea for the (?!$) from the ||[""] suggestion. In any case, you could get -2 bytes by changing ||[""] to ||[] to return undefined (instead of empty string) for any input containing no lines that are not pure whitespace. That could be interpreted as returning "nullish or the empty string" as per OP's instructions. \$\endgroup\$ Commented Feb 16, 2022 at 0:47
3
\$\begingroup\$

R, (削除) 50 (削除ここまで) (削除) 62 (削除ここまで) (削除) 60 (削除ここまで) 57 bytes

Edit: -2 bytes thanks to pajonk

sub("^( *).*","\1円",sort(sub(" *$","",readLines()),T)[1])

Try it online!

answered Feb 15, 2022 at 10:23
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Two variants of -2 bytes: Try it online! or Try it online! \$\endgroup\$ Commented Feb 15, 2022 at 19:32
  • \$\begingroup\$ @pajonk - Ah! sort(,decreasing=T)! Thanks! \$\endgroup\$ Commented Feb 15, 2022 at 22:40
3
\$\begingroup\$

Haskell, (削除) 47 (削除ここまで) 46 bytes

fst.minimum.filter((>[]).snd).map(span(==' '))

Try it online!

Nothing special here, it's a very standard approach, just with less whitespace. A more presentable version:

main :: IO ()
main = interact (f . lines)
f :: [String] -> String
f = fst -- extract it from the tuple
 . minimum -- find the shortest leading whitespace (tuples are sorted by first element)
 . filter ((> []) . snd) -- remove any tuples for which 'everything else' is empty
 . map (span (== ' ')) -- split each line into tuples of (leading whitespace, everything else)

Since minimum is a partial function, it will raise an exception if given input that consists only of whitespace. From my perusal of the rules, I'm under the impression that this is acceptable; but I'm fairly new here so please correct me if I'm wrong.

answered Feb 16, 2022 at 1:19
\$\endgroup\$
8
  • 1
    \$\begingroup\$ ...actually, I have no idea where that << is coming from, so maybe this is preferable \$\endgroup\$ Commented Feb 16, 2022 at 1:37
  • 1
    \$\begingroup\$ @UnrelatedString I can't get that to typecheck :( My experience with Haskell on TIO is that it's a little bit contrived; the actual part where I count the bytes is a comment. The real code being executed is in the header. (Open to other suggestions, though.) So your TIO links are basically running my code :) \$\endgroup\$ Commented Feb 16, 2022 at 1:55
  • 1
    \$\begingroup\$ ...didn't quite notice that :| For future reference this is how Haskell answers are usually done \$\endgroup\$ Commented Feb 16, 2022 at 1:57
  • 1
    \$\begingroup\$ @UnrelatedString Ah, ok. Thanks a lot! That's so much better. \$\endgroup\$ Commented Feb 16, 2022 at 2:00
  • 2
    \$\begingroup\$ I’m on my phone so I can’t test, but could this be implemented as the following? minimum.filter(>[]).map(takeWhile(==’ ‘) (note the smart quotes - I can’t seem to figure out how to be rid of them). At the very least you should be able to use > in lieu of /=. Also with the use of both filter and map a list comprehension might be worth the additional overhead. \$\endgroup\$ Commented Feb 17, 2022 at 10:17
2
\$\begingroup\$

Excel, (削除) 48 (削除ここまで) 58 bytes

Revised to return N spaces instead of just the value of N.

=LET(t,TRIM(A:A),REPT(" ",MIN(IF(t="","",FIND(t,A:A)))-1))

Trim the leading and trailing white space from each line then find the position what's left in the original line, ignoring those lines that are empty once they're trimmed. Find the minimum of those positions minus 1. Return a space repeated that many times.

Screenshot

Screenshot shows formula returning repeated pipes instead of spaces so you can see the results.

answered Feb 15, 2022 at 17:34
\$\endgroup\$
2
  • 1
    \$\begingroup\$ @KevinCruijssen Thanks, it's been corrected now. \$\endgroup\$ Commented Feb 15, 2022 at 20:36
  • 1
    \$\begingroup\$ Lol, I had not considered Excel as a language. Awesome! \$\endgroup\$ Commented Feb 16, 2022 at 16:53
2
\$\begingroup\$

Pip -r, 15 bytes

@SS Ks~Y#||_FIg

Takes input from stdin; prints a string of spaces (with a trailing newline) to stdout. Outputs nothing if the input contains only whitespace. Attempt This Online!

Explanation

The -r flag reads all of stdin and stores it as a list of lines in g. Then:

@SS Ks~Y#||_FIg
 FIg ; Filter g on this function:
 || ; Strip whitespace from
 _ ; each line
 # ; and get the length of what remains
 Y ; Yank the result (used here just to manipulate precedence)
 ~ ; Get first match of this regex in (each line of) the above:
 s ; Preset variable: space
 K ; Kleene star (results in this regex: ` *`)
 SS ; Sort in lexicographic order (in this case, shortest first)
@ ; Get the first element
answered Feb 15, 2022 at 17:19
\$\endgroup\$
0
2
\$\begingroup\$

JavaScript (Node.js), 39 bytes

s=>/( *)\S(.|\s+(1円|$))*$|$/.exec(s)[1]

Try it online!

Return undefined if no line

JavaScript (Node.js), 37 bytes

s=>/( *)\S(.|\s+(1円|$))*$/.exec(s)[1]

Try it online!

Throw if no line

answered Feb 15, 2022 at 2:20
\$\endgroup\$
2
  • \$\begingroup\$ OP said to return nullish or the empty string and I would not interpret throwing to count as that... But you can return undefined for only +2 bytes. \$\endgroup\$ Commented Feb 16, 2022 at 0:56
  • 1
    \$\begingroup\$ Also I'd like to note that this is a pure-regex solution and thus could also be answered in the language "Regex (ECMAScript)". I was going to do so, but I can't think of any solution shorter than yours. \$\endgroup\$ Commented Feb 16, 2022 at 1:01
1
\$\begingroup\$

Charcoal, 23 bytes

×ばつ ∨⌊ΦEυ⌕E⪪ι ¬λ0⊕ι0

Try it online! Link is to verbose version of code. Takes input by default as a list of newline-terminated strings, so if you want to provide an empty string as part of the input you'll have to encode it as a JSON list instead. Explanation:

WS⊞υι

Input the string.

×ばつ ∨⌊ΦEυ⌕E⪪ι ¬λ0⊕ι0

Split each line on spaces and find the index of the first non-space character, but filter out those lines that only have spaces, and then take the minimum number of spaces, or 0 if all lines only have spaces, and repeat a space that many times.

answered Feb 15, 2022 at 9:41
\$\endgroup\$
2
  • 1
    \$\begingroup\$ @JonathanAllan I hadn't accounted for the edge case when all the input lines only contain spaces, in which case I'm trying to repeat a string by the minimum of an empty list, which doesn't work. \$\endgroup\$ Commented Feb 15, 2022 at 13:34
  • 1
    \$\begingroup\$ @JonathanAllan Sometimes I wish the sum or minimum of an empty list would be zero like it is in Jelly... at least for sum I have a workaround by using base 1 conversion instead, which is only 1 byte longer. \$\endgroup\$ Commented Feb 15, 2022 at 13:36
1
\$\begingroup\$

Ruby, 29 bytes

->a{a.map{_1[/^ +/]}.sort[0]}

Attempt This Online!

answered Oct 10, 2022 at 21:05
\$\endgroup\$
0
\$\begingroup\$

Perl 5, 38 bytes

print[sort map/^ +/g,grep/\S/,<>]->[0]

Try it online!

answered Feb 15, 2022 at 12:49
\$\endgroup\$
0
\$\begingroup\$

Burlesque, 23 bytes

t]:nzm{Jt[.-}saq<]qesIE

Try it online!

t]:nz # Filter null/wspc lines
m{ # Map
 J # Dup
 t[ # Trim left
 .- # String Difference
} -- Diff pre/post trim
sa # non-destructive Length
q<] # If non-zero length find max
qes # Else Empty Block -> Empty string
IE # If Else
answered Feb 15, 2022 at 11:55
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @JonathanAllan Darn, missed that element. This should work now. \$\endgroup\$ Commented Feb 15, 2022 at 14:58
  • 2
    \$\begingroup\$ TIO version seems to output the longest of all whitespace prefixes, not the longest present in every line (which, confusingly, is the shortest...) try it \$\endgroup\$ Commented Feb 15, 2022 at 16:02
  • \$\begingroup\$ In my haste, I'd completely misunderstood the question. \$\endgroup\$ Commented Feb 15, 2022 at 16:51
0
\$\begingroup\$

Scala, 51 bytes

"\\n[\\s]*".r.findAllIn("\n"+y).map(_.length).min-1

Try it online!

Explanation:

"\\n[\\s]*".r // regex to find newlines and all whitespaces after
 .findAllIn( // iterator of all matches in the given string
 "\n"+y) // add a newline to the front to count first line
 .map(_.length).min // find the shortest match
 -1 // subtract one since lengths include the newlines
answered Feb 15, 2022 at 20:17
\$\endgroup\$
0
\$\begingroup\$

J-uby, 28 bytes

:*&(~:[]&/^ */)|:sort|:first

Attempt This Online!

Explanation

:* & ( # Map lines with...
 ~:[] & /^ */ # Get part that matches /^ */
) | # then
:sort | :first # sort, then get the first
answered Oct 10, 2022 at 21:16
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.