Processing:数据输入(上)

1. 方法indexOf()返回一个关键字(或词)在字符串中的位置,它有一个引数:关键字。例如下例将返回数值3:

String search = "def";  
String toBeSearched = "abcdefghi"; 
int index = toBeSearched.indexOf(search);

2. 逐一println()一下下边诸例,数一数(记住一个字符串的第一位是0而不是1,因为他是一个数组,所以你应当从0数起),你便会了解它的用法:

String sentence = "The quick brown fox jumps over the lazy dog." ;  
println(sentence.indexOf("quick"));  
println(sentence.indexOf("fo"));  
println(sentence.indexOf("The"));  
println(sentence.indexOf("blah blah"));

3. 是的,试到上边最后一个例子的时候,你会发现它返回的值是-1。这是一个很好的选择,因为在数组序号中不存在-1,因此,当indexOf()找不到一个关键字的时候,返回值-1来表述是再合适不过的。

4. 一个字符串的一部分被称为子串(substring),一个子串可以藉由substring()函数得到。它有两个引数:开始和结束的位置。比如下例的结果为“def”:

String alphabet = "abcdefghi" ;   
String sub = alphabet.substring(3,6);

5. 通过上例我们发现,子串开始于给定的开始位置(3),却结束于给定的结束位置减一(6)。减一的好处是,如果你需要的子串结束于字符串末尾,你可以方便的使用thestring.length(),同时,你可以用结束位置减去起始位置来简单的计算子串的长度。

6. 你可以这样从整个字符串中获取到”quick brown fox jumps over the lazy dog”:

String sentence = "The quick brown fox jumps over the lazy dog."; 
int foxIndex=sentence.indexOf("fox jumps over the lazy dog");  
int periodIndex = sentence.indexOf(".");  
println(sentence.substring(foxIndex,periodIndex));

当然,如果你愿意数的话,你也可以这样写:

String sentence = "The quick brown fox jumps over the lazy dog.";  
println(sentence.substring(16,(sentence.length()-1)));

第二个方式,虽然现在看起来比第一种写得更少,但是设想一下如果要在一篇文章中截取某个部分的话,硬数还不数死你?!

7. 下例实现了用户键盘输入录入:

PFont f;

// 保存输入文字的变量
String typing = "";

// 保存回车后保存文字的变量
String saved = "";

void setup() {
  size(300,200);
  f = createFont("Arial",16,true);
}

void draw() {
  background(255);
  int indent = 25;
  
  // 设置字体和文本颜色
  textFont(f);
  fill(0);
  
  // 显示一切
  text("点击这个小程序并用键盘输入。\n敲回车保存你的输入。", indent, 40);
  text(typing,indent,90);
  text(saved,indent,130);
}

void keyPressed() {
  // 如果回车键按下, 保存并清除字符串
  if (key == '\n' ) {
    saved = typing;
    // 可以通过将一个字符串设置等于""实现将其清除
    typing = ""; 
  } else {
    // 否则,串联字符串
    // 用户输入的每个字符都被加到String变量之后.
    typing = typing + key; 
  }
}

8. p5提供了其他关于字符串组合及拆分的函数:split()join()。顾名思义,split()用来将一个长字符串拆解为一个字符串数组,它有两个引数:将被拆解的字符串和分界符(delimiter):

// 用空格作为界定符拆分一个字符串 
String spaceswords = "oNEwAY is a Post Rock band from Kunming." ;   
String[] list = split(spaceswords, " ");   
for (int i = 0; i < list.length; i++) {   
  println(list[i] + " " + i);      
}

或者用逗号作为界定符:

// 用逗号作为界定符拆分一个字符串    
String commaswords = "oNEwAY,is,a,Post,Rock,band,from,Kunming." ;   
String[] list = split (commaswords, ',');
for (int i = 0; i < list.length; i++) {   
  println(list[i] + " " + i);      
}

二者运行后在信息窗口均返回结果:
oNEwAY 0
is 1
a 2
Post 3
Rock 4
band 5
from 6
Kunming. 7

9. 如果要使用多于两个分界符,你需要使用splitTokens()函数,它与split()的用法基本一致,除了一点区别:字符串中的任何元素都有作为一个界定符的机会:

