📣 独立日限时特惠: VIP通行证立减$68
查看: 8483| 回复: 11
跳转到指定楼层
上一主题 下一主题
收起左侧

[字符串] 求教Airbnb的CSV parser

全局:

注册一亩三分地论坛,查看更多干货!

您需要 登录 才可以下载或查看附件。没有帐号?注册账号

x
这道题感觉网上没有一个正确的答案……答案要么不能处理双重"", 要么不能处理逗号在引号里面的情况,如"San Francisco, CA". 感觉找不到一个好的方法,既处理双重"", 又处理逗号在引号里面的情况。。。。

求各位大牛指点!

上一篇:DP题目的空间space需要优化吗?从二维到一维
下一篇:汇集好的leetcode解题报告网站
推荐
magicsets 2018-1-16 01:53:22 | 只看该作者
全局:
这类问题用“确定有限状态自动机”(DFA,Deterministic Finite Automaton)来解决是逻辑最清晰的

搜了一个blog,举的例子还算清晰,LZ可以读一读:http://helloitworks.com/754.html

然后CSV parser这个题目,我写了份参考代码,不知道是不是满足所有要求:

  1. public class Main {
  2.   public static void main(String[] args) {
  3.     String doc =
  4.         "John,Smith,john.smith@gmail.com,Los Angeles,1\n" +
  5.         "Jane,Roberts,janer@msn.com,\"San Francisco, CA\",0\n" +
  6.         "\"Alexandra \"\"Alex\"\"\",Menendez,alex.menendez@gmail.com,Miami,1\n" +
  7.         "\"\"\"Alexandra Alex\"\"\"";

  8.     System.out.println(CSVParser.Convert(doc));
  9.   }
  10. }


  11. class CSVParser {
  12.   private enum State {
  13.     FIELD_START,
  14.     QUOTE_LESS,
  15.     QUOTE_START,
  16.     QUOTE_IN_QUOTE
  17.   }

  18.   public static String Convert(String doc) {
  19.     StringBuilder sb = new StringBuilder();
  20.     State state = State.FIELD_START;

  21.     for (int i = 0; i < doc.length(); ++i) {
  22.       char ch = doc.charAt(i);

  23.       switch (state) {
  24.         case FIELD_START:
  25.           if (ch == '"') {
  26.             state = State.QUOTE_START;
  27.             continue;
  28.           }
  29.           state = State.QUOTE_LESS;
  30.           break;
  31.         case QUOTE_LESS:
  32.           switch (ch) {
  33.             case ',':  ch = '|';  // fall through
  34.             case '\n': state = State.FIELD_START; break;
  35.             case '"':  Error(i, ch);
  36.           }
  37.           break;
  38.         case QUOTE_START:
  39.           if (ch == '"') {
  40.             state = State.QUOTE_IN_QUOTE;
  41.             continue;
  42.           }
  43.           break;
  44.         case QUOTE_IN_QUOTE:
  45.           switch (ch) {
  46.             case '"': state = State.QUOTE_START; break;
  47.             case ',': state = State.FIELD_START; ch = '|'; break;
  48.             default:  Error(i, ch);
  49.           }
  50.           break;
  51.       }
  52.       sb.append(ch);
  53.     }

  54.     return sb.toString();
  55.   }

  56.   private static void Error(int position, char ch) {
  57.     throw new RuntimeException(
  58.         "Invalid character " + ch + " at position " + position);
  59.   }
  60. }
复制代码

评分

参与人数 7大米 +24 收起 理由
chasematrix + 3 给你点个赞!
14417335 + 5 很有用的信息!
louise + 2 很有用的信息!
红红火火恍恍惚h + 3 很有用的信息!
尼尼678 + 5 给你点个赞!

查看全部评分

回复

使用道具 举报

推荐
 楼主| flykite083 2018-1-16 03:03:43 | 只看该作者
全局:
magicsets 发表于 2018-1-16 02:32
我大概知道其中的问题在哪了...

一般我们考虑CSV格式,是按这个标准:https://tools.ietf.org/html/rf ...

多谢多谢!现在豁然开朗了!就是""San Francisco, CA""这个并不valid. 因为field中间的双引号只有1个,没有成对出现。
回复

使用道具 举报