// 多分界符拆分字符串 
String stuff = "hats  &  apples, cars + phones % elephants dog.";   
String[] list = splitTokens(stuff, "& , + .");   
for (int i = 0; i < list.length; i++) {   
  println(list[i] +  " " + i);      
}

运行后信息窗口返回结果:
hats 0
apples 1
cars 2
phones 3
% 4
elephants 5
dog 6

由此可见,作为界定符的元素将不会出现在数组中。

10. 如果你要拆分一个字符串中的数字,可用int()函数将结果数组转化为一个整数数组:

// 计算字符串内各数字之和 
String numbers = "8,67,5,309";   
// 转化字符串数组为整数数组
// 字符串中的数字并非数字并不可用于数学计算,除非事先将它们转为数字类型
int[] list = int(split(numbers, ','));   
int sum = 0;   
for (int i = 0; i < list.length; i++) {   
  sum = sum + list[i];      
}
println(sum);

11. join()的过程与split()正相反,它是将一个字符串数组整合为一个长字符串。同样是两个引数:欲结合数组,分隔符。
假设这样一个数组:

String[] lines = {"It","was","a","dark","and","stormy","night."};

用“+”和for循环,我们可以这样连接它们:

// 手工连接 
String onelongstring = " ";  
for (int i = 0; i < lines.length; i++) {  
  onelongstring = onelongstring + lines[i] + " ";     
} 

使用join(),一切得到最大简化:

String onelongstring = join (lines, " ");

12. p5可以读取一个外部txt档:

String[] lines = loadStrings("text.txt");   
println ("there are " + lines.length + " lines");
for (int i = 0; i < lines.length; i++){   
  println(lines[i]);      
}

是的,仍然,作为常识,你需要将text.txt文件放入这个sketch下的data文件夹内。

13. 下例为一个可视化效果的初级例子,将data.txt中的数据转化为柱状图显示:
13.1. 创建并保存data.txt,内容(当然,你可以随便写):131,85,87,16,169,140,153,72,113,123
13.2. 用p5运行如下代码:

int[] data;

void setup() {
  size(200,200);
 // 文件中的文字被载入一个数组. 
  String[] stuff = loadStrings("data.txt");
  // 这个数组只有一个元素因为它只有一行. 
  // 用逗号作为分界符,并将数组转化为整数数组
  data = int(split(stuff[0], ',' ));
}

void draw() {
  background(255);
  stroke(0);
  
  for (int i = 0; i < data.length; i ++ ) {
    // 数组元素被用于设置矩形的颜色和高度.
    fill(data[i]); 
    rect(i*20,0,20,data[i]);
  }
  noLoop();
}

14. 继续一个稍微高级的例子,将文本文件内的数据传入一个对象的构造器:

Bubble[] bubbles;

void setup() {
  size(200,200);
  smooth();
  
  // 以字符串数组的方式载入文本文件
  String[] data = loadStrings("data.txt");
  
  // 气泡对象数组的大小由文本文件中总的行数决定
  bubbles = new Bubble[data.length]; 
  for (int i = 0; i < bubbles.length; i ++ ) {
    // 每行都被拆分为一个小数数组.
    float[] values = float(split(data[i], "," )); 
    // 数组中的值被传递入气泡类的构造器.
    bubbles[i] = new Bubble(values[0],values[1],values[2]); 
  }
}

void draw() {
  background(255);
  
  // 显示并移动所有气泡
  for (int i = 0; i < bubbles.length; i ++ ) {
    bubbles[i].display();
    bubbles[i].drift();
  }
}

class Bubble {
  float x,y;
  float diameter;
  float speed;
  float r,g;

  // 构造器初始化颜色和大小
  // 随机决定位置
  Bubble(float r_, float g_, float diameter_) {
    x = random(width);
    y = height;
    r = r_;
    g = g_;
    diameter = diameter_;
  }

  // 显示气泡
  void display() {
    stroke(0);
    fill(r,g,255,150);
    ellipse(x,y,diameter,diameter);
  }

  // 移动气泡
  void drift() {
    y += random(-3,-0.1);
    x += random(-1,1);
    if (y < -diameter*2) {
      y = height + diameter*2;
    }
  }
}