推荐
jason524 2019-3-10 12:41:20 | 只看该作者
全局:
在magicsets老哥的基础上改进了一下,原本的test input最后一行1后面的\n好像是个typo.
而且这里省掉了一个状态,算是一个简化。

  1. class Solution {

  2.     public static void main(String[] args) {
  3.         String doc =
  4.                 "John,Smith,[email]john.smith@gmail.com[/email],Los Angeles,1\n" +
  5.                         "Jane,Roberts,[email]janer@msn.com[/email],\"San Francisco, CA\",0\n" +
  6.                         "\"Alexandra \"\"Alex\"\"\",Menendez,[email]alex.menendez@gmail.com[/email],Miami,1 " +
  7.                         "\"\"\"Alexandra, Alex\"\"\"";

  8.         System.out.println(CSVParser.Convert(doc));
  9.     }

  10.     static class CSVParser {
  11.         private enum State {
  12.             FIELD_START,
  13.             QUOTE_START,
  14.             QUOTE_IN_QUOTE
  15.         }

  16.         public static String Convert(String doc) {
  17.             StringBuilder sb = new StringBuilder();
  18.             State state = State.FIELD_START;

  19.             for (int i = 0; i < doc.length(); ++i) {
  20.                 char ch = doc.charAt(i);

  21.                 switch (state) {
  22.                     case FIELD_START:
  23.                         if (ch == '"') {
  24.                             state = State.QUOTE_START;
  25.                             continue;
  26.                         } else if (ch == ',') {
  27.                             ch = '|';
  28.                         }
  29.                         break;
  30.                     case QUOTE_START:
  31.                         if (ch == '"') {
  32.                             state = State.QUOTE_IN_QUOTE;
  33.                             continue;
  34.                         }
  35.                         break;
  36.                     case QUOTE_IN_QUOTE:
  37.                         switch (ch) {
  38.                             case '"': state = State.QUOTE_START; break;
  39.                             case ',': state = State.FIELD_START; ch = '|'; break;
  40.                             default:  Error(i, ch);
  41.                         }
  42.                         break;
  43.                 }
  44.                 sb.append(ch);
  45.             }

  46.             return sb.toString();
  47.         }

  48.         private static void Error(int position, char ch) {
  49.             throw new RuntimeException(
  50.                     "Invalid character " + ch + " at position " + position);
  51.         }
  52.     }
  53. }
复制代码
回复

使用道具 举报

🔗
 楼主| flykite083 2018-1-16 02:01:47 | 只看该作者
全局:
magicsets 发表于 2018-1-16 01:53
这类问题用“确定有限状态自动机”(DFA,Deterministic Finite Automaton)来解决是逻辑最清晰的

搜了 ...

多谢多谢!跑了一下你的代码,这个case仍然过不了:"Jane,Roberts,janer@msn.com,\"\"San Francisco, CA\"\",0\n"
把San Francisco, CA加双引号,就过不了了。
回复

使用道具 举报

🔗
magicsets 2018-1-16 02:22:01 | 只看该作者
全局:
flykite083 发表于 2018-1-16 02:01
多谢多谢!跑了一下你的代码,这个case仍然过不了:"Jane,Roberts,,\"\"San Francisco, CA\"\",0\n"
把S ...

这种情况不是一个合法的输入吧

考虑下面三种情况:
(a)
"San Francisco, CA"

(b)
""San Francisco, CA""

(c)
"""San Francisco, CA"""



其中的a和c比较容易理解:

(1) a的左右双引号是用于quote里面的内容,应当去掉,所以输出就是 San Francisco, CA

(2) 对于c,考虑左边的三个双引号,第一个是用于quote后面的内容,而第二个是用于escape第三个双引号,所以只保留第三个双引号。

然后右边三个双引号也是类似处理:第四个双引号escape第五个双引号,第六个双引号与第一个双引号一起quote其中的内容,所以只保留第五个双引号。

所以输出就是 "San Francisco, CA"


那么现在问题来了,对于b,左边那两个双引号,第一个用于quote,那第二个双引号的语义是什么呢?或者说,期望的输出是什么?
回复

使用道具 举报

🔗
magicsets 2018-1-16 02:32:49 | 只看该作者
全局:
flykite083 发表于 2018-1-16 02:01
多谢多谢!跑了一下你的代码,这个case仍然过不了:"Jane,Roberts,,\"\"San Francisco, CA\"\",0\n"
把S ...

我大概知道其中的问题在哪了...

一般我们考虑CSV格式,是按这个标准:https://tools.ietf.org/html/rfc4180#page-2

语法部分是这样:
  1. file = [header CRLF] record *(CRLF record) [CRLF]

  2. header = name *(COMMA name)

  3. record = field *(COMMA field)

  4. name = field

  5. field = (escaped / non-escaped)

  6. escaped = DQUOTE *(TEXTDATA / COMMA / CR / LF / 2DQUOTE) DQUOTE

  7. non-escaped = *TEXTDATA

  8. COMMA = %x2C

  9. CR = %x0D

  10. DQUOTE =  %x22

  11. LF = %x0A

  12. CRLF = CR LF

  13. TEXTDATA =  %x20-21 / %x23-2B / %x2D-7E