上例中data.txt内的内容为(当然,你可以随便写):
88,149,22
193,78,8
90,152,56
136,18,37
47,2,55
36,142,57
10,61,31
9,121,49
156,60,12
71,200,21

15. 现在我们已经学会舒服的载入信息,然后准备问接下来的这个问题:我们该如何存储信息然后在下一次程序运行的时候载入新信息?比如说,让上例的气泡们当鼠标在其上滚动的时候变化。

// Bubble类中rollover()函数依据引数(mx, my)是否位于气泡内返回一个布尔值(真或假)
boolean rollover(int mx, int my) {   
  if (dist(mx,my,x,y) < diameter/2) {   
    return true;      
  } else {   
    return false;      
  }       
}

我们将鼠标位置(mx, my)到气泡圆心的距离与气泡半径的距离作比较,便可以确定鼠标是否位于圆内。

检查鼠标是否位于气泡内
检查鼠标是否位于气泡内
for (int i = 0; i < bubbles.length; i++) {  
  bubbles[i].display(); 
  bubbles[i].drift(); 
  if (bubbles[i].rollover(mouseX,mouseY)) {  
    bubbles[i].change(); 
  }       
}

一旦我们执行change()函数以调整Bubble的变量,我们便可以使用p5的saveStrings()函数将新信息存储到一个文本文件中。saveStrings()函数从本质上与loadStrings()相反,它接受一个文件名和一个字符串数组并且将那个数组保存到文件中。

String[] stuff = {"Each String", "will be saved", "on a", "separate line"};  
saveStrings("data.txt", stuff);

保存并运行上边这段代码,将在你的文件夹根目录内(注意不是data文件夹)创建如下txt文档。

data.txt
data.txt

如果你想要将此文件写入data文件夹,那么你需要指定路径。同样,如果该文件已经存在,它将被重写。

16. OK,具备以上知识之后,我们便可以为气泡例子整一个saveData()函数来改变"data.txt"中的信息。在本例中,我们将在鼠标点击后存储新的数据。

// 一个气泡对象的数组
Bubble[] bubbles;

void setup() {
  size(200,200);
  smooth();

  // 将文本文件作为一个字符串数组载入
  String[] data = loadStrings("data.txt");

  // 气泡对象数组的大小由文本文件中总的行数决定
  bubbles = new Bubble[data.length]; 
  for (int i = 0; i < bubbles.length; i ++ ) {
    // 每行都被拆分为一个小数数组.
    float[] values = float(split(data[i], "," )); 
    // 数组中的值被传递入气泡类的构造器.
    bubbles[i] = new Bubble(values[0],values[1],values[2]); 
  }
}

void draw() {
  background(255);

  // 显示并移动所有气泡
  for (int i = 0; i < bubbles.length; i ++ ) {
    bubbles[i].display();
    bubbles[i].drift();

    // 如果鼠标在气泡上滚动,则改变气泡
    if (bubbles[i].rollover(mouseX,mouseY)) {
      bubbles[i].change();
    }
  }
}

// 当鼠标按下时保存新的气泡数据
void mousePressed() {
  saveData();
}

void saveData() {
  // 对每个气泡存储一个字符串
  String[] data = new String[bubbles.length];

  for (int i = 0; i < bubbles.length; i ++ ) {
    // 连接气泡变量
    data[i] = bubbles[i].r + " , " + bubbles[i].g + " , " + bubbles[i].diameter;
  }

  // 存储到文件
  // 指向data文件夹使用saveStrings()重写同一个文件
  path to saveStrings().
  saveStrings("data/data.txt", data); 
}

// Bubble类
class Bubble {
  float x,y;
  float diameter;
  float speed;
  float r,g;
  
  Bubble(float r_,float g_, float diameter_) {
    x = random(width);
    y = height;
    r = r_;
    g = g_;
    diameter = diameter_;
  }
  
  // 检查鼠标是否位于气泡内
  boolean rollover(int mx, int my) {
    if (dist(mx,my,x,y) < diameter/2) {
      return true;
    } else {
      return false;
    }
  }
  
  // 改变气泡变量
  void change() {
    r = constrain(r + random(-10,10),0,255);
    g = constrain(g + random(-10,10),0,255);
    diameter = constrain(diameter + random(-2,4),4,72);
  }
  
  // 显示气泡
  void display() {
    stroke(0);
    fill(r,g,255,150);
    ellipse(x,y,diameter,diameter);
  }
  
  // 气泡向上漂流
  void drift() {
    y += random(-3,-0.1);
    x += random(-1,1);
    if (y < -diameter*2) {
      y = height + diameter*2;
    }
  }
}

17. 我们还可以使用一个统一资源定位器(URL)当作loadStrings()的参数:

String[] lines = loadStrings("http://www.douban.com/artist/oneway");

运行它的结果将返回你输入URL页面的源文件。要继续接下来的学习,你无须是一个HTML专家,但如果你完全不熟悉HTML,你可能需要阅读:http://en.wikipedia.org/wiki/HTML

18. 不像以逗号划界的文本文件,将HTML源文件存入一个字符串数组是不实用的(每个元素代表源文件中的一行)。将数组转存为一个长的字符串可以让事情简单一些。之前说过的,用join()可以做到这一点:

String onelongstring = join(lines, "  " );

19. 当获取到网页源文件后,很多情况我们仅需要其中的一小段。也许是气象信息,股市行情,或者一个新闻标题。我们可以使用indexOf(), substring(), 和length()来为我们从一对文字中找到我们需要的数据。用以下字符串作为例子:

String stuff = "Number of apples:62. Boy, do I like apples or what!";

比如说我们要从以上字符串中提取苹果的数量,我们的算法应该如下:
1)找到子字符串的结尾“apples:”。称其为开始。
2)找到 “apples:”之后的第一个句号。称其为结束。
3)在开始和结束间创建一个子字符串。
4)将字符串转换至一个数字(如果我们希望如此使用它)
写成代码:

// 一个字符串的“结束点”可以通过搜索那个字符串的索引并加上其长度(本例中“apples:”长度为7)来获得
int start = stuff.indexOf("apples:") + 7;        // 第一步   
int end = stuff.indexOf(".", start);      // 第二步   
String apples =  stuff.substring(start,end);        // 第三步   
int apple_no = int(apples);        // 第四步

20. 以上代码属于小聪明,但我们应当更谨慎,以免因无法找到子字符串而产生错误。我们可以加入一些查错的代码,然后将它们做入一个函数:

// 在两个子字符串间返回一个子字符串的函数 
String giveMeTextBetween(String s, String startTag, String endTag) {   
  String found = " ";   
  // 找到开始标签的索引   
  int startIndex = s.indexOf(startTag);   
  // 如果我们一无所获   
  if (startIndex == –1) return " ";   
  // 移动到开始标签的末尾   
  startIndex += startTag.length();   
  // 找到结束标签的索引 
  int endIndex = s.indexOf(endTag, startIndex); 
  //  如果我们一无所获 
  if (endIndex == –1) return " ";  
  // 返回其间的文字 
  return s.substring(startIndex,endIndex); 
}

21. 然而了解HTML的同学都该知道,要从它里边提取出有用的数据是如此困难的一件事。其实对于从网站获取数据来说,一个XML(扩展标记语言)的feed将更可靠和易于分析。我们首先从雅虎天气获取XML feed:
http://xml.weather.yahoo.com/forecastrss?p=CHXX0076&u=c
在p5中,从一个XML feed获取数据的方式是使用XML库。但是,为了从一个较低的层面映证字符串分析,作为一个练习,我们将使用我们的loadStrings()手动剥离提取技术。

22. 透过上边那个XML的源代码(建议用Mac的同学使用FireFox查看,Safari无法查看或我还不知道如何查看,因为Safari会主动将这个链接转换为"feed://"打头的),我们可以看到2010年1月28日晚上十点昆明的气温是11摄氏度(也~比起你们来说很暖和吧~)。

<yweather:condition  text="Fair"  code="33"  temp="11"  date="Thu, 28 Jan 2010 10:00 pm CST" />

气温总在变化而XML的格式不会,因此我们可以推论我们搜索的开始标签应该是:

temp="

结束标签是:

"