复制代码
阅读这个需要一点点编译原理的基础,不过和你的疑问相关的最重要一点是:

只有当开头有双引号时,一个field中间才可以有双引号,且必须成对出现

不满足以上要求的输入即不符合CSV格式

也就是说,类似
"abc""def"
这样的是合法的输入

类似
""abcdef""
abc""def
这样的是不合法的输入,并不满足CSV格式要求

评分

参与人数 1大米 +3 收起 理由
14417335 + 3 给你点个赞!

查看全部评分

回复

使用道具 举报

🔗
Gosod 2018-6-2 19:36:17 | 只看该作者
全局:
flykite083 发表于 2018-1-16 03:03
多谢多谢!现在豁然开朗了!就是""San Francisco, CA""这个并不valid. 因为field中间的双引号只有1个,没 ...

所以对于这个invalid的输入""San Francisco,CA""是返回San Francisco|CA""(想微软的excel)打开那样,还是这个输入就不算做testcase了呢?
回复

使用道具 举报

全局:
赞赞!面试遇到这个题目了 学习了学习了
回复

使用道具 举报

🔗
fin 2019-2-18 14:53:55 | 只看该作者
全局:
我之前练习的时候也写过这道题,贴一下我的解法。只处理单行CSV的情况。多行的话方法类似

  1. def parseCsvLine(self, line):
  2.     fields = []
  3.     field = ''
  4.     inQuote = False
  5.     i = 0

  6.     while i < len(line):
  7.         c = line[i]
  8.         if inQuote:
  9.             if c != '"':
  10.                 field += c
  11.             elif i+1 < len(line) and line[i+1] == '"':
  12.                 field += '"'
  13.                 i += 1
  14.             else:
  15.                 inQuote = False
  16.                 fields.append(field)
  17.                 field = ''
  18.                 while i < len(line) and line[i] != ',':
  19.                     i += 1
  20.         else:
  21.             if c == '"':
  22.                 inQuote = True
  23.             elif c == ',':
  24.                 fields.append(field.strip())
  25.                 field = ''
  26.             elif c != ' ' or field:
  27.                 field += c
  28.         i += 1

  29.     if field:
  30.         fields.append(field)

  31.     return '|'.join(fields)
复制代码

评分

参与人数 1大米 +5 收起 理由
14417335 + 5 给你点个赞!

查看全部评分

回复

使用道具 举报

🔗
gamesover 2019-2-28 16:38:31 | 只看该作者
全局:
  1. def csv_parser(list)
  2.   list.each do |str|
  3.     p parse(str)
  4.   end
  5. end

  6. def parse(str)
  7.   result = ''
  8.   in_quote = false
  9.   quotes = 0

  10.   (0...str.size).each do |i|
  11.     if str[i] == '"'
  12.       quotes += 1
  13.       if i == str.size - 1 || str[i + 1] != '"'
  14.         result += '"' * (quotes / 2)
  15.         in_quote = !in_quote if quotes.odd?
  16.         quotes = 0
  17.       end
  18.     else
  19.       ch = !in_quote && str[i] == ',' ? '|' : str[i]
  20.       result += ch
  21.     end
  22.   end

  23.   result
  24. end

  25. test = [
  26.   'John,Smith,[email]john.smith@gmail.com[/email],Los Angeles,1',
  27.   'Jane,Roberts,[email]janer@msn.com[/email],"San Francisco, CA",0',
  28.   '"Alexandra, ""Alex""",Menendez,[email]alex.menendez@gmail.com[/email],Miami,1',
  29.   '"""Alexandra Alex"""'
  30. ]
  31. csv_parser test
复制代码


用ruby写的,但语言选项只有python
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册账号
隐私提醒:
  • ☑ 禁止发布广告,拉群,贴个人联系方式:找人请去🔗同学同事飞友,拉群请去🔗拉群结伴,广告请去🔗跳蚤市场,和 🔗租房广告|找室友
  • ☑ 论坛内容在发帖 30 分钟内可以编辑,过后则不能删帖。为防止被骚扰甚至人肉,不要公开留微信等联系方式,如有需求请以论坛私信方式发送。
  • ☑ 干货版块可免费使用 🔗超级匿名:面经(美国面经、中国面经、数科面经、PM面经),抖包袱(美国、中国)和录取汇报、定位选校版
  • ☑ 查阅全站 🔗各种匿名方法

本版积分规则

>
快速回复 返回顶部 返回列表