23. 知道了开始和结束标签,我们便可使用上边那个giveMeTextBetween()来提取温度了:

String url = "http://xml.weather.yahoo.com/forecastrss?p=CHXX0076&u=c";   
String[] lines = loadStrings(url);   
// 为搜索整个页面而处理数组   
String xml = join(lines," ");   
// 搜索温度   
String tag1 = "temp = \"";   
String tag2 = "\"";   
temp = int(giveMeTextBetween (xml,tag1,tag2));   
println(temp);

注意,要在Java中显示一个引号,需要在要显示引号前加上一个右斜杠:

String q = "这个字符串有一个\"号";

24. 下例从雅虎天气XML feed中检索温度并将之显示于萤幕上。这个例子同样使用了面向对象编程,将所有字符串分析功能做入一个WeatherGrabber类:

PFont f;
String[] zips = { "CHXX0076" , "21209" , "90210" };
int counter = 0;

// WeatherGrabber对象为我们服务!
WeatherGrabber wg;

void setup() {
  size(200,200);
  
  // 创建一个WeatherGrabber对象
  wg = new WeatherGrabber(zips[counter]);
  // 告诉它申请天气
  wg.requestWeather();
  
  f = createFont( "Lucida" ,16,true);
}

void draw() {
  background(255);
  textFont(f);
  fill(0);
  
  // 获取要显示的值
  String weather = wg.getWeather();
  int temp = wg.getTemp();
  
  // 显示所有我们想要显示的东西
  text(zips[counter],10,160);
  text(weather,10,90);
  text(temp,10,40);
  text("点击转换城市代码. " ,10,180);
  
  // 基于温度画一个小温度计
  stroke(0);
  fill(175);
  rect(10,50,temp*6,20);
}

void mousePressed() {
  // 增大计数器并获取下一城市代码的气温
  counter = (counter + 1) % zips.length;
  wg.setZip(zips[counter]);
  // 每当鼠标点击,这个数据被一个新的城市代码再次请求
  wg.requestWeather(); 
}

// WeatherGrabber类
class WeatherGrabber {
  
  int temperature = 0;
  String weather = "";
  String zip;
  
  WeatherGrabber(String tempZip) {
    zip = tempZip;
  }
  
  // 设置一个新的城市代码
  void setZip(String tempZip) {
    zip = tempZip;
  }
  
  // 获取温度
  int getTemp() {
    return temperature;
  }
  
  // 获取天气
  String getWeather() {
    return weather;
  }
  
  // 创建实际的XML请求
  void requestWeather() {
    // 将所有HTML/XML源代码放入一个字符串数组
    // (每行是数组中的一个元素)
    String url = "http://xml.weather.yahoo.com/forecastrss?p=" + zip + "&u=c";
    String[] lines = loadStrings(url);
    
    // 将数组转入一个长的字符串
    String xml = join(lines, ""); 
    
    // 搜索气候状况
    String lookfor = "yweather:condition  text= \"";
    String end = "\"";
    weather = giveMeTextBetween(xml,lookfor,end);
    
    // 搜索气温
    lookfor = "temp=\"";
    temperature = int(giveMeTextBetween (xml,lookfor,end));
  }
  
  // 一个从两个子字符串之间返回子字符串的函数
  String giveMeTextBetween(String s, String before, String after) {
    String found = "";
    int start = s.indexOf(before);    // 找到开始标签的索引
    if (start == - 1) return"";       // 如果一无所获,返回一个空字符串
    start += before.length();         // 移动到开始标签的末尾
    int end = s.indexOf(after,start); // 找到结束标签的索引
    if (end == -1) return"";          // 如果一无所获,返回一个空字符串
    return s.substring(start,end);    // 返回两者之间的文本
  }  
}

无限崩溃中,碎了 T_T

Be Sociable, Share!

3 thoughts on “Processing:数据输入(上)”

  1. 你好!请问,Table怎么用啊,我在《可视化数据》一书看到的,我按书上写了代码,也下载了相关数据文件放在DATA里,但是processing说不能发现找到Table,是怎么回事啊?我用的版本是1.21。

    Reply

    ww1way Reply:

    请再进一步阐释你的问题

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *